内容简介:通过之前对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
。
以上所述就是小编给大家介绍的《Service的一些迷思》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
现代操作系统(第3版)
Andrew S. Tanenbaum / 陈向群、马洪兵 / 机械工业出版社 / 2009-7 / 75.00元
本书是操作系统领域的经典之作,与第2版相比,增加了关于Linux、Windows Vista和Symbian操作系统的详细介绍。书中集中讨论了操作系统的基本原理,包括进程、线程、存储管理、文件系统、输入/输出、死锁等,同时还包含了有关计算机安全、多媒体操作系统、掌上计算机操作系统、微内核、多核处理机上的虚拟机以及操作系统设计等方面的内容。此外,还在第2版的基础上对部分习题进行了增删,更有助于读者学......一起来看看 《现代操作系统(第3版)》 这本书的介绍吧!