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,null,parent); } public PathClassLoader(String dexPath,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,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.进行访问。
具体原理参照:https://www.oudahe.com/p/27137/
使用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(),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,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) ; } }}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
总结以上是内存溢出为你收集整理的亲自动手实现Android App插件化全部内容,希望文章能够帮你解决亲自动手实现Android App插件化所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)