内容简介:通过之前对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的一些迷思》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming in Haskell
Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99
Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!