二营长的意大利炮手

二营长的意大利炮手

关于服务

定义

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。

类型

  • 前台服务
  • 后台服务
  • 绑定服务

虽然分开概括讨论启动服务和绑定服务,但服务可同时以这两种方式运行,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)。

如何创建服务

如要创建服务,必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务。

主要的回调方法:

  • onStartCommand()
  • onBind
  • onCreate()
  • onDestroy()

除了继承service实现上诉的几个重要回调之外,还需要在

清单文件中注册service:

<manifest ... >
  ...
  <application ... >
  <service android:name=".MyService" />
  ...
  </application>
</manifest>

关于service标签,可以操作的属性有:

<service
 //向用户描述服务的字符串
 android:description="string resource"
 //服务是否支持直接启动,即其是否可以在用户解锁设备之前运行。
 //注:在直接启动期间,应用中的服务仅可访问存储在设备保护存储区的数据
 android:directBootAware=["true" | "false"]
 //系统是否可实例化服务 默认为“true”
 //<application> 元素拥有自己的 enabled 属性,该属性适用于所有应用组件,包括服务
 //<application> 和 <service> 属性都为“true”(因为它们都默认使用该值)时,系统才能启用服务
 android:enabled=["true" | "false"]
 //其他应用的组件是否能调用服务或与之交互,默认值取决于服务是否包含 Intent 过滤器,没有任何过滤器则为false
 //当该值为“false”时,只有同一个应用或具有相同用户 ID 的应用的组件可以启动服务或绑定到服务
 android:exported=["true" | "false"]
 //阐明服务是满足特定用例要求的前台服务
 android:foregroundServiceType=["connectedDevice" | "dataSync" |
"location" | "mediaPlayback" | "mediaProjection" |
"phoneCall"]
 //表示服务的图标,如果未设置该属性,则转而使用为应用整体指定的图标
 android:icon="drawable resource"
 //如果设置为 true,则此服务将在与系统其余部分隔离的特殊进程下运行。此服务自身没有权限,只能通过 Service API 与其进行通信(绑定和启动)
 android:isolatedProcess=["true" | "false"]
 //可向用户显示的服务名称,如果未设置该属性,则转而使用为应用整体设置的标签
 android:label="string resource"
 //实现服务的 Service 子类的名称
 android:name="string"
 //实体启动服务或绑定到服务所必需的权限的名称如果 startService()、bindService() 或 stopService() 的调用者尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务
 //如果未设置该属性,则对服务应用由 <application> 元素的 permission 属性所设置的权限。如果二者均未设置,则服务不受权限保护
 android:permission="string"
 //将运行服务的进程的名称
 android:process="string"
 >
. . .
</service>

service的活动周期:

  • 通过调用 startService() 启动服务
  • 通过调用 bindService() 来创建服务,且未调用 onStartCommand()

关于系统可能停止service的情况:

  • 只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务
  • 如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止
  • 如果将服务声明为在前台运行,则其几乎永远不会终止
  • 如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升,如果服务是启动服务,则必须将其设计为能够妥善处理系统执行的重启
  • 如果系统终止服务,则其会在资源可用时立即重启服务,但这取决于从 onStartCommand() 返回的值

onStartCommand()方法的可返回值:

  • START_ NOT_STICKY
  • START_STICKY
  • START_ REDELIVER_INTENT

上面已经提到,创建服务除了继承Service之外,还可以直接使用它的一个现有子类,那么这个之类是谁呢?就是下面这位了...

IntentService

关于两者,他们适应不同的应用场景来实现服务:

  • Service
  • IntentService

可提供的特性:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现
  • 在处理完所有启动请求后停止服务,因此永远不必调用 stopSelf()
  • 提供 onBind() 的默认实现(返回 null)
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现

扩展实现IntentService需要注意的点:

  • onStartCommand() 必须返回默认实现,从源码可见缘由:
  • 实现onHandleIntent()

至此,我们已经知道如何创建服务,那么创建好的服务又应如何进行开启、停止和绑定呢?这就是接下来我们要解决的问题...

服务的开启、停止、绑定

服务的开启

可以通过将 Intent 传递给 startService()startForegroundService(),从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,从而指定要启动的服务。

服务的停止

除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在 onStartCommand() 返回后仍会继续运行。服务必须通过调用 stopSelf() 自行停止运行,或由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf() 或 stopService() 来停止服务,系统便会尽快销毁服务。

如果服务同时处理多个对 onStartCommand() 的请求,则不应在处理完一个启动请求之后停止服务,因为可能已收到新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。换言之,在调用 stopSelf(int) 时,您需传递与停止请求 ID 相对应的启动请求 ID(传递给 onStartCommand() 的 startId)。此外,如果服务在能够调用 stopSelf(int) 之前收到新启动请求,则 ID 不匹配,服务也不会停止。

服务的绑定

绑定服务允许应用组件通过调用 bindService() 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService() 来启动它。

如要创建绑定服务,您需通过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通信的接口。

服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。不必像通过 onStartCommand() 启动的服务那样,以相同方式停止绑定服务。

在前台运行的服务

前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。

在使用前台服务时,应注意以下事项:

创建前台任务,需要请求必要的权限:

创建前台服务的示例:

val pendingIntent: PendingIntent =
Intent(this, ExampleActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}

val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build()
//提供给 startForeground() 的整型 ID 不得为 0
startForeground(ONGOING_NOTIFICATION_ID, notification)

移除前台服务:

服务的生命周期

与 Activity 类似,服务也拥有生命周期回调方法,可通过实现这些方法来监控服务状态的变化并适时执行工作。以下展示了每种生命周期方法:

class MyService : Service() {
// 指示服务被终止时的行为
private var startMode: Int = 0
// 绑定客户端的接口
private var binder: IBinder? = null
// 指示是否应使用onRebind
private var allowRebind: Boolean = false

override fun onCreate() {
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // 由于调用startService(),服务正在启动
    return mStartMode
}

override fun onBind(intent: Intent): IBinder? {
    // 客户端使用bindService()绑定到服务
    return mBinder
}

override fun onUnbind(intent: Intent): Boolean {
    // 所有客户端都与unbindService()解除绑定
    return mAllowRebind
}

override fun onRebind(intent: Intent) {
    // 在已经调用onUnbind()之后,客户端使用bindService()绑定到服务
}

override fun onDestroy() {
}

附上一张经典的service生命周期图示:

详解Android四大组件之——服务-LMLPHP

关于service的生命周期:

  • 服务的整个生命周期贯穿调用 onCreate() 和返回 onDestroy() 之间的这段时间。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。
  • 服务的活动生命周期从调用 onStartCommand() 或 onBind() 开始。每种方法均会获得 Intent 对象,该对象会传递至 startService() 或 bindService()。对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,活动生命周期会在 onUnbind() 返回时结束。

Andoird8.0开始对后台Service限制

我们知道,每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 所以,为了提升用户体验,从Android 8.0(API 级别 26)开始,对应用在后台运行时可以执行的操作施加了限制。

对于我们开发者而言,最直接的影响就是,应用处于空闲状态时,可以使用的后台 Service 被限制。 这些限制不适用于前台 Service,因为前台 Service 更容易引起用户注意。

那么什么是空闲状态呢?

空闲状态

什么时候应用被视为处于前台

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。

创建前台Service方式变更

  • Android 8.0 之前
  • Android 8.0 开始

在系统增加限制的同时,当然也提供了其他的途径来帮助开发者解决这些限制带来的问题。在大多数情况下,应用都可以使用 JobScheduler 克服这些限制。 这种方法允许应用安排其在未活跃运行时执行工作,不过仍能够使系统可以在不影响用户体验的情况下安排这些作业。

可行的迁移方案

  • 如果处于后台时,应用需要创建一个前台 Service,使用 startForegroundService() 方法,而非 startService()。
  • 如果 Service 容易引起用户注意,将其设置为前台 Service。 例如,播放音频的 Service 始终应为前台 Service。 使用 startForegroundService() 方法创建 Service, 而非 startService()。
  • 寻找一种使用计划作业实现 Service 功能的方式。 如果 Service 未在执行容易立即引起用户注意的操作,一般情况下,都能够使用计划作业。
  • 在应用正常处于前台之前,请推迟后台工作。今年金九银十我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以点击传送门,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。

详解Android四大组件之——服务-LMLPHP

11-30 14:01