動態載入APK原理分享
轉自:http://blog.csdn.net/hkxxx/article/details/42194387
(一) 綜述
隨著智慧手機硬體效能的逐步提升,移動應用也做的越來越複雜,android平臺上應用的apk包體積也越來越大,然後同類產品開始比拼誰的體積小,實現方案呢,然後很容易想到"外掛化",也就是說可以釋出核心很小的產品,隨著新增功能的需求而動態下載功能模組,促使外掛化的另一個動機是App應用固有的問題,那就是很多元件需要註冊,更新功能的話不能像Web應用那樣可在使用者無察覺的情況下通過升級伺服器而方便升級,只能彈出個框讓使用者重新下載整個程式包,然後調取系統安裝流程。被載入的apk稱之為外掛,因為機制類似於生物學的"寄生",載入了外掛的應用也被稱為宿主。
往往不是所有的apk都可作為外掛被載入,往往需要遵循一定的"開發規範",還需要外掛專案引入某種api類庫,業界通常都是這麼做的。
這裡介紹一種無須規範限制的動態載入解決方案,外掛不需要依賴任何API,這也是本人突發異想,靈感所致。
(二)功能介紹
特點:
- 外掛為普通apk,無須依賴任何jar
- Activity生命週期由系統自己管理
- 使用簡單,只需要瞭解一個類PluginManager的兩個方法
- 啟動Activity的效率高
- 不修改外掛,被載入的外掛仍然可以獨立安裝。
功能點:
- 可載入任意apk中的 Activity (包括子類 ActionBarActivity 、FragmentActivity)的派生類(不包括違反限制條件的Activity)
- 支援外掛自定義Application
- 支援外掛Apk中的Activity跳轉到別的Activity(外掛內部的或系統的,外部已安裝apk的,甚至是別的外掛中的),也沒有任何限制
- 支援Activity設定主題(與系統的主題應用規則一樣,如果Activity沒指定Theme,但所在Application指定了Theme,則使用Application的Theme)
- 初步支援.so
- 支援外掛使用 SharedPreference 或 SQLite資料庫(尚未完善)
(ps:
第一個外掛程式碼來自 https://github.com/viacheslavtitov/NDKBegining
作者是個老外,不過也比較粗心,要正常執行你需要在sd卡下建立目錄:FFMPEG
第二個外掛程式碼來自這篇博文:http://blog.csdn.net/caihanyuan/article/details/7367351
第三個外掛程式碼來自大名鼎鼎的 PullToRefresh :
https://github.com/johannilsson/android-pulltorefresh
第6第7個外掛程式碼是自測專案,分別測試ActionBarActivity和Activity基本載入和跳轉
其他apk還沒能正常載入,框架還在不斷完善中,不過騰訊的開心消消樂可以幫助記錄crashlog,這點真不錯~
)
將要支援的特性:
PackageManager service 等等,詳情都列在開源專案android-pluginmgr
https://github.com/houkx/android-pluginmgr/tree/experiment/android-pluginmgr下的TODO檔案中
(ps:修正專案主頁README描述的限制條件1,目前已經沒有這個限制了,在外掛中呼叫context.getPackageName()返回外掛的清單檔案中宣告的包名)
限制條件(永不支援的):
- 外掛apk中不能假定自己已經安裝,以及由此造成的影響,比如認為applicationInfo.dataDir==/data/data/packageName
- 不能依賴清單檔案中的程式宣告,被載入的apk以及裡面的任何元件目前都在同一個程式管理。
- 外掛中的許可權,無法動態註冊,外掛中的許可權都得在宿主中註冊(暫無解決方案)
(三) 實現
動態載入需要處理很多問題,雖然有很多問題,但是核心問題就是載入Activity,因為Activity是可見的,人們對可以看到的東西總是那麼重視,視覺資訊占人所處理資訊的90%以上。Activity如何調起來?資源的載入等等已經有大牛的文章介紹汗牛充棟了,我本菜鳥,不再贅述。
目前Activity的載入或許有很多處理方式,但是可以分為兩種:一是自己new 二是系統new 。很多動態載入框架基於第一種方式。我這個方案基於第二種
,既然要系統new,就要系統自己可以找到相應的Activity. 由於Activity需要在清單檔案註冊了才能使用,所以要註冊Activity,但是如何註冊呢?
我在網上看到有人用極端的方式:外掛裡的所有Activiy都在宿主裡註冊,既然宿主總要修改升級,何必要外掛呢,這已經違背了動態載入的初衷:不修改框架而動態擴充套件功能更多的是這麼做,註冊一個Activity基類,供外掛中的Activity繼承,在這個基類裡做動態載入的核心邏輯,這就要求外掛必須依賴某種API類庫。
我的方案通俗的說是這樣,依賴倒轉,不讓外掛依賴框架API,而是反過來,自動生成一個Activity類依賴(繼承)外掛中的Activity,這個自動生成的類就叫PluginActivity
並且宣告在框架的清單檔案中,如下:
<activity name="androidx.pluginmgr.PluginActivity" />
聰明的讀者會想,等一下,外掛裡面Activity可不止一個,你就註冊一個?
是的,就一個,自動生成的Activity類名都是androidx.pluginmgr.PluginActivity,不過放在不同的檔案中,最簡單的對映,原始Activity類名.dex檔案中儲存對應的子類:PluginActivity
其實也是偷樑換柱了,如果你想啟動外掛裡的Activity,如com.test.MyPlugActivity, 我就把啟動目標修改為androidx.pluginmgr.PluginActivity類,
然後從com.test.MyPlugActivity.dex檔案中找到 public class androidx.pluginmgr.PluginActivity extends com.test.MyPlugActivity{....}
以啟動SthActivity為例:
好了,核心思想已經表達清楚了,下面介紹如何讓系統按你說的路徑去找類檔案,這涉及到類載入器。自定義類載入器比較簡單,繼承java.lang.ClassLoader即可.
在我的開源專案原始碼中對應的類是 FrameworkClassLoader, PluginManager初始化時就去修改Application的類載入器,替換為 FrameworkClassLoader.
FrameworkClassLoader 其實不幹什麼實際載入工作,只是分發任務:
public Class loadClass(String className){
if(當前上下文外掛不為空) {
if( className 是 PluginActivity){
找到當前實際要載入的原始 Activity
return 使用外掛對應的 ActivityClassLoader 從 (自動生成的)原始Activity類名.dex 檔案 載入PluginActivity
}else{
return 使用對應的 PluginClassLoader 載入普通類
}
}else{
return super.loadClass()//即委派給宿主Application的原始類載入器載入
}
}
其中, PluginClassLoader 是一個DexClassLoader, parent 指向 FrameworkClassLoader,
ActivityClassLoader 也是一個DexClassLoader, parent 指向 PluginClassLoader
插圖2(類載入器結構圖):
- package androidx.pluginmgr;
- import android.util.Log;
- /**
- * 框架類載入器(Application 的 classLoder被替換成此類的例項)
- *
- *
- */
- class FrameworkClassLoader extends ClassLoader {
- private String[] plugIdAndActname;//代表外掛上下文
- public FrameworkClassLoader(ClassLoader parent) {
- super(parent);
- }
- //在外部或外掛內部的 startActivity 時呼叫這個方法設定外掛上下文
- String newActivityClassName(String plugId, String actName) {
- plugIdAndActname = new String[] { plugId, actName };
- return ActivityOverider.targetClassName;//targetClassName即宿主manifest配置的androidx.pluginmgr.PluginActivity
- }
- protected Class<?> loadClass(String className, boolean resolv)
- throws ClassNotFoundException {
- Log.i("cl", "loadClass: " + className);
- String[] plugIdAndActname = this.plugIdAndActname;
- Log.i("cl", "plugIdAndActname = " + java.util.Arrays.toString(plugIdAndActname));
- if (plugIdAndActname != null) {
- String pluginId = plugIdAndActname[0];
- PlugInfo plugin = PluginManager.getInstance().getPluginById(
- pluginId);
- Log.i("cl", "plugin = " + plugin);
- if (plugin != null) {
- try {
- if (className.equals(ActivityOverider.targetClassName)) {
- String actClassName = plugIdAndActname[1];//被(繼承)代理的Activity類名
- return plugin.getClassLoader().loadActivityClass(
- actClassName);// 載入動態生成的Activity類
- }else{
- return plugin.getClassLoader().loadClass(className);//載入普通類
- }
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- return super.loadClass(className, resolv);
- }
- }
到此為止,一個動態載入框架的核心雛形已經有了,但是還有許多細節待完善。
------------------------------------------------------------------------------------------------------------------------
附加:另外,關於動態生成類,對於davikvm環境,我所知道的工具有asmdex和dexmaker,我在專案中選用的是dexmaker
用什麼不是重點,看生成的程式碼吧:(ActivityOverider 類負責與自動生成的Activity類互動)
- package androidx.pluginmgr;
- import android.content.ComponentName;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.content.res.AssetManager;
- import android.content.res.Resources;
- import android.os.Bundle;
- import androidplugdemo.SthActivity;
- public final class PluginActivity
- extends SthActivity
- {
- private static final String _pluginId = "activityTest_v1";
- private AssetManager mAssertManager;
- private Resources mResources;
- public boolean bindService(Intent paramIntent, ServiceConnection paramServiceConnection, int paramInt)
- {
- return ActivityOverider.overrideBindService(this, _pluginId, paramIntent, paramServiceConnection, paramInt);
- }
- public AssetManager getAssets()
- {
- AssetManager localAssetManager = this.mAssertManager;
- if (localAssetManager == null) {
- localAssetManager = super.getAssets();
- }
- return localAssetManager;
- }
- public Resources getResources()
- {
- Resources localResources = this.mResources;
- if (localResources == null) {
- localResources = super.getResources();
- }
- return localResources;
- }
- public void onBackPressed()
- {
- if (ActivityOverider.overrideOnbackPressed(this, _pluginId)) {
- super.onBackPressed();
- }
- }
- protected void onCreate(Bundle paramBundle)
- {
- String str = _pluginId;
- AssetManager localAssetManager = ActivityOverider.getAssetManager(str, this);
- this.mAssertManager = localAssetManager;
- Resources localResources = super.getResources();
- this.mResources = new Resources(localAssetManager, localResources.getDisplayMetrics(), localResources.getConfiguration());
- ActivityOverider.callback_onCreate(str, this);
- super.onCreate(paramBundle);
- }
- protected void onDestroy()
- {
- String str = _pluginId;
- super.onDestroy();
- ActivityOverider.callback_onDestroy(str, this);
- }
- protected void onPause()
- {
- String str = _pluginId;
- super.onPause();
- ActivityOverider.callback_onPause(str, this);
- }
- protected void onRestart()
- {
- String str = _pluginId;
- super.onRestart();
- ActivityOverider.callback_onRestart(str, this);
- }
- protected void onResume()
- {
- String str = _pluginId;
- super.onResume();
- ActivityOverider.callback_onResume(str, this);
- }
- protected void onStart()
- {
- String str = _pluginId;
- super.onStart();
- ActivityOverider.callback_onStart(str, this);
- }
- protected void onStop()
- {
- String str = _pluginId;
- super.onStop();
- ActivityOverider.callback_onStop(str, this);
- }
- public void startActivityForResult(Intent paramIntent, int paramInt, Bundle paramBundle)
- {
- super.startActivityForResult(ActivityOverider.overrideStartActivityForResult(this, _pluginId, paramIntent, paramInt, paramBundle), paramInt, paramBundle);
- }
- public ComponentName startService(Intent paramIntent)
- {
- return ActivityOverider.overrideStartService(this, _pluginId, paramIntent);
- }
- public boolean stopService(Intent paramIntent)
- {
- return ActivityOverider.overrideStopService(this, _pluginId, paramIntent);
- }
- public void unbindService(ServiceConnection paramServiceConnection)
- {
- ActivityOverider.overrideUnbindService(this, _pluginId, paramServiceConnection);
- }
- }
相關文章
- DLL動態庫動態載入
- 動態載入UserControl
- Apk_動態除錯方案APK除錯
- python動態載入(三)Python
- vue 動態載入元件Vue元件
- Java動態載入類Java
- goloader - golang動態載入Golang
- QLibrary 載入動態庫
- 滾動載入圖片(懶載入)實現原理
- ListView動態載入資料View
- 指令碼的動態載入指令碼
- 使用dlopen載入動態庫
- echarts遷移圖動態載入Echarts
- OrchardCore 如何動態載入模組?
- Android-動態載入外掛資源,皮膚包的實現原理Android
- 徹底搞懂Composer自動載入原理
- 位元組碼技術---------動態代理,lombok外掛底層原理。類載入器Lombok
- Android native層動態載入so庫Android
- 動態載入的一些坑
- vue如何動態載入本地圖片Vue地圖
- javascript如何動態載入js檔案JavaScriptJS
- 啟動優化之動態庫延遲載入優化
- 載入動態連結庫——dlopen dlsym dlclose
- vue後臺管理之動態載入路由Vue路由
- SyntaxHighlighter 頁面動態js載入方式整理JS
- Drools與動態載入規則檔案
- 移動端無限滾動載入 js實現原理JS
- 效能優化 (八) APK 加固之動態替換 Application優化APKAPP
- 按需載入原理分析
- vue 動態選單以及動態路由載入、重新整理採的坑Vue路由
- jQuery 動態載入下拉框選項(Django)jQueryDjango
- Jquery Datatables (2) 動態載入資料型別jQuery資料型別
- ElementUI級聯選擇器動態載入DemoUI
- Unity3D動態載入FBX檔案Unity3D
- 優雅的實現動態載入 css、jsCSSJS
- Protobuf 動態載入 .proto 檔案並操作 Message
- JavaScript系列:動態建立iframe並載入頁面JavaScript
- 藉助 Webpack 靜態分析能力實現程式碼動態載入Web
- apk加殼加密工具(apk protect) v1.0下載APK加密