android插件话和热加载,Android动态加载之ClassLoader —热修复、插件化

Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应用不进行分dex处理,最后一个应用的apk只会有一个dex文件。

Android平台的ClassLoader

60ff644b6e4c

image.png

他们的区别是:

PathClassLoader:是 Android 应用中默认的类加载器,只能加载已经安装到 Android 系统中的apk文件(/data/app目录下,解压为 dex 后优化为 odex)。

DexClassLoader:可以加载路径下的 dex/jar/apk/zip 文件,比 PathClassLoader 更灵活,是实现热修复的关键。

首先说下双亲委托模式

双亲委托模式的特点

类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。

这样讲可能会有些抽象,来看下面的图。

60ff644b6e4c

image.png

再说dex文件的加载和类的查找过程

Java层通过我们会通过创建一个DexClassLoader来加载我们的dex,下面就以此为切入点进行

创建ClassLoader

dexClassLoader = new DexClassLoader(apkPath, getFilesDir().getAbsolutePath(), null, getClassLoader());

查看DexClassLoader的构造方法。

public class DexClassLoader extends BaseDexClassLoader {

// dexPath:是加载apk/dex/jar的路径

// optimizedDirectory:是优化dex后得到的.odex文件的输出路径

// libraryPath:是加载的时候需要用到的so库

// parent:给DexClassLoader指定父加载器

public DexClassLoader(String dexPath, String optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(dexPath, new File(optimizedDirectory), libraryPath, parent);

}

}

可以看到它调用的是父类的构造函数,所以直接来看BaseDexClassLoader的构造函数。

private final DexPathList pathList;

public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(parent);

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

}

创建了一个DexPathList实例,下面来看看DexPathList的构造函数。

private final Element[] dexElements;

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

ArrayList suppressedExceptions = new ArrayList();

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions);

}

//它调用的是makeDexElements方法来创建一个Element数组来存放Element对象,

//每个Element对象包含一个DexFile对象。

private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,

ArrayList suppressedExceptions) {

ArrayList elements = new ArrayList();

/*

* Open all files and load the (direct or contained) dex files

* up front.

*/

for (File file : files) {

File zip = null;

DexFile dex = null;

String name = file.getName();

// 如果是一个dex文件

if (name.endsWith(DEX_SUFFIX)) {

// Raw dex file (not inside a zip/jar).

try {

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException ex) {

System.logE("Unable to load dex file: " + file, ex);

}

// 如果是一个apk或者jar或者zip文件

} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)

|| name.endsWith(ZIP_SUFFIX)) {

zip = file;

try {

// 1、调用loadDexFile加载dex文件,得到一个DexFile对象

// loadDexFile通过c++层native方法去加载dex文件

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException suppressed) {

suppressedExceptions.add(suppressed);

}

} else if (file.isDirectory()) {

elements.add(new Element(file, true, null, null));

} else {

System.logW("Unknown file type for: " + file);

}

// 2、把DexFile对象封装到Element对象中,然后将Element对象加入Element数组

if ((zip != null) || (dex != null)) {

elements.add(new Element(file, false, zip, dex));

}

}

return elements.toArray(new Element[elements.size()]);

}

dex文件的加载流程:我们会使用DexClassLoader去加载dex文件,DexClassLoader会将这个任务委派给DexPathList中的makeDexElements方法,在makeDexElements中调用了native层的 c++方法去真正的加载dex文件,然后返回DexFile的对象,通过这个对象构建一个Element的对象,然后将这个Element添加到dexElements的数组中。

类的加载过程

当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程,源码如下

protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {

// 首先从已经加载的类中查找

Class> clazz = findLoadedClass(className);

if (clazz == null) {

ClassNotFoundException suppressed = null;

try {

// 如果没有加载过,先调用父加载器的 loadClass

clazz = parent.loadClass(className, false);

} catch (ClassNotFoundException e) {

suppressed = e;

}

if (clazz == null) {

try {

// 父加载器都没有加载,则尝试自己去加载

clazz = findClass(className);

} catch (ClassNotFoundException e) {

e.addSuppressed(suppressed);

throw e;

}

}

}

return clazz;

}

一句话概括:ClassLoader 加载类时,先查看自身是否已经加载过该类,如果没有加载过会首先让父加载器去加载,如果父加载器无法加载该类时才会调用自身的 findClass 方法加载,该逻辑避免了类的重复加载。所以我们所要实现的就是把要替换的类可见性提前,这样类加载器就会优先找到修复过的类。

类的查找过程

//DexClassLoader间接调用父类findClass方法

//,findClass方法中调用DexPathList中的DexPathList方法

public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(parent);

this.originalPath = dexPath;

this.pathList =

new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

Class clazz = pathList.findClass(name);

if (clazz == null) {

throw new ClassNotFoundException(name);

}

return clazz;

}

//看DexPathList中的findClass方法,可以看到它是遍历dexElements数组,

//到每个dex文件去寻找当前需要的类,找到之后直接返回不往下找了

public Class findClass(String name) {

for (Element element : dexElements) {

DexFile dex = element.dexFile;

if (dex != null) {

Class clazz = dex.loadClassBinaryName(name, definingContext);

if (clazz != null) {

return clazz;

}

}

}

return null;

}

类的查找过程:DexClassLoader通过findClass去查找一个类,同样它也是委派给DexPathList的findClass去查找,在DexPathList的findClass中会去遍历我们上面创建的dexElements数组,然后在每个dex中去查找相应的类,找到之后就返回,不再向后查找。

热修复过程

1、PathClassLoader 作为默认的类加载器,也就是第一个 DEX 文件是 PathClassLoader 自动加载的。

2、通过前面的分析来看,我们知道 PathClassLoader 里面的 DEX 文件是存放在一个 Element 数组中,可以包含多个 DEX 文件,所以我们只需要通过反射获取 PathClassLoader 中的 DexPathList 中的Element数组(已加载了第一个dex包,由系统加载),将要替换的 DEX 文件放置到这个数组中去。

3、将两个Element数组合并之后,再将其赋值给 PathClassLoader 的 Element 数组

Bugly热更新是基于Tinker的 Bugly 热更新原理图如下:

60ff644b6e4c

image.png

60ff644b6e4c

image.png

如版本 1.0.0 上线后有bug 启动1.0.0 版本app的时候 会把当前对应的TinkerId上报到平台,

修改相关代码 执行第二步骤 打补丁包后 补丁包里面会有一个YAPATH.md文件,from 表示我发布的版本, to 表示我现在打补丁包,意思就是 这个1.0.0-patch这个补丁包 是针对于1.0.0-base 来修复的,

上传补丁包到配置平台后,平台会自动解析YAPATH.md文件, 然后针对性的下发补丁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 插件是指将一个应用的功能分离为多个独立的模块,每个模块可以在运行时独立加载和卸载。这样可以优应用的体积和性能,同时也可以实现模块的动态更新和管理。插件在某些场景下被广泛运用,比如一个大型的应用需要提供不同的功能模块供用户选择,或者在多个应用中共享某些通用功能。插件的实现方式有很多种,其中最常用的是利用 Android插件框架和技术来实现。 通常情况下,一个 Android 应用的所有组件(Activity、Service、BroadcastReceiver、ContentProvider 等)都被编译打包进同一个 APK 文件中,这也就意味着只有在应用安装和更新的时候才能进行组件的更新和修改,无法实现动态更新。而插件技术则打破了这种限制,它将应用的不同组件打包成不同的插件(APK)文件,然后在运行时动态加载和卸载。这样一来,我们可以实现在不停止应用的情况下对部分组件进行更新、拓展、甚至是删除。 为了实现插件,我们需要解决一些技术难点。其中最主要的是解决插件和宿主的交互问题。在插件和宿主中间需要进行很多数据传递、资源访问和类加载等操作,如果没有基础的交互机制,插件是无法成功实现的。因此,在 Android 中,插件相关的机制主要包括四个方面:类加载器(ClassLoader)、组件的注册和管理、资源访问和切换主题(Theme)等。 总体来看,插件技术Android 应用带来了更大的可拓展性和灵活性,这对于一些复杂或大型的应用非常重要。同时,基于插件技术,也催生出了一些全新的业态,比如插件市场和插件开发社区,为 Android 生态系统带来了更多的创新力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值