基础
概念
Service
是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC
)。例如,服务可以处理网络事务、播放音乐,执行文件 I/O
或与内容提供程序交互,而所有这一切均可在后台进行。
选择使用服务还是线程
简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,但是它仍会在应用的主线程中运行。如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。
注意:
Service
和Activity
都属于UI
主线程,所以在Service
中同样需要开新线程来执行耗时操作
两种启动方式
- 启动服务
startService
组件间无交互:启动服务时,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如它可能通过网络下载或上传文件,操作完成后服务会自行停止运行。
- 绑定服务
bindService
组件间有交互:绑定到服务时,服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 ( IPC
)跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
实际使用中,大量会出现两种启动方式同时使用,既希望服务不受影响的在后台运行,又需要达到组件交互的目的。
清单属性
android:exported
这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true
,则能够被调用或交互。设置为false
,只有同一个应用程序的组件或带有相同用户ID
的应用程序才能启动或绑定该服务android:enabled
是否可以被系统实例化,默认为true
,表示服务能被激活,否则不会激活android:process
是否需要在单独的进程中运行,格式:android:process=”:remote”
:
后面表示新的进程名,为包名加上冒号后值,如:AppPackageName:remote
android:name
服务的名称
示例:
1 | <service |
启动服务 startService
重写回调 onStartCommand
通过调用 startService()
启动服务时,系统将调用此方法。源码:public int onStartCommand(Intent intent, int flags, int startId) {...}
其中 startId
为该服务的唯一标记符
返回值在 Service
被意外 Kill
掉后是否重启,代表的含义分别为:
START_NOT_STICKY
则除非有挂起Intent
要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务START_STICKY
会重建服务,并调用onStartCommand()
,传递一个空Intent
。如果有挂起Intent
要启动服务,才会传递这些Intent
,否则为空。适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)START_REDELIVER_INTENT
会重建服务,并通过传递给服务的最后一个Intent
调用onStartCommand()
。任何挂起Intent
均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务等START_STICKY_COMPATIBILITY
START_STICKY
的兼容版本,但不保证服务一定能重启
显示启动和停止服务
显示启动和停止服务都是通过 Intent
来实现,指定具体的服务类名
1 | // 启动服务 |
启动服务,一般是在服务中新建工作线程做耗时操作,操作完成后结束服务。因为组件间生命周期相互不影响,为了避免浪费系统资源和消耗电池电量,应用必须在耗时工作完成之后停止其服务,所以在服务中直接结束会更合适。源码:
1 | public void stopSelf(); |
示例分析
启动服务后,在服务中新建线程执行耗时操作,耗时执行完后关闭服务
1 | private class MyRunnable implements Runnable { |
对应的 Log
分析:
- 正常流程,服务自我关闭
1 | // 点击启动服务,服务新建线程,执行完耗时操作后主动关闭服务 |
- 主动点击停止服务
1 | // 点击启动服务,服务新建线程,执行完耗时,点击停止服务,并没有影响后台线程继续执行 |
- 连续启动服务
1 | // 连续点击三次启动服务,服务会逐个开启线程并放到后台执行,直到所有线程执行完才会停止服务 |
生命周期
启动服务的生命周期:onCreate() --> onStartCommand() --> onDestroy()
绑定服务 bindService
本地服务,扩展 Binder
类
如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder
类,让客户端通过该类直接访问服务中的公共方法。只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity
绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效
1 | public class LocalBinder extends Binder{ |
重写回调 onBind
当另一个组件想通过调用 bindService()
与服务绑定(例如执行 RPC
)时,系统将调用此方法。在此方法的实现中,必须通过返回 IBinder
提供一个接口,供客户端用来与服务进行通信。如果不允许绑定服务,则应返回 null
。源码:public IBinder onBind(Intent intent) {...}
多个客户端可同时连接到一个服务,或者一个客户端多次连接同一个服务。不过只有在第一次绑定时,系统才会调用服务的 onBind()
方法来检索 IBinder
。系统随后无需再次调用 onBind()
,便可将同一 IBinder
传递至任何其他绑定的客户端,也就是onBind
只会执行一次
如本地服务中:
1 | private final IBinder mBinder = new LocalBinder(); |
建立连接 ServiceConnection
绑定是异步的,bindService()
会立即返回,不会使 IBinder
返回客户端。要接收 IBinder
客户端必须创建一个 ServiceConnection
实例来接收。重写两个回调方法:
onServiceConnected()
系统会调用该方法接收服务的onBind()
方法返回的IBinder
onServiceDisconnected()
系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统不会调用该方法
1 | private boolean mBound = false; |
显示绑定和解绑服务
显示启动和停止服务也是通过 Intent
来实现,指定具体的服务类名
1 | // 显示绑定服务 |
注意:解绑前必须要判断该服务是否已经绑定,如果服务并没有绑定就直接解绑,会抛出异常
1 | 10:30: 9105/com.y E/AndroidRuntime: FATAL EXCEPTION: main |
常见 flags
的含义:
BIND_AUTO_CREATE
创建尚未激活的服务BIND_NOT_FOREGROUND
系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行
生命周期
onCreate() --> onBind() --> onUnBind() --> onDestroy()
联合使用 start and bind service
既可以后台保留服务,又可以通过 IBinder
和服务做交互
客户端
1 | intent.setClass(ShowService.this, StartAndBindService.class); |
服务端
同时实现 onStartCommand
和 onBind
1 | // 扩展本地 Binder |
Log
显示
1 | 19:05: 8807/com.yD/StartAndBindService: onCreate: |
生命周期对比图
onUnbind
返回值对生命周期的影响
如果服务没有停止,onUnbind
返回 true
时,下次再次绑定会执行 onRebind
,源码:public void onRebind(Intent intent) {...}
生命周期流程对比图:
对应 Log
分析:
1 | // 点击按钮启动服务 |
IntentService
Service
因为属于主线程,开启后需要新建线程来执行耗时操作,我们需要去管理 Service
的生命周期以及子线程,所以 Android
提供了 IntentService
来处理这些问题。IntentService
继承了 Service
,并实现了 ServiceHandler
和 HandlerThread
,极大的方便了后台线程操作,并且执行完耗时操作后自动关闭本服务。
特点
- 串行执行
因为使用的是Handler
和Message
的机制,所以所有任务都是串行执行,逐一执行收到的Intent
- 启动方式
startService
默认属于启动服务,无法组件间交互 - 带参数
通过Intent
传递参数到服务中
重写回调
构造函数
必须调用super
并传递类名onHandleIntent
在这里执行所有的耗时工作
示例:
1 | public class MyIntentService extends IntentService { |
其他生命周期函数如果要重写,必须调用 super.***
否则影响 IntentService
的功能
示例及 Log
分析
同时开启两次服务,分别处理 ActionBaz
和 ActionFoo
1 | // 同时发送 ActionBaz 和 ActionFoo 两个 Intent,串行处理 |
前台服务
前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知
开启前台服务
startForeground()
通过startForeground
来标记服务为前台服务,源码:public final void startForeground(int id, Notification notification) {...}
参数id
是唯一标识通知的整型数但不能为0
和状态栏的通知Notification
移除前台服务
stopForeground
移除不是停止服务,所以移除后,服务还是在运行,源码:public final void stopForeground(boolean removeNotification) {...}
示例代码:
1 | private void startForegroundService(){ |
小结
- 启动服务
建议使用IntentService
,不用关心生命周期及线程的管理,它的任务是串行执行的,不支持并发。并发还是需要继承Service
来实现。 - 绑定服务
继承Service
,大部分情况绑定服务一般会和启动服务联合使用。
参考文档
- https://developer.android.com/guide/components/services.html
- https://developer.android.com/guide/components/bound-services.html
- http://blog.csdn.net/u013553529/article/details/54754491
- http://blog.csdn.net/huutu/article/details/40357481
- http://blog.csdn.net/javazejian/article/details/52709857
- http://blog.csdn.net/lmj623565791/article/details/47143563