1. 为什么调用 stopService/unbindService 之后 Service 没有被销毁?
通过之前对 Service 销毁流程的分析, stopService 和 unbindService 最终都会进入到 ActiveServices.bringDownServiceIfNeededLocked 方法中, 该方法会判断当前的 Service 是否满足销毁条件, 其中的核心方法便是 isServiceNeeded.
- private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
- // Are we still explicitly being asked to run?
- if (r.startRequested) {
- return true;
- }
- // Is someone still bound to us keepign us running?
- if (!knowConn) {
- hasConn = r.hasAutoCreateConnections();
- }
- if (hasConn) {
- return true;
- }
- return false;
- }
有两个非常关键的变量: ServiceRecord.startRequested 和 hasConn, 前者与 start 有关, 后者与 bind 有关, 只有两者都为 false 才能销毁一个 Service. 我们先来看看 startRequested
ServiceRecord.startRequested
通过全局搜索发现, 该字段只有在 ActiveServices.startServiceLocked 方法中, 也即是 start 流程中会被置为 true. 在 ActiveServices.stopServiceLocked,ActiveServices.stopServiceTokenLocked,ActiveServices.killServicesLocked 这三个方法中会被置为 false,ActiveServices.stopServiceTokenLocked 是在 Service 调用 stopSelf 时会触发的, 而 ActiveServices.killServicesLocked 则是在清理应用 (内存不足等场景) 的时候触发.
简单来说 ServiceRecord.startRequested 会在 start 流程中被置为 true, 在 stop 流程中置为 false. 因此, 无论你之前调用过多少次 startService, 只要你调了一次 stopService(之后没有再调用 startService), 那么 startRequested 就被置为了 false.**startRequested 的值取决于最后一次调用的是 startService 还是 stopService.
hasConn
该字段的值跟 ServiceRecord.hasAutoCreateConnection 方法的返回值有关
- public boolean hasAutoCreateConnections() {
- // XXX should probably keep a count of the number of auto-create
- // connections directly in the service.
- for (int conni=connections.size()-1; conni>=0; conni--) {
- ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
- for (int i=0; i<cr.size(); i++) {
- // 这个 flags 就是调用 bindService 时使用的 flags
- if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) {
- return true;
- }
- }
- }
- return false;
- }
该方法内部会遍历所有 bind 至当前服务的连接, 如果还存在任一连接, 其调用 bindService 时使用的 flags 包含 BIND_AUTO_CREATE 标志, 则返回 true, 否则返回 false.
总结
我们以具体场景来分析怎样才能销毁一个服务:
只是用了 startService 来启动服务. 这种场景下, 只需要调用 stopService 就可以正常销毁服务
只是用了 bindService 启动服务 这种场景下, 只需要调用对应的 unbindService 即可,
同时使用了 startService 和 bindService 这种场景想要关闭服务的话, 首先要调用 stopService, 其次还需要确保之前使用 BIND_AUTO_CREATE 进行绑定的客户端解绑 (unbindService) 即可.
2. 为啥多次调用 bindServcie, 而 onBind 只触发了一次
在 Service 启动流程中有一个 realStartServiceLocked 方法, 在服务进程启动完毕之后, 会调用该方法继续服务启动的流程. realStartServiceLocked 内部调用了一个名为 requestServiceBindingsLocked 的方法处理 bind 请求. 重新贴一下该方法代码:
- private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
- throws TransactionTooLargeException {
- for (int i=r.bindings.size()-1; i>=0; i--) {
- IntentBindRecord ibr = r.bindings.valueAt(i);
- // 该方法内部会通过跨进程调用 ApplicationThread.scheduleBindService
- // 来回调 Service.onBind 方法
- if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
- break;
- }
- }
- }
可以看到这里有一个 for 循环, 这说明了 Service.onBind 被多次回调是可能的. 那么问题就变成了 ServiceRecord.bindings 什么时候会保存多个值呢? 对 bindings 字段的 put 操作只发生在 retrieveAppBindingLocked 方法中, 该方法是在 bind 流程中的 ActiveServices.bindServiceLocked 方法中被调用的. 贴下代码
- public AppBindRecord retrieveAppBindingLocked(Intent intent,// 客户端发起 bind 请求所使用的 Intent
- ProcessRecord App) {// 客户端进程记录
- Intent.FilterComparison filter = new Intent.FilterComparison(intent);
- IntentBindRecord i = bindings.get(filter);
- if (i == null) {
- i = new IntentBindRecord(this, filter);
- bindings.put(filter, i);
- }
- AppBindRecord a = i.apps.get(App);
- if (a != null) {
- return a;
- }
- a = new AppBindRecord(this, i, App);
- i.apps.put(App, a);
- return a;
- }
可以看到该方法首先将 intent 封装成了一个 FilterComparison 对象作为 key, 然后去 bindings 中检索, 如果没有对应的值就会创建一个值. 再来看看 FilterComparison.equals 方法, 因为只有创建出不同的 FilterComparison 实例, bindings 中才会保存多个值.
- //Intent$FilterComparison.java
- public boolean equals(Object obj) {
- if (obj instanceof FilterComparison) {
- Intent other = ((FilterComparison) obj).mIntent;
- return mIntent.filterEquals(other);
- }
- return false;
- }
- //Intent.java
- public boolean filterEquals(Intent other) {
- if (other == null) {
- return false;
- }
- if (!Objects.equals(this.mAction, other.mAction)) return false;
- if (!Objects.equals(this.mData, other.mData)) return false;
- if (!Objects.equals(this.mType, other.mType)) return false;
- if (!Objects.equals(this.mPackage, other.mPackage)) return false;
- if (!Objects.equals(this.mComponent, other.mComponent)) return false;
- if (!Objects.equals(this.mCategories, other.mCategories)) return false;
- return true;
- }
可以看到, FilterComparison 的比较其实是跟 Intent 密切相关的. Intent 内部 mAction,mData,mType,mPackage,mComponent,mCategories 中的任意字段发生变化, 就会产生两个不同的 FilterComparison 实例.
结论
在调用 bindService 时, 改变一下 Intent 内部的一些值, 就可以触发多次 Service.onBind.
复盘
知道了结论, 我们来复盘一下, 多次使用同一个 Intent 来 bindService 的问题 通常我们是以下面这种方式来构造 Intent 的
- Intent intent = new Intent(activity, DemoService.class);
- //Intent.java
- public Intent(Context packageContext, Class<?> cls) {
- mComponent = new ComponentName(packageContext, cls);
- }
这种方式初始化 Intent, 最终会将构造函数的入参保存成 mComponent.
第一次进入 bind 流程之后, 调用 retrieveAppBindingLocked 肯定会为 bindings 生成一条新的 IntentBindRecord 记录. 这时候如果服务已经启动, 就会马上进入 requestServiceBindingLocked 方法
- private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
- boolean execInFg, boolean rebind) throws TransactionTooLargeException {
- //...
- //requested 此时为 false
- if ((!i.requested || rebind) && i.apps.size()> 0) {
- try {
- //...
- r.App.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
- r.App.repProcState);
- if (!rebind) {
- // 触发 onBind 之后 requested 被置为了 true
- i.requested = true;
- }
- i.hasBound = true;
- i.doRebind = false;
- } catch (TransactionTooLargeException e) {
- //...
- } catch (RemoteException e) {
- //...
- }
- }
- return true;
- }
由此可见, 如果使用相同的 Intent 请求 bind, 那么第二次进来 requested 已经是 true 了, 便不会触发 Service.onBind.
来源: https://juejin.im/post/5be53084e51d45709d2efb6f