集成一個第三方相冊功能,只需集成一個插件APK到項目中,無需集成額外代碼,并且支持隨時更新相冊功能,無需發(fā)布版本更新,無需AndroidManifest中聲明四大組件,這就是插件化。
插件化可利用性很廣,但事實上大多數(shù)開發(fā)者,因為未知而放棄使用,所以本篇將深入淺出帶你了解插件化原理,從基礎到實現(xiàn),插件化不再是你陌生的領(lǐng)域。
本篇主要涉及到:
- 一、Activity/Service的啟動原理和流程。
- 二、插件化實現(xiàn)原理。
- 三、DiDi開源VirtualApk源碼解析(Activity/Service)。
- VirtualApk優(yōu)化反射帶來損耗的小技巧。
ps:如果你對此(一、二)已經(jīng)十分了解,請自行略過。
一、 Activity/Service啟動流程
Activity和Service的啟動流程十分復雜,一個startActivity的背后是無數(shù)的邏輯實現(xiàn),這里不深入討論,但需要理解這個流程,因為插件化是在流程上動手腳,以達到繞過系統(tǒng)限制的目的。
下方圖片是Activity啟動的簡化流程,可以看到,從Instrumentation開始,到ActivityManagerService和ActivityThread結(jié)束,啟動一個Activity,流程并不簡單。
在Instrumentation在execStartActivity開始啟動,到通過checkStartActivityResult校驗Activity是否在Manifest中聲明,從圖中可以看出,流程還是相當繁瑣的。
(Activity啟動流程詳見圖片)

下方圖片是Service啟動的簡化流程,同樣可以看到,ActivityManagerService和ActivityThread同樣起到了關(guān)鍵性的作用。插件化的關(guān)鍵,就在于Instrumentation、ActivityManagerService和ActivityThread。
(Service啟動流程詳見圖片)

為了更好理解插件化,如下圖,是幾個關(guān)鍵類的對應關(guān)系與實際作用,有點S/C的味道。它們的通信是通過IBinder,實現(xiàn)進程通信的,可以看出,啟動Activity和Service,ActivityThread和ActivityManagerService是關(guān)鍵,并且上面我們知道,Instrumentation是Activity的啟動入口,所以實現(xiàn)插件化的流程,便可以在這些關(guān)鍵類上開刀。
(下圖在插件化實現(xiàn)中起到關(guān)鍵作用)

提前說明
好了,帶了一波基礎姿勢的節(jié)奏,稍安勿躁,先這里在補充幾個概念,如果你已經(jīng)習得,可以跳過:
Hook:攔截某個內(nèi)部流程,在其中做某些修改,以實現(xiàn)自己的邏輯。
Instrumentation:每個Activity都有一個
Instrumentation對象,它是在Activity啟動是被賦予的Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity();這便是startActivityForResult的啟動,同時返回啟動結(jié)果。占坑:聲明一個不存在的Activity,如:
<activity android:name=".A$1" android:launchMode="standard"/>
,這樣啟動.A$1這個Activity可以欺騙系統(tǒng)檢測,然后再將插件Activity注入到.A$1這個坑位中。
二、插件化實現(xiàn)原理。
插件化的實現(xiàn)就是在于加載、繞過系統(tǒng)限制、啟動和管理插件等過程。按照VirtualApk的實現(xiàn),大致流程為:
1、初始化Hook住Instrumentation和ActivityThread等。通過PackageParser(插件apk包信息)、AssetManager(資源文件Resources)、ClassLoader等加載一個Apk插件。
2、啟動插件Activity:提前在主APP中占有坑位,通過替換Intent中的targetActivity,打開占坑聲明的A$Activity,然后繞過AndroidManifest檢測,再攔截newActivity方法中恢復targetActivity。
3、啟動插件Service:通過啟動一個代理Service統(tǒng)一管理,攔截所有Service方法,修改為startService到代理Service,在代理Service的onStartCommond統(tǒng)一管理,創(chuàng)建/停止目標service 。
三、VirtualApk源碼解析
1、初始化
初始化過程中,VirtualApk 創(chuàng)建了PluginManager ,并且hook住了Instrumentation和SystemService,如下圖所示。

如下圖所示,VrutalApk通過Instrumentation創(chuàng)建了一個VAInstrumentation對象,VAInstrumentation是一個繼承Instrumentation的類。
將VAInstrumentation反射插入到ActivityThread中,這樣系統(tǒng)接下來關(guān)于Instrumentation的操作,就會回到VAInstrumentation中,被VrtualApk接管。

這里是如何拿到Instrumentation的?
因為在ActivityThread內(nèi)部有一個sCurrentActivityThread靜態(tài)變量。如下圖,通過反射sCurrentActivityThread我們可以獲取當前ActivityThread,而ActivityThread的公開方法getInstrumentation即可拿到Instrumentation對象。

另外,上方圖1還有設置HandlerCallback的流程,其實就是攔截了ActivityThread中的mH這個Handler的Callback,從【 一、 Activity/Service啟動流程】流程圖可以看到,mH的handleMessage處理很多Activity的啟動狀態(tài)。
如下圖, 是Hook Service的流程,如圖中注釋所示,通過ActivityManagerNative的getDefault,拿到AndroidManagerService(詳見啟動流程圖),而VirtualApk通過自定義ActivityManagerProxy,重新生成了一個IActivityManager,然后注入回AndroidManagerService中,這樣接管了系統(tǒng)啟動、管理service等操作。

2、加載插件Apk
加載插件APK是通過PluginManager的loadPlugin方法,如下圖所示,此處就是將apk拆開,解析,讀取,加載,組裝為LoadedPlugin并保存,以方便后面管理與使用。

此處對Apk進行了復雜的解析、加載、合并等操作,大致流程如下:
- 解析apk的相關(guān)包信息、判斷是否加載過apk。
- 創(chuàng)建一些插件工具類。
- 通過
AssetManager創(chuàng)建Resource對象,平臺用AssetManager創(chuàng)建出Resource,判斷是否和宿主Apk合并資源。 - ClassLoader 根據(jù)插件APK路徑創(chuàng)建loader,判斷是否合并loader中的dex,合并nativeLIbraryDirectories。
- 將so復制到mNativeLibDir路徑。
- 保存Instrumentation、Activities、Services、Providers , 注冊Broadcast等。
- 創(chuàng)建出Apk的Application,并call Application onCreate。
3、啟動插件Activity
那么是時候啟動插件Activity了。通過startActivity便可以啟動。從上面的流程我們知道啟動是從Instrumentation.execStartActivity();開始的,而系統(tǒng)的Instrumentation已經(jīng)被VAInstrumentation替換,其中VAInstrumentation重寫了幾個關(guān)鍵方法:
- execStartActivity:入口。
- newActivity:創(chuàng)建。
- callActivityOnCreate:通知。
- handleMessage:處理。
沒錯,如下圖,在啟動Activity的入口處,VirtualApk攔截了請求,然后根據(jù)Intent的參數(shù),去匹配plugin中的Activity坑位,之后替換Intent中的Activity,以此來達到欺騙系統(tǒng)的效果。
但是,因為這個Activity的對象了實際上并不存在,最終我們需要啟動的是實現(xiàn)了的targetActivity,所以需要攔截Instrumentation的第二個方法newActivity,因為在ActivityTread的performLaunchActivity中,會調(diào)用Instrumentation的 newActivity。

在newActivity中,如下圖,類沒有找到時(坑位類肯定找不到啦),那么就去獲取原本保存在Intent的目標Activity,然后調(diào)用創(chuàng)建VAInstrumentation時保存的Instrumentation(mBase)去創(chuàng)建Activity。

Activity雖然創(chuàng)建好了,但是它對應的資源和context都還不對,所以我們需要在Activity的OnCreate之前完成好Resource的注入。前面加載Apk時,這些資源都保存在Plugin中。所以我們攔截callActivityOnCreate方法,如下圖,將Activity的Context、Application、Reource,都替換成Plugin中對應的對象。一個Activity就這樣繞過AndroidManifest啟動起來了。

4、啟動插件Service
startService啟動Service時,還記得上面我們通過ActivityManagerProxy生成IActivityManager嗎?它主要攔截了Service相關(guān)啟動和停止等方法,然后將其都轉(zhuǎn)化為對應的startService方法,指向代理Service。因為startService方法的特性,他們最終都會在代理Service的onStartCommand中被統(tǒng)一處理。
如下圖,是ActivityManagerProxy,其中invoke攔截了所有相關(guān)的服務請求,并做了轉(zhuǎn)化處理,下面以startService為例。

startService這里,主要便是提取原本目標service信息,然后轉(zhuǎn)化為代理Service,發(fā)送到代理Service,下方圖片為啟動流程和轉(zhuǎn)化流程。


如下圖,在代理service中,根據(jù)請求類型,代理service會通過classLoader加載來創(chuàng)建service,并操作其attach、onCreate、onStartCommand等,讓service工作起來。

自此Activity和Service都成功啟動了,是不是對插件化有了不一樣的了解?
5、AndroidStub
容許這里插入這一塊,安利下Virtual中的AndroidStub模塊,如下圖

因為都用反射很浪費性能,所以有了AndroidStub,它是用來欺騙編譯器的。正常情況下你想操作ActivityThread就會出現(xiàn)如下圖情況,因為它是一個@hide類,這時候除了反射得到ActivityThread,你還需要再反射需要執(zhí)著方法才能執(zhí)行,這在一定程度會損耗一些性能。

但是如下圖,VirtualApk通過AndroidStub,模擬源碼創(chuàng)建了如ActivityThread類,這里你就可以如圖正常使用ActivityThread了,而AndroidStub中的ActivityThread,其實只是定義了和原碼中一摸一樣的方法,并沒有其他實現(xiàn)。
因為CoreLibrary依賴AndroidStub使用的是provided ,因為provided依賴是不打包依賴包,而是運行時提供,所以成功欺騙了編輯器,以此提高了性能。很神奇吧?


終于結(jié)束了,如果你看到了這里,相信你是一個很有耐心的同志!當然插件化還是其他實現(xiàn)方式,如Replugin,只Hook住了ClassLoader,流程更加復雜,如有什么建議和疑問,歡迎留言討論。
VirtualApk:https://github.com/didi/VirtualAPK
個人github:https://github.com/CarGuo
