親自動(dòng)手實(shí)現(xiàn)Android App插件化
Android插件化目前國(guó)內(nèi)已經(jīng)有很多開(kāi)源的工程了,不過(guò)如果不實(shí)際開(kāi)發(fā)一遍,很難掌握的很好。
下面是自己從0開(kāi)始,結(jié)合目前開(kāi)源的項(xiàng)目和博客,動(dòng)手開(kāi)發(fā)插件化方案。
按照需要插件化主要解決下面的幾種問(wèn)題:
1. 代碼的加載
(1) 要解決純Java代碼的加載
(2) Android組件加載,如Activity、Service、Broadcast Receiver、ContentProvider,因?yàn)樗鼈兪怯猩芷诘模砸厥馓幚?/p>
(3) Android Native代碼的加載
(4) Android 特殊控件的處理,如Notification等
2. 資源加載
不同插件的資源如何管理,是公用一套還是插件獨(dú)立管理?
因?yàn)樵贏ndroid中訪問(wèn)資源,都是通過(guò)R. 實(shí)現(xiàn)的,
下面就一步步解決上面的問(wèn)題
1. 純Java代碼的加載
主要就是通過(guò)ClassLoader、更改DexElements將插件的路徑添加到原來(lái)的數(shù)組中。
詳細(xì)的分析可以參考我轉(zhuǎn)載的一篇文章,因?yàn)楦杏X(jué)原貼命名和結(jié)構(gòu)有點(diǎn)亂,所以轉(zhuǎn)載記錄下。
https://my.oschina.net/android520/blog/794715
Android提供DexClassLoader和PathClassLoader,都繼承BaseDexClassLoader,只是構(gòu)造方法的參數(shù)不一樣,即optdex的路徑不一樣,源碼如下
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
// PathClassLoader.java
public 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是用來(lái)存儲(chǔ)opt后的dex目錄,必須是內(nèi)部存儲(chǔ)路徑。
DexClassLoader可以加載外部的dex或apk,只要opt的路徑通過(guò)參數(shù)設(shè)置一個(gè)內(nèi)部存儲(chǔ)路徑即可。
PathClassLoader只能加載已安裝的apk,因?yàn)閛pt路徑會(huì)使用默認(rèn)的dex路徑,外部的不可以。
下面介紹下如何通過(guò)DexClassLoader實(shí)現(xiàn)加載Java代碼,參考Nuwa
這種方式類似于熱修復(fù),如果插件和宿主代碼有相互訪問(wèn),則需要在打包中使用插樁技術(shù)實(shí)現(xiàn)。
public static boolean injectDexAtFirst(String dexPath, String dexOptPath) {
// 獲取系統(tǒng)的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添加到系統(tǒng)的classLoader中
Object pathList = getPathList(getPathClassLoader());
FieldUtils.writeField(pathList, "dexElements", allDexElements);
}
public static ClassLoader getPathClassLoader() {
return DexUtils.class.getClassLoader();
}
/**
* 反射調(diào)用getPathList方法,獲取數(shù)據(jù)
* @param classLoader
* @return
* @throws ClassNotFoundException
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static Object getPathList(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return FieldUtils.readField(classLoader, "pathList");
}
/**
* 反射調(diào)用pathList對(duì)象的dexElements數(shù)據(jù)
* @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插入到原來(lái)dex的頭部
* @param firstElement
* @param secondElement
* @return
*/
public static Object combineArray(Object firstElement, Object secondElement) {
LogUtils.d("Combine DexElements");
// 取得一個(gè)數(shù)組的Class對(duì)象, 如果對(duì)象是數(shù)組,getClass只能返回?cái)?shù)組類型,而getComponentType可以返回?cái)?shù)組的實(shí)際類型
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;
}
使用上面的方式啟動(dòng)的Activity,是有生命周期的,應(yīng)該是使用系統(tǒng)默認(rèn)的創(chuàng)建Activity方式,而不是自己new Activity對(duì)象,所以打開(kāi)的Activity生命周期正常。
但是上面的方式,必須保證Activity在宿主AndroidManifest.xml中注冊(cè)。
2. 下面介紹下如何加載未注冊(cè)的Activity功能
Activity的加載原理參考 https://my.oschina.net/android520/blog/795599
主要通過(guò)Hook系統(tǒng)的IActivityManager完成
3. 資源加載
資源訪問(wèn)都是通過(guò)R.方式,實(shí)際上Android會(huì)生成一個(gè)0x7f******格式的int常量值,關(guān)聯(lián)對(duì)應(yīng)的資源。
如果資源有更改,如layout、id、drawable等變化,會(huì)重新生成R.java內(nèi)容,int常量值也會(huì)變化。
因?yàn)椴寮械馁Y源沒(méi)有參與宿主程序的資源編譯,所以無(wú)法通過(guò)R.進(jìn)行訪問(wèn)。
具體原理參照:http://www.dhdzp.com/article/100245.htm
使用addAssetPath方式將插件路徑添加到宿主程序后,因?yàn)椴寮仟?dú)立打包的,所以資源id也是從1開(kāi)始,而宿主程序也是從1開(kāi)始,可能會(huì)導(dǎo)致插件和宿主資源沖突,系統(tǒng)加載資源時(shí)以最新找到的資源為準(zhǔn),所以無(wú)法保證界面展示的是宿主的,還是插件的。
針對(duì)這種方式,可以在打包時(shí),更改每個(gè)插件的資源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) ;
}
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android開(kāi)發(fā)組件化架構(gòu)設(shè)計(jì)原理到實(shí)戰(zhàn)
- android module解耦組件化總體概述(推薦)
- Android組件化開(kāi)發(fā)路由的設(shè)計(jì)實(shí)踐
- 淺談android組件化之ARouter簡(jiǎn)單使用
- 詳解Android業(yè)務(wù)組件化之URL Schema使用
- 淺談Android插件化
- Android 插件化處理方案詳解
- Android插件化-RePlugin項(xiàng)目集成與使用詳解
- Android插件化之資源動(dòng)態(tài)加載
- Android組件化、插件化詳細(xì)講解
相關(guān)文章
JS實(shí)現(xiàn)點(diǎn)擊參數(shù)面板按鈕顯示或隱藏?cái)?shù)據(jù)
本文主要介紹JS實(shí)現(xiàn)點(diǎn)擊參數(shù)面板按鈕顯示或隱藏?cái)?shù)據(jù)的方法,具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
Android實(shí)現(xiàn)實(shí)時(shí)滑動(dòng)ViewPager的2種方式
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)實(shí)時(shí)滑動(dòng)ViewPager的2種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android本地搜索業(yè)務(wù)優(yōu)化方案
這篇文章主要為大家介紹了Android本地搜索業(yè)務(wù)優(yōu)化方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Android實(shí)現(xiàn)側(cè)滑只需一步
這篇文章主要介紹了Android實(shí)現(xiàn)側(cè)滑只需一步,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Android中簡(jiǎn)單的電話管理與短信管理App編寫(xiě)實(shí)例
這篇文章主要介紹了Android中簡(jiǎn)單的電話管理與短信管理App編寫(xiě)實(shí)例,包括監(jiān)聽(tīng)電話的呼叫狀態(tài)以及短信群發(fā)聯(lián)系人選擇等基本功能的實(shí)現(xiàn),代碼突出要點(diǎn),需要的朋友可以參考下2016-04-04
Android編程中Activity的四種啟動(dòng)模式
這篇文章主要介紹了Android編程中Activity的四種啟動(dòng)模式,較為詳細(xì)的分析了Activity四種啟動(dòng)模式的原理與功能,需要的朋友可以參考下2016-04-04
android完美實(shí)現(xiàn) 拍照 選擇圖片 剪裁等代碼分享
本文給大家分享了2個(gè)安卓實(shí)現(xiàn)實(shí)現(xiàn) 拍照 選擇圖片 剪裁等的代碼,都是從正式項(xiàng)目中提取出來(lái)了,非常實(shí)用,有需要的小伙伴可以參考下。2016-01-01
Android開(kāi)發(fā)中解析xml文件XmlUtils工具類與用法示例
這篇文章主要介紹了Android開(kāi)發(fā)中解析xml文件XmlUtils工具類與用法,結(jié)合實(shí)例形式分析了Android開(kāi)發(fā)中解析xml文件工具類定義與相關(guān)使用技巧,需要的朋友可以參考下2018-01-01
Android實(shí)現(xiàn)透明動(dòng)畫(huà)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)透明動(dòng)畫(huà),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05

