java插件化_从零开始实现⼀个插件化框架(⼀)
欢迎关注专栏:⾥⾯定期分享Android和Flutter架构技术知识点及解析,还会不断更新的BATJ⾯试专题,欢迎⼤家前来探讨交流,如有好
的⽂章也欢迎投稿。Flutter跨平台开发终极之选z huanlan.zhihu
什么是插件化
概念
插件化技术最初源于免安装运⾏ apk 的想法,这个免安装的 apk 就可以理解为插件,⽽⽀持插件的 app 我们⼀般叫宿主。宿主可以在运⾏时加载和运⾏插件,这样便可以将 app 中⼀些不常⽤的功能模块做成插件,⼀⽅⾯减⼩了安装包的⼤⼩,另⼀⽅⾯可以实现 app 功能的动
态扩展。
我们知道计算机主板就是由⼀系列的插槽组成的,我们需要什么功能,给它插上对应的芯⽚或显卡就可以了,从⽽实现热拔插。基于这个原理,软件⽅⾯的热拔插就是插件化
插件化解决的问题
APP的功能模块越来越多,体积越来越⼤,这样可以将⼀些业务模块做成插件化,按需加载,从⽽减⼩安装包的体积
安卓app开发用什么框架模块之间的耦合度⾼,协同开发沟通成本越来越⼤
⽅法数⽬可能超过65535,APP占⽤的内存过⼤
应⽤之间的互相调⽤
组件化与插件化的区别
组件化开发就是将⼀个app分成多个模块,每个模块都是⼀个组件,开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统⼀成⼀个apk,这就是组件化开发。
插件化开发和组件化略有不同,插件化开发是将整个app拆分成多个模块,这些模块包括⼀个宿主和多个插件,每个模块都是⼀个apk,最终打包的时候宿主apk和插件apk分开打包。
各插件化框架对⽐
市⾯上⽐较流⾏的插件化框架也有很多,他们之间都有哪些区别呢?
插件化实现
插件apk是没有安装的,那么怎么让宿主去加载它呢?我们知道,⼀个apk是有代码和资源组成的,所以只需要考虑两个问题即可:
如何加载插件中的类?
如何加载插件中的资源?
当然还有最重要的⼀个问题,四⼤组件如何调⽤呢?四⼤组件是需要注册的,⽽插件apk中的组件显然不会在宿主提前注册,那么如何去调⽤它呢?
下⾯我们就来⼀步⼀步解决这些问题
ClassLoader类加载器
以前在讲热修复的时候,我简单地介绍了⼀下ClassLoader的加载机制。java源码⽂件在编译后会⽣成⼀个class⽂件,⽽在Android中,将代码编译后会⽣成⼀个 apk ⽂件,将 apk ⽂件解压后就可以看到其中有⼀个或多个 classes.dex ⽂件,它就是安卓把所有 class ⽂件进⾏合并,优化后⽣成的。
java 中 JVM 加载的是 class ⽂件,⽽安卓中 DVM 和 ART 加载的是 dex ⽂件,虽然⼆者都是⽤的 ClassLoader 加
载的,但因为加载的⽂件类型不同,还是有些区别的,所以接下来我们主要介绍安卓的 ClassLoader 是如何加载
dex ⽂件的。
ClassLoader实现类
在Android中,ClassLoader是⼀个抽象类,它的实现类主要分为两种类型:系统类加载器(BootClassLoader),和⾃定义类加载器(PathClassLoader | DexClassLoader)
先看⼀下ClassLoader加载流程图:
BootClassLoader
⽤于加载Android Framework层的class⽂件,⽐如 Activity、Fragment,不过需要注意的是AppCompatActivity虽然也是google⼯程师提供的类,但是⼀个第三⽅包中的类,并不输⼊Framwork层,所以AppCompatActivity并不是使⽤BootClassLoader加载的
PathClassLoader
⽤于Android应⽤程序类加载器。可以加载指定的dex, 以及jar、zip、apk中的classes.dex
DexClassLoader
在Android8.0以后的API中,和 PathClassLoader是没有任何区别的,⽽在以前的API中,两者只有⼀个设置加载路径的区别(有的⽂章说,PathClassLoader只⽀持直接操作dex格式⽂件,⽽DexClassLoader可以⽀持.apk、.jar和.dex⽂件,并且会在指定的outpath路径
释放出dex⽂件。其实不然,甚⾄可以说两者没有任何区别)
先放⼀张ClassLoader类继承关系图,相信都能看懂,就不多讲了,下⾯来看⼀下PathClassLoader 和 DexClassLoader的源码:
// /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
// optimizedDirectory 直接为 null
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
// optimizedDirectory 直接为 null
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
// API ⼩于等于 26/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
// 26开始,super⾥⾯改变了,看下⾯两个构造⽅法
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
// API 26/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
// DexPathList 的第四个参数是 optimizedDirectory,可以看到这⼉为 null
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
}
// API 25/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
根据源码就可以了解到,PathClassLoader 和 DexClassLoader 都是继承⾃ BaseDexClassLoader,且类中只有构造⽅法,它们的类加载逻辑完全写在 BaseDexClassLoader 中。
其中我们值的注意的是,在8.0之前,它们⼆者的唯⼀区别是第⼆个参数 optimizedDirectory,这个参数的意思是
⽣成的 odex(优化的dex)存放的路径,PathClassLoader 直接为null,⽽ DexClassLoader 是使⽤⽤户传进来的
路径,⽽在8.0之后,⼆者就完全⼀样了。
下⾯我们再来了解下 BootClassLoader 和 PathClassLoader 之间的关系:// 在 onCreate 中执⾏下⾯代码
ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
Log.e("leo", "classLoader:" + classLoader);
classLoader = Parent();
}
Log.e("leo", "classLoader:" + ClassLoader());
打印结果:
classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file
"/data/user/joy.pluginactivity/cache/plugin-debug.apk", zip file
"/data/joy.pluginactivity-T4YwTh-
8gHWWDDS19IkHRg==/base.apk"],nativeLibraryDirectories=[/data/joy.pluginactivity-
T4YwTh-8gHWWDDS19IkHRg==/lib/x86_64, /system/lib64, /vendor/lib64]]]
classLoader:java.lang.BootClassLoader@a26e88d
classLoader:java.lang.BootClassLoader@a26e88d
通过打印结果可知,应⽤程序类是由 PathClassLoader 加载的,Activity 类是 BootClassLoader 加载的,并且
BootClassLoader 是 PathClassLoader 的 parent,这⾥要注意 parent 与⽗类的区别。这个打印结果我们下⾯还
会提到。
加载原理
那么如何使⽤类加载器去从dex中加载⼀个插件类呢?很简单
⽐如,有⼀个apk⽂件,路径是apkPath,⾥⾯有个类com.plugin.Test,就可以通过反射加载⼀个类:
// 初始化⼀个类加载器
DexClassLoader classLoader = new DexClassLoader(dexPath, CacheDir().getAbsolutePath, null,
/
/ 获取插件中的类
Class> clazz = classLoader.loadClass("com.plugin.Test");
// 调⽤类中的⽅法
Method method = Method("test", Context.class)
method.wInstance(), this)
dex中加载类很简单,但是我们需要的是将插件中的dex加载到宿主⾥⾯,⼜该怎么做呢?其实原理还是跟热修复⼀样,下⾯就以API 26 Android 8.0举例,通过源码,看⼀下DexClassLoader类加载器是怎么加载⼀个apk中的dex⽂件的。
通过查发现,DexClassLoader并没有加载类的⽅法,继续看它的⽗类,最后在ClassLoader类中到了⼀个loadClass⽅法,看来就是通过这个⽅法来加载类了:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 1. 检测这个类是否已经被加载,如果已经被加载了就可以直接返回了
Class> c = findLoadedClass(name);
// 如果类未被加载
if (c == null) {
try {
// 2. 判断是否有上级加载器,使⽤上级加载器的loadClass⽅法去加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 正常情况下是不会⾛到这⾥的,因为最终ClassLoader都会⾛到BootClassLoader,重写了loadClass⽅法结束掉了递归
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
// 3. 如果所有的上级都没到,就调⽤findClass⽅法去查
if (c == null) {
c = findClass(name);
}
}
return c;
}
上⾯类加载分为了3个步骤
1、 检测这个类是否已经被加载,最终会调⽤到native⽅法实现查,这⾥就不深⼊了:
protected final Class> findLoadedClass(String name) {
ClassLoader loader;
if (this == Instance())
loader = null;
else
loader = this;
//native⽅法
return VMClassLoader.findLoadedClass(loader, name);
}
2、如果没被到,就会从parent中调⽤loadClass⽅法去查,依次递归,如果到了就返回,如果所有的上级都没有到,⼜会调⽤到findClass⼀级⼀级的去查。这个过程就是双亲委托机制
3、 findClass
// -->2 加载器⼀般都会重写这个⽅法,定义⾃⼰的加载规则
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
根据前⾯的打印结果我们可以看懂,ClassLoader的最上级是BootClassLoader,来 看下它是如何重写的loadClass⽅法,结束递归的:
class BootClassLoader extends ClassLoader {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。