概述
Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。
分类
按照启动方式服务分为两种:
- startService 其他组件可以通过startService方法启动服务,启动后服务会尽可能的在后台运行。不会随着启动服务的组件销毁而销毁。此时服务不会返回结果给调用方。
- bindService 其他组件通过调用bindService方法绑定到服务,绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
服务的生命周期方法
- onCreate-所有方式启动前都会调用,且只会被调用一次。
- onStartCommand-当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。需要通过调用 stopSelf() 或 stopService() 来停止服务。(如果只想提供绑定,则无需实现此方法。)
- onBind-当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。
- onDestroy-当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。
注
- 服务可以同时使用两种方式启动,但是要实现onStartCommand()(允许组件启动服务)和 onBind()(允许绑定服务)。
- 无论应用是处于启动状态还是绑定状态,抑或处于启动并且绑定状态,任何应用组件均可像使用 Activity 那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问。
- 服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)
- 默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。
- 判断是需要使用线程还是服务的标准是如果仅仅需要在与用户交互时执行任务,就使用线程。
- 仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android 系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(稍后讨论),则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止;如果服务是启动服务,则您必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(不过这还取决于从 onStartCommand() 返回的值,本文稍后会对此加以讨论)。
- 可以通过添加 android:exported 属性并将其设置为 "false",确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。
- 其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。
- 多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 unbindService() 取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。
- 绑定服务也可以绑定到已经startService的服务,此后除非所有绑定都被接解除,否则stopSelf和stopService不能实际停止服务。
IntentService
是Service的一个子类,它会启动新的工作线程逐一处理任务,需要实现onHandleIntent方法,接收并处理Intent。所以,当不需要同时处理多个请求任务时,可以使用IntentService。
IntentService 执行以下操作:
- 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
- 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
- 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
- 提供 onBind() 的默认实现(返回 null)。
- 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
onStartCommand方法
如果是由自己处理对 onStartCommand() 的每个调用,就可以同时执行多个请求。可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。
onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一:
- START_NOT_STICKY-
如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 - START_STICKY-
如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 - START_REDELIVER_INTENT-
如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
前台服务
前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知。
创建一个通知Notification,然后startForeground(ONGOING_NOTIFICATION_ID, notification);启动前台服务。
要从前台移除服务,请调用 stopForeground()。此方法采用一个布尔值,指示是否也移除状态栏通知。 此方法不会停止服务。
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
Activity和Service通信
需要使用绑定服务方式,通过Binder提供服务的方法暴露给其他组件,在其他组件中提供一个ServiceConnection对象实例,在重写的onServiceConnected使用IBinder强转成目标Binder对象,就可以调用暴露出来的方法。
减少Service被kill的几率
- 提升拥有service的进程的优先级,绑定到可见的组件
- 使用startForeground(int, Notification)方法来将service设置为前台状态
- onStartCommand方法,返回START_STICKY,这样内存不足服务可能被杀死,但是当内存再次从充足之后,服务会被重启。
- 在Service的onDestroy方法中发送通知给广播,在广播中重启服务。
- 监听系统状态变化的广播,触发启动服务(但是在Android7.0之后,大部分的系统广播被移除,无法在应用处于后台时正常接收系统广播。)
网友评论