基础 概念 Service
是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC
)。例如,服务可以处理网络事务、播放音乐,执行文件 I/O
或与内容提供程序交互,而所有这一切均可在后台进行。
选择使用服务还是线程 简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,但是它仍会在应用的主线程中运行。如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。
注意:Service
和 Activity
都属于 UI
主线程,所以在 Service
中同样需要开新线程来执行耗时操作
两种启动方式
组件间无交互 :启动服务时,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如它可能通过网络下载或上传文件,操作完成后服务会自行停止运行。
组件间有交互 :绑定到服务时,服务提供了一个客户端-服务器接口 ,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 ( IPC
)跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
实际使用中,大量会出现两种启动方式同时使用,既希望服务不受影响的在后台运行,又需要达到组件交互的目的。
清单属性
android:exported
这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true
,则能够被调用或交互。设置为 false
,只有同一个应用程序的组件或带有相同用户 ID
的应用程序才能启动或绑定该服务
android:enabled
是否可以被系统实例化,默认为 true
,表示服务能被激活,否则不会激活
android:process
是否需要在单独的进程中运行,格式: android:process=”:remote”
:
后面表示新的进程名,为包名加上冒号后值,如:AppPackageName:remote
android:name
服务的名称
示例:
1 2 3 <service android:name =".MyIntentService" android:exported ="false" > </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 2 3 4 5 6 7 Intent intent = new Intent(ShowServiceActivity.this , StartService.class); startService(intent); Intent intent = new Intent(ShowServiceActivity.this , StartService.class); stopService(intent);
启动服务,一般是在服务中新建工作线程做耗时操作,操作完成后结束服务。因为组件间生命周期相互不影响,为了避免浪费系统资源和消耗电池电量,应用必须在耗时工作完成之后停止其服务,所以在服务中直接结束会更合适。源码:
1 2 3 public void stopSelf () ;public final boolean stopSelfResult (int startId) {...}
示例分析 启动服务后,在服务中新建线程执行耗时操作,耗时执行完后关闭服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private class MyRunnable implements Runnable { private int mStartId; public MyRunnable (int startId) { mStartId = startId; } @Override public void run () { try { Log.d(TAG, "run: " ); Thread.sleep(3000 ); Log.d(TAG, "run: stopSelfResult.mStartId = " + mStartId); stopSelfResult(mStartId); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public int onStartCommand (Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand: flags = " + flags + ", startId = " + startId); Thread thread = new Thread(new MyRunnable(startId)); thread.start(); return super .onStartCommand(intent, flags, startId); }
对应的 Log
分析:
1 2 3 4 5 6 7 8 9 10 // 点击启动服务,服务新建线程,执行完耗时操作后主动关闭服务 11:15:14.046 17723-17723/ ShowService:: onClick: button.onClick = Start Service // Activity 和 Service 都属于主线程,线程 id 相同 11:15:14.063 17723-17723/ StartService:: onCreate: 11:15:14.064 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1 // 线程 id 可以看出,进入后台线程运行 11:15:14.066 17723-17917/ StartService:: run: 11:15:17.066 17723-17917/ StartService:: run: stopSelfResult.mStartId = 1 11:15:17.068 17723-17723/ StartService:: onDestroy: 11:15:20.358 17723-17723/ ShowService:: onClick: button.onClick = Stop Service
1 2 3 4 5 6 7 8 9 // 点击启动服务,服务新建线程,执行完耗时,点击停止服务,并没有影响后台线程继续执行 11:15:34.975 17723-17723/ ShowService:: onClick: button.onClick = Start Service 11:15:34.998 17723-17723/ StartService:: onCreate: 11:15:34.999 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1 11:15:35.003 17723-18238/ StartService:: run: 11:15:36.455 17723-17723/ ShowService:: onClick: button.onClick = Stop Service // 点击停止服务,服务销毁后 ,后台线程继续执行 11:15:36.457 17723-17723/ StartService:: onDestroy: 11:15:38.004 17723-18238/ StartService:: run: stopSelfResult.mStartId = 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 连续点击三次启动服务,服务会逐个开启线程并放到后台执行,直到所有线程执行完才会停止服务 // 只要服务没有停止,Id 会逐渐增加,一旦停止服务,Id 重新由 1 开始计数 11:18:09.999 17723-17723/ ShowService:: onClick: button.onClick = Start Service 11:18:10.011 17723-17723/ StartService:: onCreate: 11:18:10.013 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1 // 后台线程 1 11:18:10.025 17723-20539/ StartService:: run: 11:18:10.702 17723-17723/ ShowService:: onClick: button.onClick = Start Service 11:18:10.711 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 2 // 后台线程 2 11:18:10.713 17723-20555/ StartService:: run: 11:18:11.404 17723-17723/ ShowService:: onClick: button.onClick = Start Service 11:18:11.409 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 3 // 后台线程 3 11:18:11.413 17723-20567/ StartService:: run: // 后台耗时逐个执行完毕 11:18:13.026 17723-20539/ StartService:: run: stopSelfResult.mStartId = 1 11:18:13.713 17723-20555/ StartService:: run: stopSelfResult.mStartId = 2 11:18:14.413 17723-20567/ StartService:: run: stopSelfResult.mStartId = 3 // 所有后台线程执行完后,服务才销毁 11:18:14.415 17723-17723/ StartService:: onDestroy:
生命周期 启动服务的生命周期:onCreate() --> onStartCommand() --> onDestroy()
绑定服务 bindService
本地服务,扩展 Binder
类 如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder
类,让客户端通过该类直接访问服务中的公共方法。只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity
绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效
1 2 3 4 5 public class LocalBinder extends Binder { public BindService getService () { return BindService.this ; } }
重写回调 onBind
当另一个组件想通过调用 bindService()
与服务绑定(例如执行 RPC
)时,系统将调用此方法。在此方法的实现中,必须通过返回 IBinder
提供一个接口,供客户端用来与服务进行通信。如果不允许绑定服务,则应返回 null
。源码:public IBinder onBind(Intent intent) {...}
多个客户端可同时连接到一个服务,或者一个客户端多次连接同一个服务。不过只有在第一次绑定时,系统才会调用服务的 onBind()
方法来检索 IBinder
。系统随后无需再次调用 onBind()
,便可将同一 IBinder
传递至任何其他绑定的客户端,也就是onBind
只会执行一次
如本地服务中:
1 2 3 4 private final IBinder mBinder = new LocalBinder();public IBinder onBind (Intent intent) { return mBinder; }
建立连接 ServiceConnection
绑定是异步 的,bindService()
会立即返回,不会 使 IBinder
返回客户端。要接收 IBinder
客户端必须创建一个 ServiceConnection
实例来接收。重写两个回调方法:
onServiceConnected()
系统会调用该方法接收服务的 onBind()
方法返回的 IBinder
onServiceDisconnected()
系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统不会 调用该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private boolean mBound = false ;private ServiceConnection mBindServiceConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { mBindService = ((BindService.LocalBinder)service).getService(); int randomNumber = mBindService.genRandomNumber()); mBound = true ; } @Override public void onServiceDisconnected (ComponentName name) { mBindService = null ; mBound = false ; } };
显示绑定和解绑服务 显示启动和停止服务也是通过 Intent
来实现,指定具体的服务类名
1 2 3 4 5 6 7 8 9 Intent intent = new Intent(ShowServiceActivity.this , StartService.class); bindService(intent, mBindServiceConnection, Context.BIND_AUTO_CREATE); if (mBound) { unbindService(mBindServiceConnection); mBound = false ; }
注意:解绑前必须要判断该服务是否已经绑定,如果服务并没有绑定就直接解绑,会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 10:30: 9105/com.y E/AndroidRuntime: FATAL EXCEPTION: main Process: com.y, PID: 9105 Caused by: java.lang.IllegalArgumentException: Service not registered: com.y.ShowService$1@11bddcab ```` 常见 `flags` 的含义: - `BIND_AUTO_CREATE` 创建尚未激活的服务 - `BIND_NOT_FOREGROUND` 系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行 ### 生命周期 `onCreate() --> onBind() --> onUnBind() --> onDestroy()` ## 联合使用 `start and bind service` 既可以后台保留服务,又可以通过 `IBinder` 和服务做交互 ### 客户端 ```java intent.setClass(ShowService.this, StartAndBindService.class); // 显示启动服务 startService(intent); // 显示绑定服务 bindService(intent, mStartAndBindServiceConnection, Context.BIND_AUTO_CREATE);
服务端 同时实现 onStartCommand
和 onBind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LocalStartAndBinder extends Binder { public StartAndBindService getService () { return StartAndBindService.this ; } } private final IBinder mBinder = new LocalStartAndBinder();@Override public int onStartCommand (Intent intent, int flags, int startId) { return super .onStartCommand(intent, flags, startId); } @Override public IBinder onBind (Intent intent) { return mBinder; }
Log
显示1 2 3 4 5 6 7 19:05: 8807/com.yD/StartAndBindService: onCreate: 19:05: 8807/com.yD/StartAndBindService: onStartCommand: flags = 0, startId = 1 19:05: 8807/com.yD/StartAndBindService: onBind: 19:05: 8807/com.yD/:ShowService:: onServiceConnected: mStartAndBindService.genRandomString = hechiffecd 19:05: 8807/com.yD/StartAndBindService: onUnbind: 19:05: 8807/com.yD/StartAndBindService: onDestroy:
生命周期对比图
onUnbind
返回值对生命周期的影响如果服务没有停止,onUnbind
返回 true
时,下次再次绑定会执行 onRebind
,源码:public void onRebind(Intent intent) {...}
生命周期流程对比图:
对应 Log
分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 点击按钮启动服务 16:39:14.923 20504-20504/ D/:ShowService:: onClick: button.onClick = Start Service // 创建服务 16:39:14.932 20504-20504/ D/:StartAndBindService: onCreate: 16:39:14.935 20504-20504/ D/:StartAndBindService: onStartCommand: flags = 0, startId = 1 // 点击按钮绑定服务 16:39:15.730 20504-20504/ D/:ShowService:: onClick: button.onClick = Bind Service // 第一次绑定 onBind 16:39:15.732 20504-20504/ D/:StartAndBindService: onBind: // 服务连接 16:39:15.734 20504-20504/ D/:ShowService:: onServiceConnected: mStartAndBindService.genRandomString = bhahahafeh // 点击按钮解绑服务 16:39:16.715 20504-20504/ D/:ShowService:: onClick: button.onClick = Unbind Service // onUnbind 返回 true 16:39:16.721 20504-20504/ D/:StartAndBindService: onUnbind: // 再次点击按钮绑定服务 16:39:17.846 20504-20504/ D/:ShowService:: onClick: button.onClick = Bind Service // 服务连接 16:39:17.848 20504-20504/ D/:ShowService:: onServiceConnected: mStartAndBindService.genRandomString = bdghebhdde // 重新绑定 onRebind 16:39:17.848 20504-20504/ D/:StartAndBindService: onRebind: // 解绑服务 16:39:19.288 20504-20504/ D/:ShowService:: onClick: button.onClick = Unbind Service
IntentService Service
因为属于主线程,开启后需要新建线程来执行耗时操作,我们需要去管理 Service
的生命周期以及子线程,所以 Android
提供了 IntentService
来处理这些问题。IntentService
继承了 Service
,并实现了 ServiceHandler
和 HandlerThread
,极大的方便了后台线程操作,并且执行完耗时操作后自动关闭本服务。
特点
串行执行 因为使用的是Handler
和 Message
的机制,所以所有任务都是串行执行,逐一执行收到的 Intent
启动方式 startService
默认属于启动服务,无法组件间交互
带参数 通过 Intent
传递参数到服务中
重写回调
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MyIntentService extends IntentService { public MyIntentService () { super ("MyIntentService" ); } @Override protected void onHandleIntent (Intent intent) { try { Thread.sleep(5000 ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
其他生命周期函数如果要重写,必须调用 super.***
否则影响 IntentService
的功能
示例及 Log
分析 同时开启两次服务,分别处理 ActionBaz
和 ActionFoo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 同时发送 ActionBaz 和 ActionFoo 两个 Intent,串行处理 14:00:49.965 7776-7776/:ShowService:: onCreate: // 点击 Button ,启动两次服务 14:00:51.332 7776-7776/:ShowService:: onClick: button.onClick = Start Intent Service 14:00:51.333 7776-7776/:MyIntentService:: startActionBaz: 14:00:51.337 7776-7776/:MyIntentService:: startActionFoo: // 启动服务 14:00:51.343 7776-7776/:MyIntentService:: onCreate: // 开启服务 1 14:00:51.344 7776-7776/:MyIntentService:: onStartCommand: startId = 1 14:00:51.347 7776-7866/:MyIntentService:: onHandleIntent: action = com...action.BAZ // 开启服务 2 14:00:51.348 7776-7776/:MyIntentService:: onStartCommand: startId = 2 // 处理 ActionBaz,工作线程处理耗时工作 14:00:51.348 7776-7866/:MyIntentService:: handleActionBaz: param1 = Action, param2 = Baz 14:00:51.348 7776-7866/:MyIntentService:: doLongRunningWork Start... 14:00:54.349 7776-7866/:MyIntentService:: doLongRunningWork Done! // 处理 ActionFoo,工作线程处理耗时工作 14:00:54.349 7776-7866/:MyIntentService:: onHandleIntent: action = com...action.FOO 14:00:54.350 7776-7866/:MyIntentService:: handleActionFoo: param1 = Action, param2 = Foo 14:00:54.350 7776-7866/:MyIntentService:: doLongRunningWork Start... 14:00:57.350 7776-7866/:MyIntentService:: doLongRunningWork Done! // 关闭服务 14:00:57.351 7776-7776/:MyIntentService:: onDestroy:
前台服务 前台服务 被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知 ,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知
开启前台服务 startForeground()
通过 startForeground
来标记服务为前台服务,源码:public final void startForeground(int id, Notification notification) {...}
参数 id
是唯一标识通知 的整型数但不能为 0
和状态栏的通知 Notification
移除前台服务 stopForeground
移除不是停止 服务,所以移除后,服务还是在运行,源码:public final void stopForeground(boolean removeNotification) {...}
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void startForegroundService () { Log.d(TAG, "startForegroundService: " ); CharSequence text = getText(R.string.foreground_service_started); PendingIntent contentIntent = PendingIntent.getActivity(this , 0 , new Intent(this , ShowService.class), 0 ); Notification notification = new Notification.Builder(this ) .setSmallIcon(R.drawable.stat_sample) .setTicker(text) .setWhen(System.currentTimeMillis()) .setContentTitle(getText(R.string.alarm_service_label)) .setContentText(text) .setContentIntent(contentIntent) .build(); startForeground(R.string.foreground_service_started, notification); } private void stopForegroundService () { stopForeground(true ); }
小结
启动服务 建议使用 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