標籤:android 動態載入 外掛程式化 架構
轉載請註明出處,本文來自【 Mr.Simple的部落格 】。
我正在參加部落格之星,點擊這裡投我一票吧,謝謝~ 前言
最近這一兩年,Android App使用外掛程式化技術開發的數量越來越大,其實還是業務地快速膨脹導致,需求越來越多,App越來越臃腫。雖然手機的記憶體空間不斷地的增大,但是太大的安裝包給使用者也造成了心理壓力。於是大家都會想到外掛程式化的開發方式,把App做成一個平台,而不是一個獨立的app。平台上可以整合各種各樣的功能,功能模組也外掛程式的形式添加進來,這些外掛程式不需要安裝,只需要使用者按需下載到某個位置,然後使用的時候動態載入進來。
想法都是好的,但是想實現一個相對比較穩定的動態載入架構還是有點難度的,一些大公司都有成熟的動態載入架構,但是他們並沒有開源出來。好在2014年知名CSDN博主任玉剛開源了一款名為DL的Android動態載入架構,該架構簡單、開源、相容性都較為好,如果有需要使用外掛程式化開發的朋友可以嘗試該架構,另外對該開源項目感興趣的朋友也可以貢獻自己的代碼,地址會在文章最後給出。
對於DL的基本情況和使用,本人就不再贅述,作者本人和參與開發的朋友已經有一些較好的文章,詳情請參考,APK動態載入架構(DL)解析、DL動態載入架構技術文檔、Android 使用動態載入架構DL進行外掛程式化開發。在這裡我就簡單介紹一下DL的基本結構與原理,希望能夠一些需要的朋友提供一些有用的資訊。
基本架構
對於Android的動態載入架構來說最重要和最麻煩的點可能基本上有兩個,第一是apk如何在不安裝的情況下運行起來,並且Activity基本的聲明周期能夠正常調用;第二就是Activity內部能夠以R的形式訪問資源檔。關於載入未安裝的apk和Android的資源載入機制請參考如下兩篇文章,Android動態載入jar、apk的實現、Android源碼分析-資源載入機制。我們針對這兩個問題依次給出DL的解決方案。
載入未安裝apk相對比較簡單,就是通過DexClassLoader將apk檔案載入到虛擬機器中,具體可以參考上文給出的文章。這裡我們主要說一下調用Activity生命週期函數的實現。在DL中,有兩個Proxy類,分別為DLProxyActivity、DLProxyFragmentActivity,這兩個類型分別繼承自Activity和FragmentActivity,他們分別代理整合自DLBasePluginActivity和DLBasePluginFragmentActivity的外掛程式Activity類,DLBasePluginActivity和DLBasePluginFragmentActivity又分別繼承自Activity和FragmentActivity,我去!這個時候是不是有點亂了?且聽我慢慢道來~
按照DL的開發規範,你外掛程式的Activity需要繼承自DLBasePluginActivity或者DLBasePluginFragmentActivity,這兩個類中封裝了一些基本的調用邏輯。這裡我們先暫時不用過多理會,重點是看DLProxyActivity、DLProxyFragmentActivity。DL的機制是這樣的,真正在DL通過Intent啟動的Activity只能是DLProxyActivity、DLProxyFragmentActivity,這兩個Activity是在宿主apk中註冊了的,因此能夠直接通過Intent來啟動。而在兩個Proxy實際上只是一個軀殼,他們會自己的生命週期函數中調用外掛程式Activity對應的生命週期函數。這是就引入了一個關鍵的類,DLProxyImpl,這個類負責解析外掛程式apk的資源、ClassLoader、通過反射載入外掛程式Activity,DLProxyImpl載入了外掛程式Activity之後又會調用Proxy Activity的attach方法將外掛程式Activity執行個體傳遞給Proxy Activity,這樣Proxy Activity就得到了外掛程式Activity的執行個體,然後就能在自己的聲明周期函數中調用外掛程式Activity對應的函數。
DLProxyImpl的launchTargetActivity函數:
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) protected void launchTargetActivity() { try { // mClass就是目標外掛程式Activity的類名 Class<?> localClass = getClassLoader().loadClass(mClass); Constructor<?> localConstructor = localClass.getConstructor(new Class[] {}); // 通過反射構建外掛程式Activity Object instance = localConstructor.newInstance(new Object[] {}); mPluginActivity = (DLPlugin) instance; // 將外掛程式Activity注入給Proxy Activity ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager); // 外掛程式activity也擷取到Proxy的引用 mPluginActivity.attach(mProxyActivity, mPluginPackage); Bundle bundle = new Bundle(); bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL); // 調用外掛程式Activity的onCreate函數,啟動外掛程式Activity mPluginActivity.onCreate(bundle); } catch (Exception e) { e.printStackTrace(); } }
宿主在載入外掛程式apk時會解析該apk的相關資訊,比如它的預設啟動的Activity,上述形式將apk運行起來,那麼在外掛程式apk中的activity跳轉則需要通過DLIntent類將目標activity的包名、類名傳遞給DL架構,DL內部會進行解析以及相應的載入邏輯。
DLProxyActivity核心代碼:
public class DLProxyActivity extends Activity implements DLAttachable { // 外掛程式Activity protected DLPlugin mRemoteActivity; // DLProxyImpl載入外掛程式Activity和資源等 private DLProxyImpl impl = new DLProxyImpl(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Proxy onCreate的時候調用DLProxyImpl的onCreate,載入外掛程式Activity impl.onCreate(getIntent()); } // 載入完外掛程式Activity後將外掛程式Activity傳遞給Proxy Activity @Override public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) { mRemoteActivity = remoteActivity; } // 擷取資源,DLProxyImpl載入了外掛程式apk的資源,通過這裡代理資源操作,這樣外掛程式Activity內部就可以通過R訪問資源檔了 @Override public Resources getResources() { return impl.getResources() == null ? super.getResources() : impl.getResources(); } /*********************** 以下都是代理外掛程式Activity的生命週期函數 ***********************/ @Override protected void onStart() { mRemoteActivity.onStart(); super.onStart(); } @Override protected void onRestart() { mRemoteActivity.onRestart(); super.onRestart(); } @Override protected void onResume() { mRemoteActivity.onResume(); super.onResume(); } @Override protected void onPause() { mRemoteActivity.onPause(); super.onPause(); } @Override protected void onStop() { mRemoteActivity.onStop(); super.onStop(); } @Override protected void onDestroy() { mRemoteActivity.onDestroy(); super.onDestroy(); } // 代碼省略}
下面我們來看另外一個重點,也就是外掛程式apk的資源載入。關於apk的資源載入機制也請參考上面給出的文章,我們直接看代碼。
private DexClassLoader createDexClassLoader(String dexPath) { File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE); dexOutputPath = dexOutputDir.getAbsolutePath(); // 建立ClassLoader DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader()); return loader; } // 根據外掛程式apk的路徑構建AssetManager,將其資源所在的路徑添加到AssetManager中,然後通過AssetManager構建一個Resources對象,這個對象就是外掛程式apk的資來源物件,外掛程式apk內部訪問資源時都是通過這個資來源物件 private AssetManager createAssetManager(String dexPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, dexPath); return assetManager; } catch (Exception e) { e.printStackTrace(); return null; } } private Resources createResources(AssetManager assetManager) { Resources superRes = mContext.getResources(); Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return resources; }
這樣,等於是外掛程式Activity的生命週期交給了Proxy Activity代理,而外掛程式的Activity的啟動和資源訪問則交給了DLProxyImpl代理。這樣,通過兩個代理就啟動了外掛程式Activity,並且資源訪問問題也得到瞭解決。
最後我們通過一張圖來看DL的基本結構。
DLPluginManager載入、管理外掛程式包,DLIntent是外掛程式之間跳轉的資訊載體。Base Plugin Activity是外掛程式Activity的基類,封裝代理操作。Proxy Activity代理外掛程式Activity的生命週期函數,也是外掛程式Activity的外殼。DLProxy負責載入外掛程式Activity以及資源操作。這麼一來,整個動態載入架構就運行起來了,更多的細節有興趣的朋友自己去看源碼吧。
後期特性
1. 支援service、靜態廣播、ContentProvider
bug
1. 外掛程式透明主題的支援
2. 完整activity api的重寫
最後給出DL架構github地址,希望更多的人蔘與到DL架構的開發中來。
我正在參加部落格之星,點擊這裡投我一票吧,謝謝~
Android動態載入架構DL的架構與基本原理解析