后台默默的劳动者,Service
引言
前段时间,小编在修改bug的时候和Service打了不少交道。突然发现,已经很久没碰过Service了,有些东西记忆模糊,那么今天做一个Service总结,方便日后查看。
Service是什么?
Service是Android中实现程序后台运行的解决方案,它非常适合执行哪些不需要和用户交互而且要求长期运行的任务。Service的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行。
不过需要注意的是,Service并不是运行在一个独立的进程中,而是依赖于创建Service时所在的应用程序的进程。当某个应用程序进程被杀时,所有依赖于该进程的Service也会停止运行。
另外,也不要被Service的后台概念所迷惑,实际上Service并不会自动打开线程,所有的代码都是默认运行在主线程中。也就是说,我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。
Service的基本用法
定义一个Service
- MyService:我们将类的名字定义为MyService
- Exported:表示是否将这个Service暴露给外部其他程序访问
- Enabled:表示是否启用这个Service
创建好之后:
class MyService : Service() { |
可以看到,MyService继承自系统的Service类的。目前MyService中可以算是空空如也,但是有一个onBind()
方法特别醒目。这个是Service唯一的抽象方法。后面会讲解onBind
的使用,这里暂时不用它。
Service中同样有类似于Activity的生命周期方法,这里咱们先使用,后面会讲解Service的生命周期。
class MyService : Service() { |
这些是Service最常用的三个方法,看名字也知道是什么意思,这里就不浪费时间了。
另外,每一个Service都需要在AndroidManifest.xml
文件中注册才能生效(这也是四大组件的特点)。不过按照我上述的操作,直接右键New一个Service的话,AndroidStudio就会自动为我们注册,不用再手写,还是很方便的。
<service |
启动和停止Service
下面是两个按钮触发Service
binding.startServiceBtn.setOnClickListener { |
可以看到在startServiceBtn按钮的点击事件里,我们构建了一个Intent对象,并调用startService()
方法来启动MyService。在stopServiceBtn按钮的点击事件里,我们同样构建了一个Intent对象,并调用stopService()
方法来停止MyService。startService()
和stopService()
方法都是定义在Context
类中的,所以我们在Activity里可以直接调用这两个方法。另外,Service也可以自我停止运行,只需要在Service内调用stopself()
方法即可。
从Android8.0之后,应用的后台功能被大幅削减。现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时可能被系统回收。这样改动,是为了防止许多恶意的应用程序长期在后台占用手机资源,从而导致手机变得越来越卡。
那么,onCreate()和onStartCommand()方法的区别是什么呢?
- onCreate()方法是在Service第一次创建的时候调用
- 而onStartCommand()方法则在每次启动Service的时候都会调用
Activity和Service进行通信
通过上述的例子,我们可以知道Activity只是起到启动Service的作用,启动完之后,二者就基本没有关系了。
那如果我们想Activity与Service有关系,或者说Activity能控制Service,能交互,又该怎么办呢?那就要用到前面被搁置的onBind()
。
我们在MyService中模拟提供下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService中的代码。如下所示:
class MyService : Service() { |
这里,我们新建了一个DownloadBinder类,并让它继承自Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法。
接着,在MyService中创建了DownloadBinder的实例,然后再onBinder()方法里返回了这个实例,这样MyService中准备的工作就完成了。
lateinit var downloadBinder:MyService.DownloadBinder |
首先,我们创建了一个
ServiceConnection
的匿名内部类实现,并在里面重写了onServiceConnected()
方法和onServiceDisconnected()
方法
- onServiceConnected():会在Activity与Service成功绑定的时候调用
- onServiceDisconnected():只有在Service的创建进程崩溃或被杀掉的时候才会调用,这个方法不太常用
在
onServiceConnected()
方法中,我们通过向下转型得到了DownloadBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。当然,需要真正的绑定还需要
bindService()
,需要三个参数:
- Intent对象
- ServiceConnection实例,已经创建
- 一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使得MyServicede onCreate()方法得到执行,但onStartCommand()方法不会执行。
Service的生命周期
一旦在项目中的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。Service启动之后会一直保持运行状态,直到stopService()或stopself()方法被调用,或者被系统回收。注意,虽然每调用一次startService()方法,onStartCommaned()就会执行一次,但实际上每个Service只会保存一个实例。虽然不管你调用了多少次startService()方法,只需调用一次stopService()或stopself()方法,Service就会停止。
另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。类似地,如果这个Service之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回地IBinder对象的实例,这样就能自由地和Service进行通信了。只要调用方和Service之间的连接没有断开,Service就会一直保持运行状态,直到被系统回收。
当调用了startService()方法后,再去调用stopService()方法。这时,Service中的onDestroy()方法就会执行,表示Service已经销毁。类似的,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个Service既调用了startService()又调用了bindService()方法,在这种情况下该如何让Service销毁呢?根据Android系统的机制,一个Service只要被启动或绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
前台Service
前面说过,Android8.0以后,只有当应用保持在前台可见状态下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。而你如果希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service与后台Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。比如你的音乐、导航都用到了前台Service。
由于状态栏一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地直到当前什么应用正在运行,因此也不存在某些应用长期在后台偷偷占用手机资源的情况。
下面来看一下如何创建前台Service。
首先,需要在AndroidManifest.xml
中加入权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> |
class MyService : Service() { |
可以看到,这里只是修改了onCreate()方法中的代码,相信大家对这部分代码都比较熟悉——没错,就是创建通知(后续,小编会加一章关于通知的文章)。
- 创建通知渠道
channel
PendingIntent
用于让通知实现可点击进入应用- 然后是通知的创建
notification
调用startForeground()方法,两个参数:
- 通知的id
- 构建的Notification对象
看一下效果:
使用IntentService
Service中的代码都是默认运行在主线程,如果直接在Service里处理一些耗时任务,同样很容易出现ANR。
解决方法是开启一个子线程,并且还要记得线程执行完后主动stopself()
或调用stopService()
。
是不是很麻烦?!因为程序要很可能会忘记开线程,或者忘记调用stopself()
。
这个时候,我们的主角IntentService就诞生了!!!一个集Service、HandlerThread、Handler一身的“天才”(感兴趣的读者可以查一下它的原理,或者如果有需要可以联系小编出一期关于Handler和HandlerThread的文章,小编可以尝试聊聊它们的原理)
使用那就很简单了,不然我们怎么会用它呢,直接上代码:
|
创建方式也是与Service一样,可以右键直接new一个,亦或创建个类继承IntentService,同样的使用后者这种方式别忘了注册。
这里就不介绍使用了,因为IntentService和Service的使用一摸一样,而且大家分别打印Activity和IntentService中的当前线程Thread.currentThread().name
就会发现,IntentService自动为我们完成了线程切换,当然,这一切还是归功于Handler。
参考资料:《第一行代码》