教育行业A股IPO第一股(股票代码 003032)

全国咨询/投诉热线:400-618-4000

Android培训实战教程之多次解绑抛出异常原因

更新时间:2015年12月28日14时27分 来源:传智播客Android培训学院 浏览次数:

 
多次解绑服务(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://www.itcast.cn/android/
0 分享到:
和我们在线交谈!