1. 程式人生 > >Android外掛化框架使用心得 (使用篇)

Android外掛化框架使用心得 (使用篇)

最近專案中,有些子功能需要按需載入,於是考慮使用外掛化方案實現,看過幾個外掛化方案後,最終選擇了360之前開源的DroidPlugin方案。

在使用中,還是有一些心得體會,網上的原始碼分析,詳細原理分析的文章一經很多(文末會有連結),本篇文章,主要從使用的角度入手,簡單描述下DroidPlugin原理,也記錄下使用中心得體會。

基本原理

閱讀了部分DroidPlugin的原始碼,也看了部分網上的文章和android元件啟動過程的分析。對於此外掛化框架的基本的原理,個人理解如下:

外掛化目的是為了從宿主中,啟動外掛中元件,不是同一個應用,也不是同一個程序,如果按照android原有的機制執行API,中途必然不能執行下去。

為了實現這種特殊的目的,外掛化框架中Hook了大量系統的API, 通過改寫入參,返回值的方式,使得API的行為發生變化,繞過系統部分規則和檢查,完成元件載入,功能實現等。

簡單描述下Hook的思路,顧名思義,就是像鉤子一樣,將程式段中的部分程式碼掛住,之後,改寫程式段的部分行為。

通過Java的動態載入機制,在結合反射,就能實現hook的大致功能。
最終通過實現 InvocationHandler 介面中的 invoke 方法,達到改寫某些系統原生api的行為。而通過反射,可以在執行時,動態改寫部分類或者方法的如參,配合被改寫的api共同實現,繞過系統系統限制,載入外掛的目的。

框架使用心得

在使用DroidPlugin框架過程中,還是有部分心得體會,簡要記錄下,外掛化框架的原理十分複雜,但是如果簡單使用的話,從API呼叫層面來看,其實並不是很複雜。

首先是外掛初始化的過程,在這流程中,主要完成外掛化框架,載入到宿主APP上,安裝了所有的hook等操作。

PluginHelper.getInstance().applicationOnCreate(getBaseContext());
PluginHelper.getInstance().applicationAttachBaseContext(base);

下面這兩個API, 是需要在宿主APP, 啟動元件前需要呼叫的,和框架裡使用了AIDL方式進行互動,所有要使用各種API, 首先要建立服務的連線。

PluginManager.getInstance().isConnected();
PluginManager.getInstance().addServiceConnection(mServiceConnection);

這三個API, 是處理外掛包相關的常見API,安裝的過程,其實是把外掛包的資訊,寫入到外掛化框架中,專門為維護外掛相關的資訊的一段資料結構中,後續的get流程,和刪除流程,實際上都是對這個資料結構作操作。

PluginManager.getInstance().installPackage(item.apkFile, 0);
PluginManager.getInstance().getInstalledPackages(0);
PluginManager.getInstance().deletePackage(item.packageInfo.packageName, 0);

這邊需要特別注意,官方文件中的一個提示,外掛框架中的 manifest 檔案中 provider 的 authorities 一定要記得修改,不然真的會引起衝突。

All provider's authorities value in DroidPlugin's Libraries\DroidPlugin\AndroidManifest.xml default to be com.morgoo.droidplugin_stub_P00, e.g. :

  <provider
          android:name="com.morgoo.droidplugin.stub.ContentProviderStub$StubP00"
          android:authorities="com.morgoo.droidplugin_stub_P00"
          android:exported="false"
          android:label="@string/stub_name_povider" />

You'd better change it to avoid conflict with other instances, e.g.:

  <provider
          android:name="com.morgoo.droidplugin.stub.ContentProviderStub$StubP00"
          android:authorities="com.example.droidplugin_stub_P00"
          android:exported="false"
          android:label="@string/stub_name_povider" />

and change PluginManager.STUB_AUTHORITY_NAME to your value:

  PluginManager.STUB_AUTHORITY_NAME="com.example.droidplugin_stub"