标签(空格分隔): Android
基础知识补充:###
为什么需要分包:
Android2.3及以前版本用来执行dexopt(用于优化dex文件)的内存只分配了5M,
一个dex文件最多只支持65536个方法
其实android中的分包,除了用dex分包还可以用插件化,即将一些独立的功能做成一个单独的apk,当打开的时候使用DexClassLoader动态加载,然后使用反射机制来调用插件中的类和方法。这固然是一种解决问题的方案:但这种方案存在着以下两个问题:
- 插件化只适合一些比较独立的模块;
- 必须通过反射机制去调用插件的类和方法,因此,必须搭配一套插件框架来配合使用;
但是使用dex分包方案仍然有几个注意点:
- 由于第二个dex包是在Application的onCreate中动态注入的,如果dex包过大,会使app的启动速度变慢,因此,在dex分包过程中一定要注意,第二个dex包不宜过大。
- 由于上述第一点的限制,假如我们的app越来越臃肿和庞大,往往会采取dex分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。(采用dex分包+插件化)
热修复、热补丁##
参考文章:安卓App热补丁动态修复技术介绍——QQ空间终端开发团队、鸿洋文章
应用场景:
当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。
这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?
热修复是基于android dex分包方案的,android dex分包原理
简单来说,其原理是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中,并在Application的onCreate回调中被注入到系统的ClassLoader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。
这里要区别PathClassLoader与DexClassLoader###
PathClassLoader和DexClassLoader都继承自BaseDexClassLoader
1、Android使用PathClassLoader作为其类加载器,只能去加载已经安装到Android系统中的apk文件;
2、DexClassLoader可以从.jar和.apk类型的文件内部加载classes.dex文件就好了。如果大家对于插件化有所了解,肯定对这个类不陌生,插件化一般就是提供一个apk(插件)文件,然后在程序中load该apk,那么如何加载apk中的类呢?其实就是通过这个DexClassLoader。热修复也用到这个类!
#BaseDexClassLoader源码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
#DexPathList
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;
}
#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
//
可以看出呢,BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements,而对于类加载呢,就是遍历这个集合,通过DexFile去寻找。
ok,通俗点说:
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。(来自:安卓App热补丁动态修复技术介绍)
总结##
其实就是两件事:1、动态改变BaseDexClassLoader对象间接引用的dexElements;2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志。
理论基础:把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类
在此理论基础上,我们构想了热补丁的方案,,我们可以在这个dexElements中去做一些事情,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面比如,在这个数组的第一个元素放置我们的patch.jar,里面包含修复过的类,这样的话,当遍历findClass的时候,我们修复的类就会被查找到,从而替代有bug的类。
插件化##
Android插件化基础:使用了自定义的ClassLoader来实现的加载,功能比较简单,只支持class的加载与另外也可以通过使用系统的DexClassLoader来直接加载Jar,Apk
使用PathClassLoader加载已安装的apk插件、DexClassLoader加载未安装的apk插件
通过插件化的操作,就可以做一个简单的代码逻辑的插件化了。将你的逻辑处理代码打包成Jar或者APK,放到服务器上。
客户端开启后检查是否有新的版本,如果有就后台下载,覆盖源文件(这样逻辑上应该先把旧的逻辑加载到内存中,这里可以有很多不同的策略)。
下次打开的时候,直接加载新下载的Jar或者APK就动态更新了内部逻辑
【与原先的下载新的APK跟新版本不同,因为插件化的APK只是新增的功能类,所以体积小】
插件化框架:
插件化发展历史:博客一博客二
说到未来,也不得不提去年出来的ReactNative,RN比插件化更轻量级,越来越多人选择了RN,或许会代替插件化,虽然还有很多缺点,比如说没网的时候
热修复与插件化的对比##
共同原理:
都使用ClassLoader来实现的加载的新的功能类,都可以使用PathClassLoader与DexClassLoader
不同的是:
热修复因为是为了修复Bug的,所以要将新的同名类替代同名的Bug类,要抢先加载新的类而不是Bug类,所以多做两件事:在原先的app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志,还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements,这样才能抢先代替Bug类,完成系统不加载旧的Bug类
而插件化只是增肌新的功能类或者是资源文件,所以不涉及抢先加载旧的类这样的使命,就避过了阻止相关类去打上CLASS_ISPREVERIFIED标志和还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements
所以插件化比热修复简单,热修复是在插件化的基础上在进行替旧的Bug类