启动方式
独立启动
Context.startService(Intent service)
,和启动它的组件无关,有自己的生命周期,在后台一直运行直到运行完成或者别的组件关闭了它。
使用场景
- 后台音乐播放
- 后台下载
- 常驻型服务
生命周期
- onCreate()
- onStartCommand(Intent, int, int)
- onDestroy()
绑定启动
Context.bindService(Intent service, ServiceConnection conn,int flags)
,和启动它的组件绑定,可以与绑定它的组件之间长时间交互。
使用场景
- 前台音乐播放(UI更新)
- 数据计算,需要随组件启动和销毁,需要交互。
生命周期
- onCreate()
- onBind(intent: Intent): IBinder
- onUnbind(intent: Intent?): Boolean
- onDestroy()
特性
-
不需要直接和用户交互,被设计用于长时间的后台操作。需要在
Manifest
中进行注册。 -
不是一个子线程。和别的组件一样运行在
Main Thread
中,耗时操作需要另起新现成或者使用IntentService
,否则会造成ANR错误。 -
不是一个单独的进程,除非特殊声明。
-
可以通过
startForeground()
方法指定前台服务,高优先级,不会被系统杀掉。 -
启动Service有两个额外的操作模式,设置方法为
onStartCommand():
的返回值:START_STICKY
代表需要明确的启动和停止。START_NOT_STICKY
orSTART_REDELIVER_INTENT
代表没有明确的停止,会在后台一直运行。
-
绑定启动具体实现:
- 需要在service中定义
IBinder
的子类,用于组件与Service之间通信。(其实也就是类似一个回调接口,没有明确规定,可以写任何方法和属性) - 在启动Service的组件中,定义
ServiceConnection
,用于得到IBinder实例,以及连接成功和断开连接的回调方法。
public class MService extends Service { private Notification mNotification; private class MyBinder extends Binder { void start() { Log.i("TAG", "startService"); } } private MyBinder mMyBinder = new MyBinder(); @Override public void onCreate() { super.onCreate(); mNotification = new Notification.Builder(this) .setSmallIcon(R.mipmap.icon) .build(); } @Override public IBinder onBind(Intent intent) { return mMyBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { //前台服务 高优先级 startForeground(1, mNotification); //返回值决定额外的操作模式 return START_STICKY; } }
- 需要在service中定义
-
特殊用法
- 需要和组件之间进行通信,但是又不希望在组件停止之后就停止运行先通过Context.startService()启动service再调用Context.bindService()进行绑定实现绑定独立运行的服务。
多次启动
Service是支持多次调用启动方法的,但是在一次生命周期中,onCreate 、onBind、onUnbind、onDestroy方法只会执行一次,onStartCommand 可执行多次,并且独立启动和绑定启动是可以交叉使用的。
举个栗子:
下面统称Context.startService() 为
start
,Context.bindService() 为bind
,Context.unBindService() 为unbind
,Context.stopService() 为stop
执行顺序: start -> bind -> bind -> start -> unbind -> stop
生命周期:
onCreate() ->
onStartCommand() ->
onBind() ->
onStartCommand() ->
onUnbind() ->
onDestroy()
执行顺序: bind-> bind-> start -> start -> stop
生命周期:
onCreate() ->
onBind() ->
onStartCommand() ->
onStartCommand() ->
onDestroy()
需要注意的地方
- 同一个context在一次生命周期内只能调用一次bind
- 同一个Service可以被多个context bind,但是onbind仅会执行一次
- 同一个context在一次生命周期内也可以调用多次start,且onStartCommand会执行多次
- 通过start启动的也能通过 bind 后期绑定,但是只能通过stop结束
- 通过bind启动的也能通过 start 执行到 onStartCommand,
在所有bind的context全部unbind后结束并且切换为start模式,通过stop结束
版本适配
5.0(21)
为确保安全性,使用推荐使用显式Intent,如果使用隐式Intent调用BindService将会抛出异常
8.0(26)
当应用未在前台,无法通过startService
创建后台服务,可考虑使用startForegroundService
创建前台服务
9.0(28)
如果需要创建前台服务需要在Manifest中声明权限FOREGROUND_SERVICE
,否则将抛出SecurityException
12(31)
后台启动服务思路
从Android 12 之后系统完全禁止了通过后台启动Service的方式,那如果确实需要有这种需求应该怎么办呢,这里基于官方的赦免说明提供以下思路:
广播启动
通过赦免的系统静态广播启动,可参考Android四大组件-BroadcastReceiver
省电白名单
通过引导用户手动将应用添加至省电白名单
/**
* 请求用户添加省电白名单
*/
@SuppressLint("BatteryLife", "QueryPermissionsNeeded")
private fun requestIgnoreBatteryOptimization() {
// 判断当前是否处于白名单
if (!(getSystemService(POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(
packageName
)
) {
// 开启引导弹窗
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:$packageName")
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
}
}
值得注意的是,上述官方调用代码国内很多定(流)制(氓)ROM似乎并不能生效,因此在业务中大概率还是需要针对不同厂商定制不同的用户引导说明。
服务意义
- Android中的后台操作主要分为两种:Thread,Service。而Service又分为前台服务和后台服务。既然线程也能后台执行那还要服务来干嘛,这里就涉及到Android系统的优先级了。
通常优先级排列如下:线程 < 后台服务 < 前台服务
。因此需要后台操作具有一定稳定性时推荐使用服务,需要后台操作完全可靠时使用前台服务,对后台操作要求不高时使用线程。 - 通常一个应用在系统层面就是一个进程,Android系统为每个进程分配的资源都是有限的,但是这不代表一个应用只允许存在单进程,在
AndroidManifest.xml
中,可以为四大组件配置android:process="xxx"
来使该组件在新进程中运行。
而跨进程通信IPC(Inter-Process Communication)是Service的强项,可以说才是Service存在的最重要的意义。