Android四大组件之 Service
概览
定义
Service
是一种可在后台执行长时间运行操作而不提供界面的应用组件。
特性
- 生命周期独立,可在后台单独运行。
- 通过绑定(bindService)操作,可与组件之间进行交互。甚至可执行进程间通信(IPC)。
- service在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。
类型
前台服务:前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
后台服务:后台服务执行用户不会直接注意到的操作。
官方建议:API级别26以上设备中,当应用本身未在前台运行时,系统会对运行后台服务施加限制。在诸如此类的大多数情况下,您的应用应改为使用WorkManager。
绑定服务:
绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
在服务和线程之间进行选择
简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。
如果您必须在主线程之外执行操作,但只在用户与您的应用交互时执行此操作,则应创建新线程。例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 onCreate()
中创建线程,在 onStart()
中启动线程运行,然后在 onStop()
中停止线程。您还可考虑使用 AsyncTask
或 HandlerThread
,而非传统的 Thread
类。如需了解有关线程的详细信息,请参阅进程和线程文档。
请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。
官网还提到了一个叫
IntentService
的类,继承自service,默认开辟的是一个工作线程,将服务的代码放到工作线程上去运行。 由于目前我们已经有了很多异步的处理方式,所以很多情况下已经不会去用IntentService
了。
生命周期
Service的操作与生命周期的关系如下图,可以清晰的看到什么操作对应什么Service生命周期:
官网提供的生命周期图在下面。
如下图所示,左边为前台/后台服务的生命周期,右边为绑定服务的生命周期。
在绑定服务的操作中,服务(Service)将会与组件(Activity)组合形成 服务器/客户端(C/S) 模式,Service作为服务器端可以与多个客户端(activity)进行绑定,若仅在绑定模式下,当最后一个客户端(activity)解绑后,service自动销毁。
为什么叫“仅在绑定模式”下。因为这绑定模式还可以和其他两种模式组合,先start,然后bind,如下图。这样就算最后一个客户端(activity)解绑后,Service也不会销毁,而是长期存在了。直到你调用stopService()
或stopSelf()
为止。
代码实现
xml配置
android:exported=" "
:其他应用的组件是否能调用服务或与之交互 ,“true”表示可以,“false”表示不可以。当该值为“false”时,只有同一个应用或具有相同用户 ID 的应用的组件可以启动服务或绑定到服务。;
默认值取决于服务是否包含 Intent 过滤器。没有任何过滤器意味着服务只能通过指定其确切的类名称进行调用。这意味着服务专供应用内部使用(因为其他应用不知晓其类名称)。因此,在这种情况下,默认值为“false”。另一方面,至少存在一个过滤器意味着服务专供外部使用,因此默认值为“true”。
android:enabled=" "
:系统是否可实例化服务,“true”表示可以,“false”表示不可以。默认值为“true”。
和
属性都为“
true
”(因为它们都默认使用该值)时,系统才能启用服务。任何一项为“false
”都会造成服务停用,从而使系统无法将其实例化。
后台服务
最基础的Service,直接新建文件继承Service类,后在AndroidManiFest.xml中注册即可。
用 Intent(this,MyService::java.class).also{ startService(it) }
即可调用。
绑定服务
- 在基础的后台Service中,重写
onBInd()
方法。返回一个继承Ibinder的对象。 - 我们自定义一个类继承BInder即可。里面可以存放一些数据,甚至是把当前service存放进去。
inner class MyBinder :Binder(){
val service = this@MyService
}
- onBind中返回这个自定义的Binder类。里面也可以进行一些线程相关操作,因为通过生命周期可知,绑定服务必会运行这个方法。
我这里定义了个变量循环+1.
var numLiveData = MutableLiveData(0)
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
lifecycleScope.launch {
while (true){
delay(1000)
numLiveData.value = numLiveData.value?.plus(1)
}
}
return MyBinder()
}
- Activity内绑定,需要定义一个
ServiceConnection
对象。
val connection = object :ServiceConnection{
override fun onServiceConnected(name: ComponentName, service: IBinder) {
//这里拿到的service就是上一步传来的MyBinder
(service as MyService.MyBinder)
// .service.numLiveData就是上一步里面循环+1的那个livedata了
}
override fun onServiceDisconnected(name: ComponentName) {
}
}
//BIND_AUTO_CREATE 创建service的几种模式
bindService(intent,connection, BIND_AUTO_CREATE)
前台服务
特征
①.用户可以感知到它的存在,状态栏中会有通知;
②.不太容易会系统销毁。(如果是后台服务,从“最近使用的应用”中划出去,则服务亦将被销毁;若是前台服务则不会)
代码
- 在service中调用startForeground()即变成了前台service。
startForeground(id,notification)
传入两个参数,id为正整数(不能为0),notification为通知。
val pendingIntent: PendingIntent =
Intent(this, ExampleActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
//CHANNEL_ID为自定义的String值
//如果在Android8.0及以上,这个值还需要拿去做一些处理 applyNotification()
//applyNotification()代码在最后
val notification: Notification = Notification.Builder(this, CHANNEL_ID)
.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()
// Notification ID cannot be 0.
startForeground(ONGOING_NOTIFICATION_ID, notification)
- 既然是前台进程,得要权限
- 最后如果Android版本大于8.0,申请通知的时候还有些额外设置。
//Android8.0以上 Google把通知的权限管理还给了用户,这里设置的值会显示在权限管理的界面
fun applyNotification(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel
val name = "行行好,申请下权限呗"
val descriptionText = "申请权限的相关描述"
val importance = NotificationManager.IMPORTANCE_DEFAULT
//CHANNEL_ID在这里
val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
mChannel.description = descriptionText
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
}
}
参考$致谢
b站大佬longway777的教学视频
Google官方文档