多次解绑服务(unBindService)抛出异常原因解析
大家在学习绑定服务的时候,如果对一个服务进行多次解绑,那么就会抛出服务没有注册的异常,我们也仅仅是记住了这个结果,但是为什么会出现这个原因,我们并没有去深究,今天我们可以通过查看源码的方式,去看看到底android是怎么抛出这个异常的。
此次源码查看,我们分为两部分: 一部分是绑定服务的源码,一部分是解绑服务的代码。这里我们就按照绑定服务,然后解绑服务的思路去看源码。
绑定服务的源码,通常我们都是调用bindService()这个方法,这个方法虽然定义在Context中,但是实际上它的实现是在Context的一个实现类中,叫做ContextImpl .
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
IServiceConnection sd;
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
mMainThread.getHandler(), flags);
} else {
throw new RuntimeException("Not supported in system context");
}
...
}
解释: 绑定服务的代码后续还有很多,我们现在只关注到这里即可。大家看源码要有一个主线目标,千万不要眉毛胡子一把抓, 我们这里的主线目标是: 为什么多次解绑会抛出异常。至于这个服务是怎么启动起来的,跟我们目前没有关系。
If里面的对象 mPackageInfo , 实际上是一个LoadedApk的类对象,这个LoadedApk ,主要是用来保存当前加载的应用程序的一些信息。接下来我们去瞅瞅getServiceDispatcher这个方法。
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Handler handler, int flags) {
synchronized (mServices) {
LoadedApk.ServiceDispatcher sd = null;
HashMap<ServiceConnection,LoadedApk.ServiceDispatcher>map
=mServices.get(context);
if (map != null) {
sd = map.get(c);
}
if (sd == null) {
sd = new ServiceDispatcher(c, context, handler, flags);
if (map == null) {
map=newHashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
mServices.put(context, map);
}
map.put(c, sd);
} else {
sd.validate(context, handler);
}
return sd.getIServiceConnection();
}
}
解释:
1.方法的代码比较多,但是实际上仔细一看,这个代码就是做了一堆的if判空操作,然后执行对Map集合的添加操作。 mServices 是一个Map , key是以上下文, value又是一个map,
HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
2.这里的get方法作用就是去获取曾经有没有绑定这个服务,我们首次进来,得到的结果是null , 所以会直接进入第二个if判断 , 里面的代码看似简单,但是有可能也会绕晕。
3.它实际上的工作就是构建一个对象sd, 然后创建一个map<ServiceConnection , sd >集合, 把构建好的这个sd对象装到map集合中,又把map集合装到mService<context , map >中。
总结:
这里有两个Map集合嵌套:
外层 map集合key是 上下文, value是内层嵌套的map ,
内层嵌套的map, key是ServiceConnection ,也就是我们绑定服务的conn , value是 ServiceDispatcher对象
--------------------------------------------华丽的分割线-----------------------------------------------------------
绑定服务的代码就看到这里,接下来我们去看看接解绑服务的代码,解绑服务,我们使用的是unBinderService , 这个方法与bindService一样,都是在ContextImpl中实现的
public void unbindService(ServiceConnection conn) {
if (mPackageInfo != null) {
IServiceConnection sd = mPackageInfo.forgetServiceDispatcher(
getOuterContext(), conn);
try {
ActivityManagerNative.getDefault().unbindService(sd);
} catch (RemoteException e) {
}
} else {
throw new RuntimeException("Not supported in system context");
}
}
代码并不多, 这里的mPackageInfo 正是早前我们绑定服务提到的LoadedApk类的对象,此处不为空, 我们只看if里面的第一句代码即可。早前我们绑定服务用的是getServiceDispatcher 主要就是做封装(Map的数据添加)工作,那么这里的方法forgetServiceDispatcher ,通过方法名字,我们应该能够猜出来,它实际上也就是做Map的删除工作。
public final IServiceConnection forgetServiceDispatcher(Context context,
ServiceConnection c) {
synchronized (mServices) {
HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map
= mServices.get(context);
LoadedApk.ServiceDispatcher sd = null;
if (map != null) {
sd = map.get(c);
if (sd != null) {
map.remove(c);
sd.doForget();
if (map.size() == 0) {
mServices.remove(context);
}
...
return sd.getIServiceConnection();
}
}
if (context == null) {
throw new IllegalStateException("Unbinding Service " + c
+ " from Context that is no longer in use: " + context);
} else {
throw new IllegalArgumentException("Service not registered: " + c);
}
}
}
解释:
1. 方法进来第一步就是去找mService ,早前我们绑定服务的时候用过它,实际上是一个外层的Map集合 ,先从里面取出当前上下文为key对应的值,早前我们绑定过服务,所以此处得到的对象map不为空,
2. 执行 map.get(c) 判断内层的map是否有对应的数据, 这个c就是我们解绑传递进来的ServiceConnection对象, 早前我们绑定服务用的也是这个对象,所以是能够拿到一个sd对象的。并且它还不是null, 最后就从map里面移除了。
3. 接着判断内层map是空,再移除外层map集合的记录。最后执行return返回,这个方法执行完毕。 后续的服务停止的代码我们就不去看了。
if (map.size() == 0) {
mServices.remove(context)
}
4. 这个时候,如果我们在执行 解绑服务,那么可想而知,Map集合中就不会再有记录了。所以上面的if语句都不会执行,直接跑到最后的if逻辑 ,并且我们的context不会是空,所以就只有抛出 服务没有注册的异常了。
if (context == null) {
throw new IllegalStateException("Unbinding Service " + c
+ " from Context that is no longer in use: " + context);
} else {
throw new IllegalArgumentException("Service not registered: " + c);
}
源码看到这,这个问题的答案也就水落石出了,其实整个过程并不算太难,只不过有时候我们没有查看源码的习惯,导致看起来有一点不是那么的顺畅。还是希望大家在以后的学习中多查看系统的源码,了解系统架构的设计。
最后总结一下:
1. 绑定服务,首先要做的事情就是先用Map记录当前绑定服务所需的一些信息。 然后启动服务。
2. 解绑服务,先从早前的Map集合中移除记录,然后停止服务。
3. 如果再次解绑,无非就是再到这个map集合中找找有没有这条记录,没有就抛出服务没有注册的异常,也就是早前根本没有注册过任何服务。
本文版权归黑马程序员Android+物联网培训学院所有,欢迎转载,转载请注明作者出处。谢谢!
作者:黑马程序员Android+物联网培训学院
首发:http://android.itheima.com