国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

親自動手實現Android App插件化

2019-12-12 04:19:28
字體:
來源:轉載
供稿:網友

Android插件化目前國內已經有很多開源的工程了,不過如果不實際開發一遍,很難掌握的很好。

下面是自己從0開始,結合目前開源的項目和博客,動手開發插件化方案。

按照需要插件化主要解決下面的幾種問題:

1. 代碼的加載

(1) 要解決純Java代碼的加載

(2) Android組件加載,如Activity、Service、Broadcast Receiver、ContentProvider,因為它們是有生命周期的,所以要特殊處理

(3) Android Native代碼的加載

(4) Android 特殊控件的處理,如Notification等

2. 資源加載

不同插件的資源如何管理,是公用一套還是插件獨立管理?

因為在Android中訪問資源,都是通過R. 實現的, 

下面就一步步解決上面的問題

1. 純Java代碼的加載

主要就是通過ClassLoader、更改DexElements將插件的路徑添加到原來的數組中。

詳細的分析可以參考我轉載的一篇文章,因為感覺原貼命名和結構有點亂,所以轉載記錄下。

https://my.oschina.net/android520/blog/794715

Android提供DexClassLoader和PathClassLoader,都繼承BaseDexClassLoader,只是構造方法的參數不一樣,即optdex的路徑不一樣,源碼如下

// DexClassLoader.javapublic class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory,  String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); }}// PathClassLoader.javapublic class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath,  ClassLoader parent) { super(dexPath, null, libraryPath, parent); }}

其中,optimizedDirectory是用來存儲opt后的dex目錄,必須是內部存儲路徑。

DexClassLoader可以加載外部的dex或apk,只要opt的路徑通過參數設置一個內部存儲路徑即可。

PathClassLoader只能加載已安裝的apk,因為opt路徑會使用默認的dex路徑,外部的不可以。

下面介紹下如何通過DexClassLoader實現加載Java代碼,參考Nuwa

這種方式類似于熱修復,如果插件和宿主代碼有相互訪問,則需要在打包中使用插樁技術實現。

public static boolean injectDexAtFirst(String dexPath, String dexOptPath) { // 獲取系統的dexElements Object baseDexElements = getDexElements(getPathList(getPathClassLoader())); // 獲取patch的dexElements DexClassLoader patchDexClassLoader = new DexClassLoader(dexPath, dexOptPath, dexPath, getPathClassLoader()); Object patchDexElements = getDexElements(getPathList(patchDexClassLoader)); // 組合最新的dexElements Object allDexElements = combineArray(patchDexElements, baseDexElements); // 將最新的dexElements添加到系統的classLoader中 Object pathList = getPathList(getPathClassLoader()); FieldUtils.writeField(pathList, "dexElements", allDexElements);}public static ClassLoader getPathClassLoader() { return DexUtils.class.getClassLoader();}/** * 反射調用getPathList方法,獲取數據 * @param classLoader * @return * @throws ClassNotFoundException * @throws NoSuchFieldException * @throws IllegalAccessException */public static Object getPathList(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { return FieldUtils.readField(classLoader, "pathList");}/** * 反射調用pathList對象的dexElements數據 * @param pathList * @return * @throws NoSuchFieldException * @throws IllegalAccessException */public static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException { LogUtils.d("Reflect To Get DexElements"); return FieldUtils.readField(pathList, "dexElements");}/** * 拼接dexElements,將patch的dex插入到原來dex的頭部 * @param firstElement * @param secondElement * @return */public static Object combineArray(Object firstElement, Object secondElement) { LogUtils.d("Combine DexElements"); // 取得一個數組的Class對象, 如果對象是數組,getClass只能返回數組類型,而getComponentType可以返回數組的實際類型 Class objTypeClass = firstElement.getClass().getComponentType(); int firstArrayLen = Array.getLength(firstElement); int secondArrayLen = Array.getLength(secondElement); int allArrayLen = firstArrayLen + secondArrayLen; Object allObject = Array.newInstance(objTypeClass, allArrayLen); for (int i = 0; i < allArrayLen; i++) { if (i < firstArrayLen) {  Array.set(allObject, i, Array.get(firstElement, i)); } else {  Array.set(allObject, i, Array.get(secondElement, i - firstArrayLen)); } } return allObject;}

使用上面的方式啟動的Activity,是有生命周期的,應該是使用系統默認的創建Activity方式,而不是自己new Activity對象,所以打開的Activity生命周期正常。

但是上面的方式,必須保證Activity在宿主AndroidManifest.xml中注冊。

2. 下面介紹下如何加載未注冊的Activity功能

Activity的加載原理參考 https://my.oschina.net/android520/blog/795599

主要通過Hook系統的IActivityManager完成

3. 資源加載

資源訪問都是通過R.方式,實際上Android會生成一個0x7f******格式的int常量值,關聯對應的資源。

如果資源有更改,如layout、id、drawable等變化,會重新生成R.java內容,int常量值也會變化。

因為插件中的資源沒有參與宿主程序的資源編譯,所以無法通過R.進行訪問。

具體原理參照://m.survivalescaperooms.com/article/100245.htm

使用addAssetPath方式將插件路徑添加到宿主程序后,因為插件是獨立打包的,所以資源id也是從1開始,而宿主程序也是從1開始,可能會導致插件和宿主資源沖突,系統加載資源時以最新找到的資源為準,所以無法保證界面展示的是宿主的,還是插件的。

針對這種方式,可以在打包時,更改每個插件的資源id生成的范圍,可以參考public.xml介紹。

代碼參考Amigo

public static void loadPatchResources(Context context, String apkPath) throws Exception { AssetManager newAssetManager = AssetManager.class.newInstance(); invokeMethod(newAssetManager, "addAssetPath", apkPath); invokeMethod(newAssetManager, "ensureStringBlocks"); replaceAssetManager(context, newAssetManager);}private static void replaceAssetManager(Context context, AssetManager newAssetManager)  throws Exception { Collection<WeakReference<Resources>> references; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager"); Object resourcesManager = invokeStaticMethod(resourcesManagerClass, "getInstance"); if (getField(resourcesManagerClass, "mActiveResources") != null) {  ArrayMap<?, WeakReference<Resources>> arrayMap =   (ArrayMap) readField(resourcesManager, "mActiveResources", true);  references = arrayMap.values(); } else {  references = (Collection) readField(resourcesManager, "mResourceReferences", true); } } else { HashMap<?, WeakReference<Resources>> map =   (HashMap) readField(ActivityThreadCompat.instance(), "mActiveResources", true); references = map.values(); } AssetManager assetManager = context != null ? context.getAssets() : null; for (WeakReference<Resources> wr : references) { Resources resources = wr.get(); if (resources == null) continue; try {  writeField(resources, "mAssets", newAssetManager);  originalAssetManager = assetManager; } catch (Throwable ignore) {  Object resourceImpl = readField(resources, "mResourcesImpl", true);  writeField(resourceImpl, "mAssets", newAssetManager); } resources.updateConfiguration(resources.getConfiguration(),   resources.getDisplayMetrics()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { for (WeakReference<Resources> wr : references) {  Resources resources = wr.get();  if (resources == null) continue;  // android.util.Pools$SynchronizedPool<TypedArray>  Object typedArrayPool = readField(resources, "mTypedArrayPool", true);  // Clear all the pools  while (invokeMethod(typedArrayPool, "acquire") != null) ; } }}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 鲁山县| 永德县| 施秉县| 汶上县| 宽城| 高阳县| 如皋市| 安义县| 河北区| 昌都县| 扶余县| 繁昌县| 纳雍县| 深泽县| 南陵县| 海伦市| 扎兰屯市| 忻城县| 班戈县| 平潭县| 青浦区| 甘南县| 阳新县| 黑龙江省| 沈阳市| 灵台县| 营山县| 阳泉市| 海丰县| 榆林市| 桃园市| 和林格尔县| 汾西县| 扎囊县| 南丰县| 五原县| 丰城市| 荣成市| 年辖:市辖区| 海丰县| 嵩明县|