只是写一遍加深印象。
1 概述
一种后台服务。使用时需要在Manifest声明。
两种使用方式:
- 启动模式。适用于Service不需要与启动组件交互的场景。
- 绑定模式。适用于Service需要与启动组件交互的场景,当解除此类Service的所有绑定者后,Service也被销毁。
2 声明
使用Service前需要在Manifest中声明,其属性有:
-
exported[true|false]
。是否支持隐式调用。 -
name
。 -
permission
。 -
process[:xxx|xxx]
。注意这里的冒号,有冒号代表进程名为“包名:xxx
”,无冒号代表进程名为不加包名前缀的“xxx
”。 -
isolatedProcess[true|false]
。进程与其他进程分开且无权限,通信只能通过Service的API(bind
和start
)。 - enable。父标签(若有)和子标签必须同时为
true
,该服务才有效。
3 启动服务
3.1 startService启动
这种方式启动的Service不会随着启动者的退出而停止,可以用stopService(Intent)
或者服务自身调用stopSelf
停止。
每次启动都会调用onStartCommand
。
需要关注的方法
onCreate
首次创建Service时回调此方法,之后不会再回调。-
onStartCommand(Intent intent,int flag,int startId)
startService
时会回调,然后启动服务。- @params
intent
是启动服务时的传参。
flag
有三个值:
1.0
,无参数。
2.START_FLAG_REDELIVERY
。代表本方法的返回值为START_REDELIVER_INTENT
,当service因内存不足被系统关闭后,会 用最后一个传入的intent
作为参数再次调用本方法(onStartCommand)。
3.代表当本方法调用后一直没有返回值时,会重新调用本方法。
startId
当前服务id。 - @return
三个可选值:
1.START_STICKY。Service因内存不足被杀死后,尝试重新创建此服务并回调onStartCommand
,传入intent
为空(除非有挂起的pendingIntent
)。适用于无限期运行等待作业的播放器等。
2.START_NOT_STICKY。不重启。
3.START_REDELIVER_INTENT。重启并传递最后的intent
。适用于恢复下载等。
- @params
onDestroy
在这里清理所有资源。
3.2 bindService启动
Service处于绑定状态时,相当于“client-server”模型中的server,client通过代理与server交互。
绑定的Service的生命周期与宿主绑定,宿主退出即服务销毁。
显然,在绑定服务时必须提供IBinder接口的实现。
定义Service接口的三种方法
1.扩展Binder类。
client通过binder调用service提供的能力。常用于service作为本应用的后台线程的情况,比如下载、播放等。
唯一不适用的场景是service需要提供给其他app调用。
2.使用Messenger
Messenger用于跨进程传递数据(基于Handler、AIDL,本质是封装之后的AIDL),可以使用其为服务定制接口。
Messenger串行处理客户端发来的消息(线程安全)。
3.使用AIDL
允许同时处理多个跨进程的请求。
一般同时采用线程安全式设计。使用时需要自定义.aidl文件。SDK工具利用该文件生成实现接口并处理IPC的抽象类,随后对其进行扩展。
3.2.1 扩展Binder类
1.在Service
的子类(目标业务类)中创建一个实现Binder
接口的实例对象,并实现公共方法用于客户端调用;
2.onBind()
回调返回此Binder
实例;
3.在客户端中,从onServiceConnected()
回调方法接收Binder,并用提供的方法调用服务的能力。
典型场景是后台音乐服务。服务和客户端必须在同一进程内。
// flag 0代表不创建service,BIND_AUTO_CREATE代表绑定时自动创建service
bindService(intent,conn,Service.BIND_AUTO_CREATE);
conn = new ServiceConnection() {
/**
* @param name 封装组件的描述信息,很少使用
* 与服务器端交互的接口方法 绑定服务的时候被回调,在这个方法获取绑定Service传递过来的IBinder对象,
* 通过这个IBinder对象,实现宿主和Service的交互。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "绑定成功调用:onServiceConnected");
// 获取Binder
// 注意这里是将IBinder接口强转为Binder实现类
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
/**
* 当取消绑定的时候被回调。但正常情况下是不被调用的,它的调用时机是当Service服务被意外销毁时,
* 例如内存的资源不足时这个方法才被自动调用。
* 当客户端取消绑定,不会回调此方法
*/
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
多次调用bindService
、unbindService
,对应的onBind
、onUnbind
也只会回调一次。
绑定方式的service生命周期为onCreate
->onBind
->onUnbind
->onDestroy
。
3.2.2 Messenger
用于不同进程间的通信。如果需要互相发送消息,需要在服务端和客户端都布置Messenger和Handler。大致过程如下:
1.服务端实现Handler
,用于接收客户端的回调;
2.使用上述Handler
创建Messenger
;
3.用Messenger
创建IBinder
,在service
的onBind
方法中,将其返回客户端;return mMessenger.getBinder();
4.客户端收到IBinder
后,用其实例化Messenger
(new Message(iBinder)
),用其发送Message
;
5.Messenger
在Handler
中(handleMessage()
)接收Message
并处理。
3.2.3 绑定服务注意
1.多个客户端可以同时绑定一个服务,只有首次绑定回调onBind
,其余情况系统会自动传递IBinder
;除非startService
也启动了该服务,否则最后一个客户端取消绑定时,服务被销毁。
2.应该在客户端的引入和退出时刻绑定、注销服务。只需要在activity可见时存在的服务应绑定在onStart
、onStop
;需要在activity存活期间都运行的服务,应该绑定在onCreate
、onDestroy
,并且应该提高进程的权重。
3.不应该在onPause
和onResume
期间绑定、取消绑定,它们太频繁,并且可能引起服务的销毁和重建。
4.远程方法可能引起DeadObjectException
,由连接中断引发,表示调用的对象已死亡(service对象已销毁),这是远程方法的唯一异常,继承RemoteException
。
5.bindService
方法内部,系统会调用服务的onBind
方法,返回用于与服务交互的IBinder
,该方法是异步的。
3.2.4 启动服务、绑定服务的优先级问题
startService
(启动服务)的优先级大于bindService
(绑定服务)。若同时启动、绑定了服务,在调用stopService
或者stopSelf
之前,即使所有客户端都取消绑定了,服务在完成前也不会停止。
同样,在仍有客户端绑定在服务上时,stopService
或者stopSelf
也不会停止服务。
3.3 服务在其托管进程的主线程运行
如果需要进行耗时操作,需要另起新的线程。
3.4 前台服务以及通知
前台服务在状态栏提供无法清除通知(除非服务停止或者从前台删除),例如音乐播放器。
两个相关的方法:
1.startForeground(int id,Notification)。参数显然为标识id(不得为0)和通知。
2.stopForeground(boolean removeNotification)
从前台删除服务并删除通知。
3.5 常见使用场景
下载文件,service后台执行,notification前台显示,thread异步下载。
app维持service从网络获取推送服务。
这里引入讨论IntentService
。
3.6 生命周期
3.7 显示、隐式启动
- 显示启动
Intent intent=new Intent(this,xxxService.class);
startService(intent);
- 隐式启动
主要用于启动其他应用的Service。
为Intent
设置action
,可以是服务的全路径名,此时android:exported
默认为true
。
Intent intent=new Intent();
intent.setAction("com.android.xxxService");
startService(intent);
隐式启动在4.4版本会警告,5.0以上会抛异常,因为隐式声明intent去启动Service是不安全的。
-
需要同时设置action和packageName。
就是加上
intent.setPackage(getPackageName());//设置应用包名
- 转换为显式启动
需要为intent
设置component
再启动服务。
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}