android插件化(一)

终于完成公司的项目。空出时间回顾一下插件化。

插件化主要用来解决的问题:
1.app的模块功能越来越多,体积越来越大
2.模块之间的耦合越来越高,协同开发成本越来越大
3.方法数超过65535
4.应用之间的相互调用

但是插件化维护成本很高,因为每出一个android版本,都要去适配一下。所以一般小型企业不太会搞这个玩意。

市面上的插件化开源框架
在这里插入图片描述

插件化主要是,启动插件app,调用插件的类的方法,和插件的资源。
分为三类,普通类,四大组件,和资源文件。
这里先说普通类的加载。

先上效果图
在这里插入图片描述
这是正常的toast。
当我在sdcard目录下加入插件apk

在这里插入图片描述

主要通过类加载来实现上面的功能。使用反射来实现(反射在调用过程中会有一些缺点1.会产生大量的临时对象,所以为频繁的gc。2.还有检查可见性。3.当反射达到一定的数量的时候,会生成字节码,并且没有优化,所以会耗时。4.会进行一些类型转换)

类加载器的关系
在这里插入图片描述

PathClassLoader和DexClassLoader的区别
在8.0之前。他们二者唯一的区别就是第二参数optimizedDirectory,这个参数的含义是生成odex(优化后的dex)存放的路径

翻阅源码

public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
}

 public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

在8.0之后就完全一样了

public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }


public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }

BootClassLoader和PathClassLoder的区别。我们可以自己测试一下
创建一个TestActivity,加入如下代码

public void testClassLoader(){
        ClassLoader classLoader = getClassLoader();
        while (classLoader!=null){
            Log.e(TAG, "classLoader: "+classLoader );
            classLoader = classLoader.getParent();
        }
}

在这里插入图片描述
可以看到当前类的类加载器是PathClassLoader.
而parent是BootClassLoader。

(这里的parent并不是父类,可以想像成一个链表,parent只是Classloder类的成员变量。这个要注意)

PathClassLoader用来加载自定义以及第三方开源框架的类。
BootClassLoader用来家在sdk中的类。
就像上方TestActivity的类加载器为PathClassLoder
而TestActivity继承了Activity,所以Activity的类加载器为BootActivity

说到类加载不得不说双亲委派机制了。上源码

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 查询自己是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
               		 //委派给parent去查询
                    if (parent != null) {
                    	//其实就是递归调用loadClass。最后调用到bootClassLoader
                    	//bootClassLoder源码很少,大概就是查询已经加载过,然后查询自己,返回class
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                   	//如果parent没有找到,就自己加载class
                    c = findClass(name);
                }
            }
            return c;
    }

画个图
画的比较龊,见谅
在这里插入图片描述

至于为啥要设计双亲委派机制呢?
1.避免重复加载,当父类加载器已经加载了,就没有必要再加载一次
2.安全心考虑,避免核心api库随意修改

整个流程的示意图
在这里插入图片描述
虽然画的不是很好,但是基本上还是很清晰的。

可以从上图或者源码中得到,本身宿主的类都是保存在dexFile里,而后dexFile都是保存在Element[]中。到这里就很明白了,其实只要将插件的类也保存到上诉的Element[]中,然后生成新的element[],是不是就可以实现加载插件了?

而后的步骤用一句话说,就是通过反射 给你dexElement赋值。将原先的element[]改成宿主和插件合并的element[]就好了。后面就是反射的api调用了。

目标是获取dexElements
从源码中可以知道 dexElements不是静态的成员变量,所以要获取DexPathList类的对象。

final class DexPathList {
  	...
    /** list of dex/resource (class path) elements */
    private final Element[] dexElements;
	...
}

DexPathList对象在哪儿?可以从源码中找到,BaseDexClassLoader中有pathList。但是这个pathList也不是静态的成员变量,所以又要获取BaseDexClassLoader的对象。而这个就是类加载器。

public class BaseDexClassLoader extends ClassLoader {
  	...
    /** structured lists of path elements */
    private final DexPathList pathList;
    ...
}

获取宿主的类加载器,得到的element[]就是宿主的。
获取插件的类加载器,得到的element[]就是插件的。

是不是非常清晰了?剩下的就是撸码了。

先做好准备工作,将插件apk放到sdcard目录下

private final static String apkPath = "/sdcard/test-debug.apk";

首先获取宿主的classLoader

// 宿主的 类加载器
ClassLoader pathClassLoader =context.getClassLoader();
// DexPathList类的对象
Object hostPathList = pathListField.get(pathClassLoader);

而后要获取elements则要通过上面分析的步骤,一步一步获取对象
首先获取BaseDexClassLoader的pathList

Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");
pathListField.setAccessible(true);

然后获取dexElements

Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);

获取到dexElements之后,就可以获取elements了

 // 宿主的 dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);

同理可得到插件的elements

// 插件的 类加载器
ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),null, pathClassLoader);
// DexPathList类的对象
Object pluginPathList = pathListField.get(dexClassLoader);
// 插件的 dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);

而后创建数组,然后合并赋值

Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),hostDexElements.length + pluginDexElements.length);

System.arraycopy(hostDexElements, 0, newDexElements,0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newDexElements,hostDexElements.length, pluginDexElements.length);

//赋值
dexElementsField.set(hostPathList, newDexElements);

然后就没有然后了。完事了。
最后调用就好了。

这是普通类的加载,等空了再写四大组件和资源文件的。
希望大家喜欢

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值