首頁技術文章正文

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

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

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


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