Android插件化快速入門與實例解析(VirtualApk)

集成一個第三方相冊功能,只需集成一個插件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開始,到ActivityManagerServiceActivityThread結(jié)束,啟動一個Activity,流程并不簡單。

InstrumentationexecStartActivity開始啟動,到通過checkStartActivityResult校驗Activity是否在Manifest中聲明,從圖中可以看出,流程還是相當繁瑣的。
  
Activity啟動流程詳見圖片

Activity簡化啟動流程圖

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

Service啟動流程詳見圖片

Service簡化啟動流程圖

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

下圖在插件化實現(xiàn)中起到關(guān)鍵作用

關(guā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住InstrumentationActivityThread等。通過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接管。

Hook Instrumentation 圖1

這里是如何拿到Instrumentation的?

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

Hook Instrumentation 圖2

另外,上方圖1還有設置HandlerCallback的流程,其實就是攔截了ActivityThread中的mH這個Handler的Callback,從【 一、 Activity/Service啟動流程】流程圖可以看到,mH的handleMessage處理很多Activity的啟動狀態(tài)。

如下圖, 是Hook Service的流程,如圖中注釋所示,通過ActivityManagerNativegetDefault,拿到AndroidManagerService(詳見啟動流程圖),而VirtualApk通過自定義ActivityManagerProxy,重新生成了一個IActivityManager,然后注入回AndroidManagerService中,這樣接管了系統(tǒng)啟動、管理service等操作。

Hook Service 圖1

2、加載插件Apk

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

加載Apk插件

此處對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,因為在ActivityTreadperformLaunchActivity中,會調(diào)用InstrumentationnewActivity。

啟動Activity

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

創(chuàng)建Activity

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

屏幕快照 2017-07-15 上午12.36.21.png

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為例。

ActivityManagerProxy

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

啟動流程
轉(zhuǎn)化流程

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

啟動真正的目標service

自此Activity和Service都成功啟動了,是不是對插件化有了不一樣的了解?

5、AndroidStub

容許這里插入這一塊,安利下Virtual中的AndroidStub模塊,如下圖

AndroidStub

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

但是如下圖,VirtualApk通過AndroidStub,模擬源碼創(chuàng)建了如ActivityThread類,這里你就可以如圖正常使用ActivityThread了,而AndroidStub中的ActivityThread,其實只是定義了和原碼中一摸一樣的方法,并沒有其他實現(xiàn)。

因為CoreLibrary依賴AndroidStub使用的是provided ,因為provided依賴是不打包依賴包,而是運行時提供,所以成功欺騙了編輯器,以此提高了性能。很神奇吧?

ActivityThread
CoreLibrary依賴AndroidStub

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

VirtualApk:https://github.com/didi/VirtualAPK

個人github:https://github.com/CarGuo

終于結(jié)束了,點個贊不?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容