Android插件化原理解析——廣播的管理
在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,源碼如下:
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的<receiver>標簽(也即靜態注冊的廣播)的時候保存相應的信息;而Apk的解析過程是在PMS中進行的,因此 靜態注冊廣播的信息存儲在PMS中 。接下來的分析會證實這一結論。
發送和接收過程
發送過程
發送廣播很簡單,就是一句context.sendBroadcast(),我們順藤摸瓜,跟蹤這個方法。前文也提到過,Context中方法的調用都會委托到ContextImpl這個類,我們直接看ContextImpl對這個方法的實現:
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方法內部有如下代碼:
// 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 ,看名字好像是廣播接收者的列表;下面是它們的賦值過程:
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方法的后半部分有如下代碼:
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方法,源碼如下:
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 方法:(堅持一下,馬上就分析完了 ^ ^)
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中的<receiver>標簽下面的內容,我們可以選擇手動解析xml文件;這里我們選擇使用系統的 PackageParser 幫助解析,這種方式在之前的 [插件加載過程][] 中也用到過,如果忘記了可以溫習一下。
PackageParser中有一系列方法用來提取Apk中的信息,可是翻遍了這個類也沒有找到與「Receiver」名字相關的方法;最終我們發現BroadcastReceiver信息是用與Activity相同的類存儲的!這一點可以在PackageParser的內部類Package中發現端倪——成員變量 receivers 和 activities 的范型類型相同。所以,我們要解析apk的<receiver>的信息,可以使用PackageParser的 generateActivityInfo 方法。
知道這一點之后,代碼就比較簡單了;使用反射調用相應的隱藏接口,并且在必要的時候構造相應參數的方式我們在插件化系列文章中已經講述過很多,相信讀者已經熟練,這里就不贅述,直接貼代碼:
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來加載它;這一點在插件加載機制 已有講述,不啰嗦了。
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 和我的博客 ! 如果你覺得能從文中學到皮毛,還請支持一下 :)
來自: http://weishu.me/2016/04/12/understand-plugin-framework-receiver/