标签(空格分隔): classloader multidex 热更新
前言:Android P的发布,使得一大批使用第三方热更新框架的APP一片哀嚎(核心原因是:Android P 禁止应用通过反射,JNI等方式调用系统的非SDK方法,第三框架或多或少都用到了反射,在Android P上APP的功能失效)
感谢磊神分享
Java Classloader
- 加载对象:字节码文件
- 动态加载:运行时需要该类时再加载到JVM
- 父加载器与委托加载
- 常见的类加载器:
- BootstrapClassloader
纯c++实现的类加载器,没有对应的java类,主要加载的jre/lib/目录下的核心库 - ExtClassloader
主要加载jre/lib/ext/目录下的扩展包 - AppClassloader
主要加载java classpath路径下的包
- BootstrapClassloader
类的继承关系:
注:
ExtClassLoader.java 和 AppClassLoader.java本身不具有类的继承关系,他们共同继承URLClassLoader.java
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {}
Parent通过ExtClassLoader 构建 APPClassLoader:
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
//将ExtClassLoader对象实例传递进去
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
1071527397.jpg
ClassLoader 测试代码
ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
System.out.println("app ClassLoader : " + appClassLoader);
System.out.println("app ClassLoader class : " + appClassLoader.getClass());
System.out.println("app ClassLoader load Path : " + System.getProperty("java.class.path"));
System.out.println("-------------------------");
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("ext ClassLoader : " + extClassLoader);
System.out.println("ext ClassLoader class : " + extClassLoader.getClass());
System.out.println("ext ClassLoader load Path : " + System.getProperty("java.ext.dirs"));
System.out.println("-------------------------");
ClassLoader bootClassLoader = extClassLoader.getParent();
System.out.println("boot ClassLoader : " + bootClassLoader);
System.out.println("boot ClassLoader load Path : " +(System.getProperty("sun.boot.class.path")));
执行结果如下:
app ClassLoader : sun.misc.Launcher$AppClassLoader@c387f44
app ClassLoader class : class sun.misc.Launcher$AppClassLoader
app ClassLoader load Path : D:\Android_work\java\ClassLoaderTest\bin
-------------------------
ext ClassLoader : sun.misc.Launcher$ExtClassLoader@659e0bfd
ext ClassLoader class : class sun.misc.Launcher$ExtClassLoader
ext ClassLoader load Path : D:\soft\devsoft\soft\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
-------------------------
boot ClassLoader : null
boot ClassLoader load Path : D:\soft\devsoft\soft\jre\lib\resources.jar;D:\soft\devsoft\soft\jre\lib\rt.jar;D:\soft\devsoft\soft\jre\lib\sunrsasign.jar;D:\soft\devsoft\soft\jre\lib\jsse.jar;D:\soft\devsoft\soft\jre\lib\jce.jar;D:\soft\devsoft\soft\jre\lib\charsets.jar;D:\soft\devsoft\soft\jre\lib\jfr.jar;D:\soft\devsoft\soft\jre\classes
为什么要引入父委托加载机制
- 保证软件系统的安全性,防止注入恶意代码
- 可以通过父委托机制实现"伪继承"的方式调用系统私有方法或者实现多版本SDK兼容
例如:
String.java此方法不公开,而我们想使用,最常用的方式是反射。介绍另外一种方法利用ClassLoader的加载顺序实现"伪继承":
1.在应用中构建一个同包名和同函数的String方法,并将非公开的方法设置为public,在编译时会使用应用中的String,运行时由于ClassLoader的父委托机制,优先加载系统类(public等关键字在编译时后无效)
/**
* Copy characters from this string into dst starting at dstBegin.
* This method doesn't perform any range checking.
*/
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
Android Classloader
- 加载对象:dex文件、jar及apk
- 常用类加载器:
- BootClassLoader
所有类加载器的parent,是ClassLoader的内部类;和java的BootStrapClassLoader不一样,是由java代码实现的 - PathClassLoader(系统默认加载器)
一般加载已安装好的apk文件,/data/app目录下- DVM虚拟机(JIT)
- ART虚拟机(AOT) 可加载未安装的dex、jar或者apk文件
- DexClassLoader
加载路径需要在创建DexClassLoader时传入,所以可以加载任何路径下的dex、jar或者apk文件
- BootClassLoader
- 动态加载的两种方式(个人理解其实就是热更新实现的两种方式)
- 将DexClassLoader放在PathClassLoader和BootClassLloader之间
- 将DexClassLoader要加载的文件路径追加到PathClassLoader的DexPathList的后面
Android Multidex(分包)
- 分包机制
- 什么是分包
- 解决65535问题
203851534.jpg
在android 5.0之前,DVM虚拟机用short类型的变量保存了一个dex文件中的方法数,所以最大值是65535
- 分包方法
- gradle本身支持自动分包,清单文件配置即可
- 配置依赖"com.android.support:multidex:1.0.0"
- 启动multidex: multiDexEnable true
- 在application里配置: MultiDex.install()
- 开发者自己分包,自己动态加载
将class文件转为dex文件:
使用android sdk里的build-tool下的dx工具,执行
dx --dex --output=xxx.dex xxx.class
- gradle本身支持自动分包,清单文件配置即可
带包名的类转dex注意: 最后一个参数应当携带包的相对路径,例如:
dx –-dex –-output=xxx.dex 包名/xxx.class
基于类加载器实现的简单热更新Demo
准备操作:生成补丁文件与搭建后台
- dx --dex --output=patch.dex com/sensetime/classloader/Statistic.class
- java servlet搭建简单后台
- 客户端启动检查是否要热更新,进行多线程下载补丁文件
- 将下载好的补丁文件拷贝到应用私有目录下
- 动态加载补丁文件
第一种实现
- 将DexClassLoader放在PathClassLoader和BootClassLoader之间
第二种实现
- 将DexClassLoader要加载的文件路径追加到PathClassLoader的DexPathList的后面
结语:第三方热更新的框架基本都是通过这两种方式实现热更新,但是在Android P上或多或少都存在问题,建议大家尽量使用Android 标准的接口,原因在于Android逐渐加强了系统安全控制,第三方框架或多或少有很多灰色地带。
网友评论