首頁技術(shù)文章正文

Android+物聯(lián)網(wǎng)培訓(xùn)之多次解綁拋出異常原因

更新時(shí)間:2017-07-02 來源:黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院 瀏覽量:

 
多次解綁服務(wù)(unBindService)拋出異常原因解析
 
大家在學(xué)習(xí)綁定服務(wù)的時(shí)候,如果對一個服務(wù)進(jìn)行多次解綁,那么就會拋出服務(wù)沒有注冊的異常,我們也僅僅是記住了這個結(jié)果,但是為什么會出現(xiàn)這個原因,我們并沒有去深究,今天我們可以通過查看源碼的方式,去看看到底android是怎么拋出這個異常的。
此次源碼查看,我們分為兩部分: 一部分是綁定服務(wù)的源碼,一部分是解綁服務(wù)的代碼。這里我們就按照綁定服務(wù),然后解綁服務(wù)的思路去看源碼。
 
綁定服務(wù)的源碼,通常我們都是調(diào)用bindService()這個方法,這個方法雖然定義在Context中,但是實(shí)際上它的實(shí)現(xiàn)是在Context的一個實(shí)現(xiàn)類中,叫做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");
        }
...
}
 
解釋: 綁定服務(wù)的代碼后續(xù)還有很多,我們現(xiàn)在只關(guān)注到這里即可。大家看源碼要有一個主線目標(biāo),千萬不要眉毛胡子一把抓, 我們這里的主線目標(biāo)是: 為什么多次解綁會拋出異常。至于這個服務(wù)是怎么啟動起來的,跟我們目前沒有關(guān)系。
If里面的對象 mPackageInfo , 實(shí)際上是一個LoadedApk的類對象,這個LoadedApk ,主要是用來保存當(dāng)前加載的應(yīng)用程序的一些信息。接下來我們?nèi)コ虺騡etServiceDispatcher這個方法。
 
  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.方法的代碼比較多,但是實(shí)際上仔細(xì)一看,這個代碼就是做了一堆的if判空操作,然后執(zhí)行對Map集合的添加操作。 mServices 是一個Map , key是以上下文, value又是一個map, 
HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
 
2.這里的get方法作用就是去獲取曾經(jīng)有沒有綁定這個服務(wù),我們首次進(jìn)來,得到的結(jié)果是null ,  所以會直接進(jìn)入第二個if判斷 , 里面的代碼看似簡單,但是有可能也會繞暈。
3.它實(shí)際上的工作就是構(gòu)建一個對象sd, 然后創(chuàng)建一個map<ServiceConnection  , sd >集合, 把構(gòu)建好的這個sd對象裝到map集合中,又把map集合裝到mService<context , map >中。
 
總結(jié):
這里有兩個Map集合嵌套:
外層 map集合key是 上下文, value是內(nèi)層嵌套的map ,
內(nèi)層嵌套的map, key是ServiceConnection ,也就是我們綁定服務(wù)的conn , value是 ServiceDispatcher對象
 
--------------------------------------------華麗的分割線-----------------------------------------------------------
 
綁定服務(wù)的代碼就看到這里,接下來我們?nèi)タ纯唇咏饨壏?wù)的代碼,解綁服務(wù),我們使用的是unBinderService , 這個方法與bindService一樣,都是在ContextImpl中實(shí)現(xiàn)的
 
 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 正是早前我們綁定服務(wù)提到的LoadedApk類的對象,此處不為空, 我們只看if里面的第一句代碼即可。早前我們綁定服務(wù)用的是getServiceDispatcher 主要就是做封裝(Map的數(shù)據(jù)添加)工作,那么這里的方法forgetServiceDispatcher ,通過方法名字,我們應(yīng)該能夠猜出來,它實(shí)際上也就是做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. 方法進(jìn)來第一步就是去找mService ,早前我們綁定服務(wù)的時(shí)候用過它,實(shí)際上是一個外層的Map集合 ,先從里面取出當(dāng)前上下文為key對應(yīng)的值,早前我們綁定過服務(wù),所以此處得到的對象map不為空,
2.  執(zhí)行 map.get(c) 判斷內(nèi)層的map是否有對應(yīng)的數(shù)據(jù), 這個c就是我們解綁傳遞進(jìn)來的ServiceConnection對象, 早前我們綁定服務(wù)用的也是這個對象,所以是能夠拿到一個sd對象的。并且它還不是null, 最后就從map里面移除了。
 
3. 接著判斷內(nèi)層map是空,再移除外層map集合的記錄。最后執(zhí)行return返回,這個方法執(zhí)行完畢。 后續(xù)的服務(wù)停止的代碼我們就不去看了。
if (map.size() == 0) {
                        mServices.remove(context)
                    }
 
4. 這個時(shí)候,如果我們在執(zhí)行 解綁服務(wù),那么可想而知,Map集合中就不會再有記錄了。所以上面的if語句都不會執(zhí)行,直接跑到最后的if邏輯 ,并且我們的context不會是空,所以就只有拋出 服務(wù)沒有注冊的異常了。
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);
            }
 
源碼看到這,這個問題的答案也就水落石出了,其實(shí)整個過程并不算太難,只不過有時(shí)候我們沒有查看源碼的習(xí)慣,導(dǎo)致看起來有一點(diǎn)不是那么的順暢。還是希望大家在以后的學(xué)習(xí)中多查看系統(tǒng)的源碼,了解系統(tǒng)架構(gòu)的設(shè)計(jì)。
最后總結(jié)一下:
1. 綁定服務(wù),首先要做的事情就是先用Map記錄當(dāng)前綁定服務(wù)所需的一些信息。 然后啟動服務(wù)。
2. 解綁服務(wù),先從早前的Map集合中移除記錄,然后停止服務(wù)。
3. 如果再次解綁,無非就是再到這個map集合中找找有沒有這條記錄,沒有就拋出服務(wù)沒有注冊的異常,也就是早前根本沒有注冊過任何服務(wù)。


本文版權(quán)歸黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院
首發(fā):http://android.itheima.com
分享到:
在線咨詢 我要報(bào)名
和我們在線交談!