在Activity生命週期管理 以及 外掛載入機制 中我們詳細講述了外掛化過程中對於Activity元件的處理方式,為了實現Activity的外掛化我們付出了相當多的努力;那麼Android系統的其他元件,比如BroadcastReceiver,Service還有ContentProvider,它們又該如何處理呢?
相比Activity,BroadcastReceiver要簡單很多——廣播的生命週期相當簡單;如果希望外掛能夠支援廣播,這意味著什麼?
回想一下我們日常開發的時候是如何使用BroadcastReceiver的:註冊, 傳送和接收;因此,要實現BroadcastReceiver的外掛化就這三種操作提供支援;接下來我們將一步步完成這個過程。
閱讀本文之前,可以先clone一份 understand-plugin-framework,參考此專案的receiver-management
模組。另外,外掛框架原理解析系列文章見索引。
如果連BroadcastReceiver的工作原理都不清楚,又怎麼能讓外掛支援它?老規矩,知己知彼。
原始碼分析
我們可以註冊一個BroadcastReceiver然後接收我們感興趣的廣播,也可以給某有緣人發出某個廣播;因此,我們對原始碼的分析按照兩條路線展開:
註冊過程
不論是靜態廣播還是動態廣播,在使用之前都是需要註冊的;動態廣播的註冊需要藉助Context類的registerReceiver方法,而靜態廣播的註冊直接在AndroidManifest.xml中宣告即可;我們首先分析一下動態廣播的註冊過程。
Context類的registerReceiver的真正實現在ContextImpl裡面,而這個方法間接呼叫了registerReceiverInternal,原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) { IIntentReceiver rd = null; // Important !!!!! if (receiver != null) { if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = new LoadedApk.ReceiverDispatcher( receiver, context, scheduler, null, true).getIIntentReceiver(); } } try { return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId); } catch (RemoteException e) { return null; } } |
可以看到,BroadcastReceiver的註冊也是通過AMS
完成的;在進入AMS
跟蹤它的registerReceiver方法之前,我們先弄清楚這個IIntentReceiver
型別的變數rd
是什麼。首先查閱API文件,很遺憾SDK裡面沒有匯出這個類,我們直接去 grepcode 上看,文件如下:
System private API for dispatching intent broadcasts. This is given to the activity manager as part of registering for an intent broadcasts, and is called when it receives intents.
這個類是通過AIDL工具生成的,它是一個Binder物件,因此可以用來跨程式傳輸;文件說的很清楚,它是用來進行廣播分發的。什麼意思呢?
由於廣播的分發過程是在AMS中進行的,而AMS所在的程式和BroadcastReceiver所在的程式不一樣,因此要把廣播分發到BroadcastReceiver具體的程式需要進行跨程式通訊,這個通訊的載體就是IIntentReceiver類。其實這個類的作用跟 Activity生命週期管理 中提到的 IApplicationThread
相同,都是App程式給AMS程式用來進行通訊的物件。另外,IIntentReceiver
是一個介面,從上述程式碼中可以看出,它的實現類為LoadedApk.ReceiverDispatcher。
OK,我們繼續跟蹤原始碼,AMS類的registerReceiver方法程式碼有點多,這裡不一一解釋了,感興趣的話可以自行查閱;這個方法主要做了以下兩件事:
- 對傳送者的身份和許可權做出一定的校檢
- 把這個BroadcastReceiver以BroadcastFilter的形式儲存在AMS的
mReceiverResolver
變數中,供後續使用。
就這樣,被傳遞過來的BroadcastReceiver已經成功地註冊在系統之中,能夠接收特定型別的廣播了;那麼註冊在AndroidManifest.xml中的靜態廣播是如何被系統感知的呢?
在 外掛載入機制 中我們知道系統會通過PackageParser解析Apk中的AndroidManifest.xml檔案,因此我們有理由認為,系統會在解析AndroidMafest.xml的標籤(也即靜態註冊的廣播)的時候儲存相應的資訊;而Apk的解析過程是在PMS中進行的,因此靜態註冊廣播的資訊儲存在PMS中。接下來的分析會證實這一結論。
傳送和接收過程
傳送過程
傳送廣播很簡單,就是一句context.sendBroadcast(),我們順藤摸瓜,跟蹤這個方法。前文也提到過,Context中方法的呼叫都會委託到ContextImpl這個類,我們直接看ContextImpl對這個方法的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void sendBroadcast(Intent intent) { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } } |
嗯,傳送廣播也是通過AMS進行的,我們直接檢視ActivityManagerService類的broadcastIntent方法,這個方法僅僅是呼叫了broadcastIntentLocked方法,我們繼續跟蹤;broadcastIntentLocked這個方法相當長,處理了諸如粘性廣播,順序廣播,各種Flag以及動態廣播靜態廣播的接收過程,這些我們暫時不關心;值得注意的是,在這個方法中我們發現,其實廣播的傳送和接收是融為一體的。某個廣播被髮送之後,AMS會找出所有註冊過的BroadcastReceiver中與這個廣播匹配的接收者,然後將這個廣播分發給相應的接收者處理。
匹配過程
某一條廣播被髮出之後,並不是阿貓阿狗都能接收它並處理的;BroadcastReceiver可能只對某些型別的廣播感興趣,因此它也只能接收和處理這種特定型別的廣播;在broadcastIntentLocked方法內部有如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Figure out who all will receive this broadcast. List receivers = null; List<BroadcastFilter> registeredReceivers = null; // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); } if (intent.getComponent() == null) { if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) { // Query one target user at a time, excluding shell-restricted users // 略 } else { registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId); } } |
這裡有兩個列表receivers
和registeredReceivers
,看名字好像是廣播接收者的列表;下面是它們的賦值過程:
1 2 |
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId); |
讀者可以自行跟蹤這兩個方法的程式碼,過程比較簡單,我這裡直接給出結論:
receivers
是對這個廣播感興趣的靜態BroadcastReceiver列表;collectReceiverComponents 通過PackageManager獲取了與這個廣播匹配的靜態BroadcastReceiver資訊;這裡也證實了我們在分析BroadcasrReceiver註冊過程中的推論——靜態BroadcastReceiver的註冊過程的確實在PMS中進行的。mReceiverResolver
儲存了動態註冊的BroadcastReceiver的資訊;還記得這個mReceiverResolver
嗎?我們在分析動態廣播的註冊過程中發現,動態註冊的BroadcastReceiver的相關資訊最終儲存在此物件之中;在這裡,通過mReceiverResolver物件匹配出了對應的BroadcastReceiver供進一步使用。
現在系統通過PMS拿到了所有符合要求的靜態BroadcastReceiver,然後從AMS中獲取了符合要求的動態BroadcastReceiver;因此接下來的工作非常簡單:喚醒這些廣播接受者。簡單來說就是回撥它們的onReceive
方法。
接收過程
通過上文的分析過程我們知道,在AMS的broadcastIntentLocked方法中找出了符合要求的所有BroadcastReceiver;接下來就需要把這個廣播分發到這些接收者之中。在broadcastIntentLocked方法的後半部分有如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId); boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } |
首先建立了一個BroadcastRecord代表此次傳送的這條廣播,然後把它丟進一個佇列,最後通過scheduleBroadcastsLocked通知佇列對廣播進行處理。
在BroadcastQueue中通過Handle排程了對於廣播處理的訊息,排程過程由processNextBroadcast方法完成,而這個方法通過performReceiveLocked最終呼叫了IIntentReceiver的performReceive方法。
這個IIntentReceiver
正是在廣播註冊過程中由App程式提供給AMS程式的Binder物件,現在AMS通過這個Binder物件進行IPC呼叫通知廣播接受者所在程式完成餘下操作。在上文我們分析廣播的註冊過程中提到過,這個IItentReceiver的實現是LoadedApk.ReceiverDispatcher;我們檢視這個物件的performReceive方法,原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 |
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); args.sendFinished(mgr); } } } |
這個方法建立了一個Args
物件,然後把它post到了mActivityThread這個Handler中;我們檢視Args
類的run
方法:(堅持一下,馬上就分析完了 ^ ^)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public void run() { final BroadcastReceiver receiver = mReceiver; final boolean ordered = mOrdered; final IActivityManager mgr = ActivityManagerNative.getDefault(); final Intent intent = mCurIntent; mCurIntent = null; if (receiver == null || mForgotten) { if (mRegistered && ordered) { sendFinished(mgr); } return; } try { ClassLoader cl = mReceiver.getClass().getClassLoader(); // Important!! load class intent.setExtrasClassLoader(cl); setExtrasClassLoader(cl); receiver.setPendingResult(this); receiver.onReceive(mContext, intent); // callback } catch (Exception e) { if (mRegistered && ordered) { sendFinished(mgr); } if (mInstrumentation == null || !mInstrumentation.onException(mReceiver, e)) { throw new RuntimeException( "Error receiving broadcast " + intent + " in " + mReceiver, e); } } if (receiver.getPendingResult() != null) { finish(); } } |
這裡,我們看到了相應BroadcastReceiver的onReceive
回撥;因此,廣播的工作原理到這裡就水落石出了;我們接下來將探討如何實現對於廣播的外掛化。
思路分析
上文中我們分析了BroadcastReceiver的工作原理,那麼怎麼才能實現對BroadcastReceiver的外掛化呢?
從分析過程中我們發現,Framework對於靜態廣播和動態廣播的處理是不同的;不過,這個不同之處僅僅體現在註冊過程——靜態廣播需要在AndroidManifest.xml中註冊,並且註冊的資訊儲存在PMS中;動態廣播不需要預註冊,註冊的資訊儲存在AMS中。
從實現Activity的外掛化過程中我們知道,需要在AndroidManifest.xml中預先註冊是一個相當麻煩的事情——我們需要使用『替身』並在合適的時候進行『偷樑換柱』;因此看起來動態廣播的處理要容易那麼一點,我們先討論一下如何實現動態註冊BroadcastReceiver的外掛化。
首先,廣播並沒有複雜的生命週期,它的整個存活過程其實就是一個onReceive
回撥;而動態廣播又不需要在AndroidManifest.xml中預先註冊,所以動態註冊的BroadcastReceiver其實可以當作一個普通的Java物件;我們完全可以用純ClassLoader技術實現它——不就是把外掛中的Receiver載入進來,然後想辦法讓它能接受onReceive
回撥嘛。
靜態BroadcastReceiver看起來要複雜一些,但是我們連Activity都搞定了,還有什麼難得到我們呢?對於實現靜態BroadcastReceiver外掛化的問題,有的童鞋或許會想,我們可以借鑑Activity的工作方式——用替身和Hook解決。但是很遺憾,這樣是行不通的。為什麼呢?
BroadcastReceiver有一個IntentFilter的概念,也就是說,每一個BroadcastReceiver只對特定的Broadcast感興趣;而且,AMS在進行廣播分發的時候,也會對這些BroadcastReceiver與發出的廣播進行匹配,只有Intent匹配的Receiver才能收到廣播;在分析原始碼的時候也提到了這個匹配過程。如果我們嘗試用替身Receiver解決靜態註冊的問題,那麼它的IntentFilter該寫什麼?我們無法預料外掛中靜態註冊的Receiver會使用什麼型別的IntentFilter,就算我們在AndroidManifest.xml中宣告替身也沒有用——我們壓根兒收不到與我們的IntentFilter不匹配的廣播。其實,我們對於Activity的處理方式也有這個問題;如果你嘗試用IntentFilter的方式啟動Activity,這並不能成功;這算得上是DroidPlugin的缺陷之一。
那麼,我們就真的對靜態BroadcastReceiver無能為力嗎?想一想這裡的難點是什麼?
沒錯,主要是在靜態BroadcastReceiver裡面這個IntentFilter我們事先無法確定,它是動態變化的;但是,動態BroadcastReceiver不是可以動態新增IntentFilter嗎!!!
可以把靜態廣播當作動態廣播處理
既然都是廣播,它們的功能都是訂閱一個特定的訊息然後執行某個特定的操作,我們完全可以把外掛中的靜態廣播全部註冊為動態廣播,這樣就解決了靜態廣播的問題。當然,這樣也是有缺陷的,靜態BroadcastReceiver與動態BroadcastReceiver一個非常大的不同之處在於:動態BroadcastReceiver在程式死亡之後是無法接收廣播的,而靜態BroadcastReceiver則可以——系統會喚醒Receiver所在程式;這算得上缺陷之二,當然,瑕不掩瑜。
靜態廣播非靜態的實現
上文我們提到,可以把靜態BroadcastReceiver當作動態BroadcastReceiver處理;我們接下來實現這個過程。
解析
要把外掛中的靜態BroadcastReceiver當作動態BroadcastReceiver處理,我們首先得知道外掛中到底註冊了哪些廣播;這個過程歸根結底就是獲取AndroidManifest.xml中的標籤下面的內容,我們可以選擇手動解析xml檔案;這裡我們選擇使用系統的 PackageParser 幫助解析,這種方式在之前的 [外掛載入過程][] 中也用到過,如果忘記了可以溫習一下。
PackageParser中有一系列方法用來提取Apk中的資訊,可是翻遍了這個類也沒有找到與「Receiver」名字相關的方法;最終我們發現BroadcastReceiver資訊是用與Activity相同的類儲存的!這一點可以在PackageParser的內部類Package中發現端倪——成員變數receivers
和activities
的範型型別相同。所以,我們要解析apk的的資訊,可以使用PackageParser的generateActivityInfo
方法。
知道這一點之後,程式碼就比較簡單了;使用反射呼叫相應的隱藏介面,並且在必要的時候構造相應引數的方式我們在外掛化系列文章中已經講述過很多,相信讀者已經熟練,這裡就不贅述,直接貼程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private static void parserReceivers(File apkFile) throws Exception { Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser"); Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); Object packageParser = packageParserClass.newInstance(); // 首先呼叫parsePackage獲取到apk物件對應的Package物件 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS); // 讀取Package物件裡面的receivers欄位,注意這是一個 List<Activity> (沒錯,底層把<receiver>當作<activity>處理) // 接下來要做的就是根據這個List<Activity> 獲取到Receiver對應的 ActivityInfo (依然是把receiver資訊用activity處理了) Field receiversField = packageObj.getClass().getDeclaredField("receivers"); List receivers = (List) receiversField.get(packageObj); // 呼叫generateActivityInfo 方法, 把PackageParser.Activity 轉換成 Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity"); Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Class<?> userHandler = Class.forName("android.os.UserHandle"); Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); int userId = (Integer) getCallingUserIdMethod.invoke(null); Object defaultUserState = packageUserStateClass.newInstance(); Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component"); Field intentsField = componentClass.getDeclaredField("intents"); // 需要呼叫 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int) Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo", packageParser$ActivityClass, int.class, packageUserStateClass, int.class); // 解析出 receiver以及對應的 intentFilter for (Object receiver : receivers) { ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId); List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiver); sCache.put(info, filters); } } |
註冊
我們已經解析得到了外掛中靜態註冊的BroadcastReceiver的資訊,現在我們只需要把這些靜態廣播動態註冊一遍就可以了;但是,由於BroadcastReceiver的實現類存在於外掛之後,我們需要手動用ClassLoader來載入它;這一點在 外掛載入機制 已有講述,不囉嗦了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ClassLoader cl = null; for (ActivityInfo activityInfo : ReceiverHelper.sCache.keySet()) { Log.i(TAG, "preload receiver:" + activityInfo.name); List<? extends IntentFilter> intentFilters = ReceiverHelper.sCache.get(activityInfo); if (cl == null) { cl = CustomClassLoader.getPluginClassLoader(apk, activityInfo.packageName); } // 把解析出來的每一個靜態Receiver都註冊為動態的 for (IntentFilter intentFilter : intentFilters) { BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(activityInfo.name).newInstance(); context.registerReceiver(receiver, intentFilter); } } |
就這樣,我們對外掛靜態BroadcastReceiver的支援已經完成了,是不是相當簡單?至於外掛中的動態廣播如何實現外掛化,這一點交給讀者自行完成,希望你在解決這個問題的過程中能夠加深對於外掛方案的理解 ^ ^
小節
本文我們介紹了BroadcastReceiver元件的外掛化方式,可以看到,外掛方案對於BroadcastReceiver的處理相對簡單;同時「靜態廣播非靜態」的特性以及BroadcastReceiver先天的一些特點導致外掛方案沒有辦法做到盡善盡美,不過這都是大醇小疵——在絕大多數情況下,這樣的處理方式是可以滿足需求的。
雖然對於BroadcastReceiver的處理方式相對簡單,但是文章的內容卻並不短——我們花了大量的篇幅講述BroadcastReceiver的原理,這也是我的初衷:藉助DroidPlugin更深入地瞭解Android Framework。
接下來為文章會講述四大元件中的另外兩個——Service和ContentProvider的外掛化方案;喜歡就點個贊吧~持續更新,請關注github專案 understand-plugin-framework 和我的 部落格 ! 如果你覺得能從文中學到皮毛,還請支援一下 :)
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式