多次解綁服務(unBindService)拋出異常原因解析
大家在學習綁定服務的時候,如果對一個服務進行多次解綁,那么就會拋出服務沒有注冊的異常,我們也僅僅是記住了這個結果,但是為什么會出現(xiàn)這個原因,我們并沒有去深究,今天我們可以通過查看源碼的方式,去看看到底android是怎么拋出這個異常的。
此次源碼查看,我們分為兩部分: 一部分是綁定服務的源碼,一部分是解綁服務的代碼。這里我們就按照綁定服務,然后解綁服務的思路去看源碼。
綁定服務的源碼,通常我們都是調(diào)用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 ,主要是用來保存當前加載的應用程序的一些信息。接下來我們?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.方法的代碼比較多,但是實際上仔細一看,這個代碼就是做了一堆的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是內(nèi)層嵌套的map ,
內(nèi)層嵌套的map, key是ServiceConnection ,也就是我們綁定服務的conn , value是 ServiceDispatcher對象
--------------------------------------------華麗的分割線-----------------------------------------------------------
綁定服務的代碼就看到這里,接下來我們?nèi)タ纯唇咏饨壏盏拇a,解綁服務,我們使用的是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) 判斷內(nèi)層的map是否有對應的數(shù)據(jù), 這個c就是我們解綁傳遞進來的ServiceConnection對象, 早前我們綁定服務用的也是這個對象,所以是能夠拿到一個sd對象的。并且它還不是null, 最后就從map里面移除了。
3. 接著判斷內(nèi)層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培訓學院所有,歡迎轉載,轉載請注明作者出處。謝謝!
作者:傳智播客Android培訓學院
首發(fā):http://xamj520.com/android/