Android 插件化原理解析——Activity生命周期管理
之前的?Android插件化原理解析?系列文章揭開了Hook機制的神秘面紗,現在我們手握倚天屠龍,那么如何通過這種技術完成插件化方案呢?具體來說,插件中的Activity,Service等組件如何在Android系統(tǒng)上運行起來?
在Java平臺要做到動態(tài)運行模塊、熱插拔可以使用ClassLoader技術進行動態(tài)類加載,比如廣泛使用的OSGi技術。在Android上當然也可以使用動態(tài)加載技術,但是僅僅把類加載進來就足夠了嗎?Activity,Service等組件是有生命周期的,它們統(tǒng)一由系統(tǒng)服務AMS管理;使用ClassLoader可以從插件中創(chuàng)建Activity對象,但是,一個沒有生命周期的Activity對象有什么用?所以在Android系統(tǒng)上,僅僅完成動態(tài)類加載是不夠的;我們需要想辦法把我們加載進來的Activity等組件交給系統(tǒng)管理,讓AMS賦予組件生命周期;這樣才算是一個有血有肉的完善的插件化方案。
接下來的系列文章會講述 DroidPlugin對于Android四大組件的處理方式,我們且看它如何采用Hook技術坑蒙拐騙把系統(tǒng)玩弄于股掌之中,最終賦予Activity,Service等組件生命周期,完成借尸還魂的。
首先,我們來看看DroidPlugin對于Activity組件的處理方式。
閱讀本文之前,可以先clone一份?understand-plugin-framework,參考此項目的intercept-activity模塊。另外,如果對于Hook技術不甚了解,請先查閱我之前的文章:
AndroidManifest.xml的限制
讀到這里,或許有部分讀者覺得疑惑了,啟動Activity不就是一個startActivity的事嗎,有這么神秘兮兮的?
啟動Activity確實非常簡單,但是Android卻有一個限制:必須在AndroidManifest.xml中顯示聲明使用的Activity;我相信讀者肯定會遇到下面這種異常:
| 1 2 3 | 03-18 15:29:56.074 20709-20709/com.weishu.intercept_activity.app E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.intercept_activity.app, PID: 20709 android.content.ActivityNotFoundException: Unable to find explicit activity class {com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declared this activity in your AndroidManifest.xml? |
『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個硬性要求很大程度上限制了插件系統(tǒng)的發(fā)揮:假設我們需要啟動一個插件的Activity,插件使用的Activity是無法預知的,這樣肯定也不會在Manifest文件中聲明;如果插件新添加一個Activity,主程序的AndroidManifest.xml就需要更新;既然雙方都需要修改升級,何必要使用插件呢?這已經違背了動態(tài)加載的初衷:不修改插件框架而動態(tài)擴展功能。
能不能想辦法繞過這個限制呢?
束手無策啊,怎么辦?借刀殺人偷梁換柱無中生有以逸待勞乘火打劫瞞天過海…等等!偷梁換柱瞞天過海?貌似可以一試。
我們可以耍個障眼法:既然AndroidManifest文件中必須聲明,那么我就聲明一個(或者有限個)替身Activity好了,當需要啟動插件的某個Activity的時候,先讓系統(tǒng)以為啟動的是AndroidManifest中聲明的那個替身,暫時騙過系統(tǒng);然后到合適的時候又替換回我們需要啟動的真正的Activity;所謂瞞天過海,莫過如此!
現在有了方案了,但是該如何做呢?兵書又說,知己知彼百戰(zhàn)不殆!如果連Activity的啟動過程都不熟悉,怎么完成這個瞞天過海的過程?
Activity啟動過程
啟動Activity非常簡單,一個startActivity就完事了;那么在這個簡單調用的背后發(fā)生了什么呢?Look the fucking source code!
關于Activity 的啟動過程,也不是三言兩語能解釋清楚的,如果按照源碼一步一步走下來,插件化系列文章就不用寫了;所以這里我就給出一個大致流程,只列出關鍵的調用點(以Android 6.0源碼為例);如果讀者希望更詳細的講解,可以參考老羅的?Android應用程序的Activity啟動過程簡要介紹和學習計劃
首先是Activity類的startActivity方法:
| 1 2 3 | public void startActivity(Intent intent) { startActivity(intent, null); } |
跟著這個方法一步一步跟蹤,會發(fā)現它最后在startActivityForResult里面調用了Instrument對象的execStartActivity方法;接著在這個函數里面調用了ActivityManagerNative類的startActivity方法;這個過程在前文已經反復舉例講解了,我們知道接下來會通過Binder IPC到AMS所在進程調用AMS的startActivity方法;Android系統(tǒng)的組件生命周期管理就是在AMS里面完成的,那么在AMS里面到底做了什么呢?
ActivityManagerService的startActivity方法如下:
| 1 2 3 4 5 6 7 8 | public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } |
很簡單,直接調用了startActivityAsUser這個方法;接著是ActivityStackSupervisor類的startActivityMayWait方法。這個ActivityStackSupervisor類到底是個啥?如果仔細查閱,低版本的Android源碼上是沒有這個類的;后來AMS的代碼進行了部分重構,關于Activity棧管理的部分單獨提取出來成為了ActivityStackSupervisor類;好了,繼續(xù)看代碼。
startActivityMayWait這個方法前面對參數進行了一系列處理,我們需要知道的是,在這個方法內部對傳進來的Intent進行了解析,并嘗試從中取出關于啟動Activity的信息。
然后這個方法調用了startActivityLocked方法;在startActivityLocked方法內部進行了一系列重要的檢查:比如權限檢查,Activity的exported屬性檢查等等;我們上文所述的,啟動沒有在Manifestfest中顯示聲明的Activity拋異常也是這里發(fā)生的:
| 1 2 3 4 5 | if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } |
這里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后會檢查這個值,然后跑出異常:
| 1 2 3 4 5 6 | case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); |
源碼看到這里,我們已經確認了『必須在AndroidManifest.xml中顯示聲明使用的Activity』的原因;然而這個校檢過程發(fā)生在AMS所在的進程system_server,我們沒有辦法篡改,只能另尋他路。
OK,我們繼續(xù)跟蹤源碼;在startActivityLocked之后處理的都是Activity任務棧相關內容,這一系列ActivityStack和ActivityStackSupervisor糾纏不清的調用看下圖就明白了;不明白也沒關系: D 目前用處不大。
調用流程圖
這一系列調用最終到達了ActivityStackSupervisor的realStartActivityLocked方法;人如其名,這個方法開始了真正的“啟動Activity”:它調用了ApplicationThread的scheduleLaunchActivity方法,開始了真正的Activity對象創(chuàng)建以及啟動過程。
這個ApplicationThread是什么,是一個線程嗎?與ActivityThread有什么區(qū)別和聯(lián)系?
不要被名字迷惑了,這個ApplicationThread實際上是一個Binder對象,是App所在的進程與AMS所在進程system_server通信的橋梁;在Activity啟動的過程中,App進程會頻繁地與AMS進程進行通信:
App進程與AMS進程的通信過程如圖所示:
App進程內部的ApplicationThread server端內部有自己的Binder線程池,它與App主線程的通信通過Handler完成,這個Handler存在于ActivityThread類,它的名字很簡單就叫H,這一點我們接下來就會講到。
現在我們明白了這個ApplicationThread到底是個什么東西,接上文繼續(xù)跟蹤Activity的啟動過程;我們查看ApplicationThread的scheduleLaunchActivity方法,這個方法很簡單,就是包裝了參數最終使用Handler發(fā)了一個消息。
正如剛剛所說,ApplicationThread所在的Binder服務端使用Handler與主線程進行通信,這里的scheduleLaunchActivity方法直接把啟動Activity的任務通過一個消息轉發(fā)給了主線程;我們查看Handler類對于這個消息的處理:
| 1 2 3 4 5 6 7 | Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); |
可以看到,這里直接調用了ActivityThread的handleLaunchActivity方法,在這個方法內部有一句非常重要:
| 1 | Activity a = performLaunchActivity(r, customIntent); |
繞了這么多彎,我們的Activity終于被創(chuàng)建出來了!繼續(xù)跟蹤這個performLaunchActivity方法看看發(fā)生了什么;由于這個方法較長,我就不貼代碼了,讀者可以自行查閱;要指出的是,這個方法做了兩件很重要的事情:
| 1 2 3 4 5 | java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); |
| 1 2 3 4 5 6 7 8 | Application app = r.packageInfo.makeApplication(false, mInstrumentation); // ... 省略 if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } |
Activity的啟動過程到這里就結束了,可能讀者還是覺得迷惑:不就是調用了一系列方法嗎?具體做了什么還是不太清楚,而且為什么Android要這么設計?
方法調用鏈再長也木有關系,有兩點需要明白:
瞞天過海——啟動不在AndroidManifest.xml中聲明的Activity
簡要分析
通過上文的分析,我們已經對Activity的啟動過程了如指掌了;就讓我們干點壞事吧 :D
對與『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個問題,上文給出了思路——瞞天過海;我們可以在AndroidManifest.xml里面聲明一個替身Activity,然后在合適的時候把這個假的替換成我們真正需要啟動的Activity就OK了。
那么問題來了,『合適的時候』到底是什么時候?在前文Hook機制之動態(tài)代理中我們提到過Hook過程最重要的一步是尋找Hook點;如果是在同一個進程,startActivity到Activity真正啟動起來這么長的調用鏈,我們隨便找個地方Hook掉就完事兒了;但是問題木有這么簡單。
Activity啟動過程中很多重要的操作(正如上文分析的『必須在AndroidManifest.xml中顯式聲明要啟動的Activity』)都不是在App進程里面執(zhí)行的,而是在AMS所在的系統(tǒng)進程system_server完成,由于進程隔離的存在,我們對別的進程無能為力;所以這個Hook點就需要花點心思了。
這時候Activity啟動過程的知識就派上用場了;雖然整個啟動過程非常復雜,但其實一張圖就能總結:
簡要啟動過程
先從App進程調用startActivity;然后通過IPC調用進入系統(tǒng)進程system_server,完成Activity管理以及一些校檢工作,最后又回到了APP進程完成真正的Activioty對象創(chuàng)建。
由于這個檢驗過程是在AMS進程完成的,我們對system_server進程里面的操作無能為力,只有在我們APP進程里面執(zhí)行的過程才是有可能被Hook掉的,也就是第一步和第三步;具體應該怎么辦呢?
既然需要一個顯式聲明的Activity,那就聲明一個!可以在第一步假裝啟動一個已經在AndroidManifest.xml里面聲明過的替身Activity,讓這個Activity進入AMS進程接受檢驗;最后在第三步的時候換成我們真正需要啟動的Activity;這樣就成功欺騙了AMS進程,瞞天過海!
說到這里,是不是有點小激動呢?我們寫個demo驗證一下:『啟動一個并沒有在AndroidManifest.xml中顯示聲明的Activity』
實戰(zhàn)過程
具體來說,我們打算實現如下功能:在MainActivity中啟動一個并沒有在AndroidManifest.xml中聲明的TargetActivity;按照上文分析,我們需要聲明一個替身Activity,我們叫它StubActivity;
那么,我們的AndroidManifest.xml如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.weishu.intercept_activity.app"> <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" > <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <!-- 替身Activity, 用來欺騙AMS --> <activity android:name=".StubActivity"/> </application> </manifest> |
OK,那么我們啟動TargetActivity很簡單,就是個startActivity調用的事:
| 1 | startActivity(new Intent(MainActivity.this, TargetActivity.class)); |
如果你直接這么運行,肯定會直接拋出ActivityNotFoundException然后直接退出;我們接下來要做的就是讓這個調用成功啟動TargetActivity。
貍貓換太子——使用替身Activity繞過AMS
由于AMS進程會對Activity做顯式聲明驗證,因此在
啟動Activity的控制權轉移到AMS進程之前,我們需要想辦法臨時把TargetActivity替換成替身StubActivity;在這之間有很長的一段調用鏈,我們可以輕松Hook掉;選擇什么地方Hook是一個很自由的事情,但是Hook的步驟越后越可靠——Hook得越早,后面的調用就越復雜,越容易出錯。
我們可以選擇在進入AMS進程的入口進行Hook,具體來說也就是Hook?AMS在本進程的代理對象ActivityManagerNative。如果你不知道如何Hook掉這個AMS的代理對象,請查閱我之前的文章?Hook機制之AMS&PMS
我們Hook掉ActivityManagerNative對于startActivity方法的調用,替換掉交給AMS的intent對象,將里面的TargetActivity的暫時替換成已經聲明好的替身StubActivity;這種Hook方式?前文?講述的很詳細,不贅述;替換的關鍵代碼如下:
| 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 38 39 40 41 42 | if ("startActivity".equals(method.getName())) { // 只攔截這個方法 // 替換參數, 任你所為;甚至替換原始Activity啟動別的Activity偷梁換柱 // API 23: // public final Activity startActivityNow(Activity parent, String id, // Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, // Activity.NonConfigurationInstances lastNonConfigurationInstances) { // 找到參數里面的第一個Intent 對象 Intent raw; int index = 0; for (int i = 0; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break; } } raw = (Intent) args[index]; Intent newIntent = new Intent(); // 這里包名直接寫死,如果再插件里,不同的插件有不同的包 傳遞插件的包名即可 String targetPackage = "com.weishu.intercept_activity.app"; // 這里我們把啟動的Activity臨時替換為 StubActivity ComponentName componentName = new ComponentName(targetPackage, StubActivity.class.getCanonicalName()); newIntent.setComponent(componentName); // 把我們原始要啟動的TargetActivity先存起來 newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw); // 替換掉Intent, 達到欺騙AMS的目的 args[index] = newIntent; Log.d(TAG, "hook success"); return method.invoke(mBase, args); } return method.invoke(mBase, args); |
通過這個替換過程,在ActivityManagerNative的startActivity調用之后,system_server端收到Binder驅動的消息,開始執(zhí)行ActivityManagerService里面真正的startActivity方法;這時候AMS看到的intent參數里面的組件已經是StubActivity了,因此可以成功繞過檢查,這時候如果不做后面的Hook,直接調用
| 1 | startActivity(new Intent(MainActivity.this, TargetActivity.class)); |
也不會出現上文的ActivityNotFoundException
借尸還魂——攔截Callback從恢復真身
行百里者半九十。現在我們的startActivity啟動一個沒有顯式聲明的Activity已經不會拋異常了,但是要真正正確地把TargetActivity啟動起來,還有一些事情要做。其中最重要的一點是,我們用替身StubActivity臨時換了TargetActivity,肯定需要在『合適的』時候替換回來;接下來我們就完成這個過程。
在AMS進程里面我們是沒有辦法換回來的,因此我們要等AMS把控制權交給App所在進程,也就是上面那個『Activity啟動過程簡圖』的第三步。AMS進程轉移到App進程也是通過Binder調用完成的,承載這個功能的Binder對象是IApplicationThread;在App進程它是Server端,在Server端接受Binder遠程調用的是Binder線程池,Binder線程池通過Handler將消息轉發(fā)給App的主線程;(我這里不厭其煩地敘述Binder調用過程,希望讀者不要反感,其一加深印象,其二懂Binder真的很重要)我們可以在這個Handler里面將替身恢復成真身。
這里不打算講述Handler 的原理,我們簡單看一下Handler是如何處理接收到的Message的,如果我們能攔截這個Message的接收過程,就有可能完成替身恢復工作;Handler類的dispathMesage如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 | public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } |
從這個方法可以看出來,Handler類消息分發(fā)的過程如下:
那么,ActivityThread中的Handler類H是如何實現的呢?H的部分源碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // 以下略 } } |
可以看到H類僅僅重載了handleMessage方法;通過dispathMessage的消息分發(fā)過程得知,我們可以攔截這一過程:把這個H類的mCallback替換為我們的自定義實現,這樣dispathMessage就會首先使用這個自定義的mCallback,然后看情況使用H重載的handleMessage。
這個Handler.Callback是一個接口,我們可以使用動態(tài)代理或者普通代理完成Hook,這里我們使用普通的靜態(tài)代理方式;創(chuàng)建一個自定義的Callback類:
| 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /* package */ class ActivityThreadHandlerCallback implements Handler.Callback { Handler mBase; public ActivityThreadHandlerCallback(Handler base) { mBase = base; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { // ActivityThread里面 "LAUNCH_ACTIVITY" 這個字段的值是100 // 本來使用反射的方式獲取最好, 這里為了簡便直接使用硬編碼 case 100: handleLaunchActivity(msg); break; } mBase.handleMessage(msg); return true; } private void handleLaunchActivity(Message msg) { // 這里簡單起見,直接取出TargetActivity; Object obj = msg.obj; // 根據源碼: // 這個對象是 ActivityClientRecord 類型 // 我們修改它的intent字段為我們原來保存的即可. /* switch (msg.what) { / case LAUNCH_ACTIVITY: { / Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); / final ActivityClientRecord r = (ActivityClientRecord) msg.obj; / / r.packageInfo = getPackageInfoNoCheck( / r.activityInfo.applicationInfo, r.compatInfo); / handleLaunchActivity(r, null); */ try { // 把替身恢復成真身 Field intent = obj.getClass().getDeclaredField("intent"); intent.setAccessible(true); Intent raw = (Intent) intent.get(obj); Intent target = raw.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT); raw.setComponent(target.getComponent()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } |
這個Callback類的使命很簡單:把替身StubActivity恢復成真身TargetActivity;有了這個自定義的Callback之后我們需要把ActivityThread里面處理消息的Handler類H的的mCallback修改為自定義callback類的對象:
| 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 | // 先獲取到當前的ActivityThread對象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); currentActivityThreadField.setAccessible(true); Object currentActivityThread = currentActivityThreadField.get(null); // 由于ActivityThread一個進程只有一個,我們獲取這個對象的mH Field mHField = activityThreadClass.getDeclaredField("mH"); mHField.setAccessible(true); Handler mH = (Handler) mHField.get(currentActivityThread); // 設置它的回調, 根據源碼: // 我們自己給他設置一個回調,就會替代之前的回調; // public void dispatchMessage(Message msg) { // if (msg.callback != null) { // handleCallback(msg); // } else { // if (mCallback != null) { // if (mCallback.handleMessage(msg)) { // return; // } // } // handleMessage(msg); // } // } Field mCallBackField = Handler.class.getDeclaredField("mCallback"); mCallBackField.setAccessible(true); mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH)); |
到這里,我們已經成功地繞過AMS,完成了『啟動沒有在AndroidManifest.xml中顯式聲明的Activity』的過程;瞞天過海,這種玩弄系統(tǒng)與股掌之中的快感你們能體會到嗎?
僵尸or活人?——能正確收到生命周期回調嗎
雖然我們完成了『啟動沒有在AndroidManifest.xml中顯式聲明的Activity 』,但是啟動的TargetActivity是否有自己的生命周期呢,我們還需要額外的處理過程嗎?
實際上TargetActivity已經是一個有血有肉的Activity了:它具有自己正常的生命周期;可以運行Demo代碼驗證一下。
這個過程是如何完成的呢?我們以onDestroy為例簡要分析一下:
從Activity的finish方法開始跟蹤,最終會通過ActivityManagerNative到AMS然后接著通過ApplicationThread到ActivityThread,然后通過H轉發(fā)消息到ActivityThread的handleDestroyActivity,接著這個方法把任務交給performDestroyActivity完成。
在真正分析這個方法之前,需要說明一點的是:不知讀者是否感受得到,App進程與AMS交互幾乎都是這么一種模式,幾個角色 ActivityManagerNative, ApplicationThread, ActivityThread以及Handler類H分工明確,讀者可以按照這幾個角色的功能分析AMS的任何調用過程,屢試不爽;這也是我的初衷——希望分析插件框架的過程中能幫助深入理解Android Framework。
好了繼續(xù)分析performDestroyActivity,關鍵代碼如下:
| 1 2 3 4 5 | ActivityClientRecord r = mActivities.get(token); // ...略 mInstrumentation.callActivityOnDestroy(r.activity); |
這里通過mActivities拿到了一個ActivityClientRecord,然后直接把這個record里面的Activity交給Instrument類完成了onDestroy的調用。
在我們這個demo的場景下,r.activity是TargetActivity還是StubActivity?按理說,由于我們欺騙了AMS,AMS應該只知道StubActivity的存在,它壓根兒就不知道TargetActivity是什么,為什么它能正確完成對TargetActivity生命周期的回調呢?
一切的秘密在token里面。AMS與ActivityThread之間對于Activity的生命周期的交互,并沒有直接使用Activity對象進行交互,而是使用一個token來標識,這個token是binder對象,因此可以方便地跨進程傳遞。Activity里面有一個成員變量mToken代表的就是它,token可以唯一地標識一個Activity對象,它在Activity的attach方法里面初始化;
在AMS處理Activity的任務棧的時候,使用這個token標記Activity,因此在我們的demo里面,AMS進程里面的token對應的是StubActivity,也就是AMS還在傻乎乎地操作StubActivity(關于這一點,你可以dump出任務棧的信息,可以觀察到dump出的確實是StubActivity)。但是在我們App進程里面,token對應的卻是TargetActivity!因此,在ActivityThread執(zhí)行回調的時候,能正確地回調到TargetActivity相應的方法。
為什么App進程里面,token對應的是TargetActivity呢?
回到代碼,ActivityClientRecord是在mActivities里面取出來的,確實是根據token取;那么這個token是什么時候添加進去的呢?我們看performLaunchActivity就完成明白了:它通過classloader加載了TargetActivity,然后完成一切操作之后把這個activity添加進了mActivities!另外,在這個方法里面我們還能看到對Ativityattach方法的調用,它傳遞給了新創(chuàng)建的Activity一個token對象,而這個token是在ActivityClientRecord構造函數里面初始化的。
至此我們已經可以確認,通過這種方式啟動的Activity有它自己完整而獨立的生命周期!
小節(jié)
本文講述了『啟動一個并沒有在AndroidManifest.xml中顯示聲明的Activity』的解決辦法,我們成功地繞過了Android的這個限制,這個是插件Activity管理技術的基礎;但是要做到啟動一個插件Activity問題遠沒有這么簡單。
首先,在Android中,Activity有不同的啟動模式;我們聲明了一個替身StubActivity,肯定沒有滿足所有的要求;因此,我們需要在AndroidManifest.xml中聲明一系列的有不同launchMode的Activity,還需要完成替身與真正Activity launchMode的匹配過程;這樣才能完成啟動各種類型Activity的需求,關于這一點,在 DroidPlugin 的com.morgoo.droidplugin.stub包下面可以找到。
另外,每啟動一個插件的Activity都需要一個StubActivity,但是AndroidManifest.xml中肯定只能聲明有限個,如果一直startActivity而不finish的話,那么理論上就需要無限個StubActivity;這個問題該如何解決呢?事實上,這個問題在技術上沒有好的解決辦法。但是,如果你的App startActivity了十幾次,而沒有finish任何一個Activity,這樣在Activity的回退棧里面有十幾個Activity,用戶難道按back十幾次回到主頁嗎?有這種需求說明你的產品設計有問題;一個App一級頁面,二級頁面..到五六級的頁面已經影響體驗了,所以,每種LauchMode聲明十個StubActivity絕對能滿足需求了。
最后,在本文所述例子中,TargetActivity與StubActivity存在于同一個Apk,因此系統(tǒng)的ClassLoader能夠成功加載并創(chuàng)建TargetActivity的實例。但是在實際的插件系統(tǒng)中,要啟動的目標Activity肯定存在于一個單獨的文件中,系統(tǒng)默認的ClassLoader無法加載插件中的Activity類——系統(tǒng)壓根兒就不知道要加載的插件在哪,談何加載?因此還有一個很重要的問題需要處理:
我們要完成插件系統(tǒng)中類的加載,這可以通過自定義ClassLoader實現。解決了『啟動沒有在AndroidManifest.xml中顯式聲明的,并且存在于外部文件中的Activity』的問題,插件系統(tǒng)對于Activity的管理才算得上是一個完全體。篇幅所限,欲知后事如何,請聽下回分解!
原文出處: http://weishu.me/2016/03/21/understand-plugin-framework-activity-management/
總結
以上是生活随笔為你收集整理的Android 插件化原理解析——Activity生命周期管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android插件化原理解析——Hook
- 下一篇: Android 插件化原理解析——插件加