Android 应用热修复:ClassLoader 技术解析与应用
大家好,今天小编来为大家解答以下的问题,关于Android 应用热修复:ClassLoader 技术解析与应用,这个很多人还不知道,现在让我们一起来看看吧!
在用公司的框架进行开发时,最大的特点是模块纯java开发,打成dex包进行模块更新,而不用更新app。
这个算是热修复框架里的java multidex方式实现的,接下来看看ClassLoader如何实现的热修复。
2. 原理
首先来看看实现功能需要哪些原理。
2.1 Dalvik,JIT与ART
Java是编译-解释语言,即程序员编译之后不可以直接编译为机器码,而是会编译成 字节码 (在Java程序中为.class文件,在Android程序中为.dex文件)。然后我们需要将字节码再解释成 机器码 ,使之能被CPU解读。这第二次解释,即从 字节码解释成机器码 的过程,是程序安装或运行后,在 Java虚拟机中实现 的。
2.1.1 Dalvik
android在最开始时,内置了一个 Dalvik虚拟机 ,其实也就是Google自己编写的一个Java虚拟器,然后使用 边解释边执行 的方式来运行Java代码,这种模式==运行效率极其低下==,因此很快Google就引入了 JIT模式 来改善这种情况。
2.1.2 JIT
JIT(Just In Time)是 即时编译 的意思,当用户在使用App时, 会将经常使用的功能编译成机器码 ,这样当再次使用这个功能时就可以直接运行机器码,而不用每次都一行行翻译了。
虽然JIT挺聪明的,且总体思路清晰理想丰满,但现实是仍然卡的要死。==打开APP的时候会变慢==;==每次打开APP都要重复劳动,不能一劳永逸==;==如果用户打开了JIT没有编译的代码,就只能等DVM中的解释器去边执行边解释了==。
2.1.3 ART
然而JIT的机制仍然不够完美,在Android 5.0系统的时候Google进行了一次大变更,废弃了Dalvik虚拟机,引入了全新开发的ART虚拟机,并使用 AOT(Ahead Of Time) 的方式来提升运行效率。 AOT就是在应用安装的时候预先将代码都编译成机器码 ,这样在应用运行的时候就不用再做解释工作了,直接就可以运行。
然而最终用户实际的反馈却并不怎么好,==AOT机制使得安装应用变得太慢了,而且预先编译机器码还会占用额外的手机空间==。
2.1.4 混合编译
于是在Android 7.0系统中,Google又进行了调整,这次重新引入了JIT模式。应用安装的时候不会进行编译操作,以保证安装速度。App运行时,dex文件 先通过解析器被直接执行 并记录在profile文件,通过profile来判断是否为热点函数, 热点函数 会被识别并被 JIT编译 后存储在 jit code cache 中。手机进入 IDLE(空闲) 或者 Charging(充电) 状态的时候,系统会 扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译 。如果执行到还没来得及编译的代码,那么就使用 JIT+解释执行 的方式来顶住。
2.1.5 Android 8.0 改进解释器
在Android8.0时期,谷歌又盯上了解释器,其实纵观上面的问题,根源就是这个解释器解释的太慢了!(什么JIT,AOT,老夫解释只有一个字,快)那我们何不让这个解释器解释的快一点呢? 于是谷歌改进了解释器,解释模式执行效率大大提升 。
2.1.6 Android 9.0 改进编译模板
在Android9.0上提供了预先放置热点代码的方式, 应用在安装的时候就能知道常用代码会被提前编译 。
谷歌允许你在开发阶段添加一个配置文件,这个配置文件内可指定“热点代码”,当应用安装完后,ART在后台悄悄编译APP时,会优先编译配置文件中指定的“热点代码”。
2.1.7 文件格式odex与oat
dex :可以简单的理解为:Dex 文件是很多 .class 文件处理后的产物,最终可以在 Android 运行时环境执行。是 字节码 。
odex : dalvik虚拟机 中,安装时从apk文件中提取出classes.dex文件,并通过 dexopt 生成一个可运行的文件单独存放在data/dalvik-cache目录下,在 DexClassLoader 动态加载Dex文件时,也会进行Dex的优化。 odex 文件也属于dex文件 。
oat : art虚拟机 中,APK中的.dex文件(字节码)会被 dex2oat 解释为ELF格式的.oat文件(机器码)同样保存在手机的data/dalvik-cache目录下。
APK运行时,上述生成的oat文件会被 加载到内存中 ,并且ART虚拟机可以通过里面的oatdata和oatexec段找到任意一个类的方法对应的本地机器指令来执行。 oat文件中的 oatdata 包含用来生成本地机器指令的dex文件,内容oat文件中的 oatexec 包含有生成的本地机器指令。
2.1.8 编译模式jit与aot
在android5.0以下和7.0以上,jit和art的表现都不一样。
2.1.8.1 android5.0以下的jit
在运行时对dex的指令进行intercept,解释成机器码;虚拟机根据函数调用的次数,来决定 热点代码 ;以函数为维度将热点代码的机器码进行缓存,而 jit就是将热点代码优化编译缓存 ,存在下一次调用时直接调用该机器码。
优点:安装速度超快;存储空间小
缺点:Multidex加载的时候会非常慢,因为在dex加载时会进行dexopt;JIT中需要解释器,解释器解释的字节码会带来CPU和时间的消耗;由于热点代码的Monitor一直在运行,也会带来电量的损耗;每次启动应用都需要 重新编译 (没有缓存)
2.1.8.2 5.0-7.0的aot
art虚拟机运行的文件格式也从odex转换成了oat格式。oat文件包含oatdata和oatexec,前者包含dex文件内容,后者包含生成的本地机器指令。
在APK安装的时候,PackageManagerService会调用 dex2oat 通过 aot静态编译 的方式,来将所有的dex文件(包括Multidex)编译oat文件;DexClassLoader动态加载也会生成oat文件。
优点:运行时会超级快;在运行时省电,也节省各种资源
缺点:应用安装和系统升级之后的 应用优化比较耗时 (重新编译,把程序代码转换成机器语言),所有app都需要进行dex2oat的操作;由于oat文件中包含dex文件与编译后的Native Code,导致 占用空间也越来越大
2.1.8.3 7.0至今的aot与jit
7.0之后的采用了 Hybrid Mode的ART虚拟机 :aot,jit,解释器三种混合编译,来从运行时的性能、存储、安装、加载时间进行平衡。
安装时以Intercept的方式来运行App;在系统空闲的时候会在后台对App进行 AOT静态编译 ,并且会根据 解释器运行时所收集的运行时函数调用的信息生成的Profile文件来进行参考 ,Profile文件中的热点函数会被jit编译器编译并缓存下次使用。
[图片上传失败...(image-6ba82c-1576567815069)]
由上图可知, 解释器解释函数会记录在Profile文件中,如果Profile文件判断方法是热点代码,会jit编译器进行编译并缓存,下次调用则直接使用缓存机械码 。
在BackgroundDexOptService中,会根据所生成的Profile以及Dex文件在后台进行AOT, 根据运行时的Profile文件会选择性的将常用的函数编译成NativeCode
9.0谷歌推出了 热点代码配置功能 ,当应用安装完后,ART在后台悄悄编译APP时,会优先编译配置文件中指定的“热点代码”。
2.1.8.4 存放目录
大家都知道 apk其实就是zip包 apk安装过程其实就是解压过程
用户应用安装涉及以下几个目录
data/app安装目录 安装时会把apk文件copy到这里
data/dalvik-cache如上述描述中的存放.dex(.odex 无论davilk的dex 还是art的oat格式)
data/data/pkg/存放应用程序的数据
2.1.9 方舟编译器
翻译器和编译器是不同的; 编译器 是把源程序的每一条语句都编译成机器语言并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,因而速度很快;而 解释器 则是 只在执行程序时 ,才一条一条地解释成机器语言来让计算机执行,因此运行速度不如编译后的程序运行得快。
例如上面的混合编译,jit是即时编译,AOT静态编译,jit配合解释器使用。无论是编译器还是解释器,只是在虚拟机上打补丁,手机上的虚拟机+编译器+解释器本身不仅占用硬件资源,还无法最大发挥软件运行性能。正因如此,所以绝大部分手机厂商只能无奈的通过简单粗暴提升Android手机的内存和存储空间,来弥补虚拟机的弊端。
方舟编译器与其说是一个编译器,不如说是一个编译运行系统;这个系统的运行需要开发环境和终端(也就是智能手机)的配合,其目的是 绕过Android操作系统中App的运行所必须依赖的虚拟机 ,将Java/C/C++等混合代码 一次编译成机器码 直接在手机上运行,彻底告别Java的JNI额外开销,也彻底告别了虚拟机的GC内存回收带来的应用进程掉线——从而最终实现Android操作系统的流畅度。
它将编译过程放在了开发者那里,直接运行机械码也比Java/C/C++等混合代码快,还自制了更好的垃圾回收。
2.2 ClassLoader简介
在java中,它是类加载器,顾名思义,根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例,将class类载入到JVM中。
而android中,也同样需要有ClassLoader机制将class类加载到Android 的 Dalvik(5.0之前版本)/ART(5.0增加的)中,是将class打包成一个或者多个 dex文件,再由BaseDexClassLoader来进行处理。
在Android中,ClassLoader是一个抽象类,实际开发过程中,我们一般是使用其具体的子类 DexClassLoader 、 PathClassLoader 这些类加载器来加载类的, BootClassLoader 加载系统类,它们的不同之处是:
BootClassLoader:主要用于加载系统的类,包括java和android系统的类库,和JVM中不同,BootClassLoader是ClassLoader内部类,是由Java实现的,它也是所有系统ClassLoader的父ClassLoader
DexClassLoader:可以用于加载任意路径的zip、dex、jar或者apk文件,也是进行安卓动态加载的基础
PathClassLoader:用于加载Android系统类和开发编写应用的类,只能加载已经安装应用的 dex 或 apk 文件
简单来说,DexClassLoader是动态加载sd卡中的dex,PathClassLoader加载安装时解压在私有路径data/dalvik-cache下的dex。
在Android系统启动的时候会创建一个 Boot类型的ClassLoader实例 ,用于加载一些 系统Framework层级需要的类 ,我们的Android应用里也需要用到一些系统的类,所以APP启动的时候也会把这个Boot类型的ClassLoader传进来。
一个运行的Android应用至少有2个ClassLoader。一个是 BootClassLoader (系统启动的时候创建的),另一个是 PathClassLoader 。
DexClassLoader
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); }}
PathClassLoader
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); }}
这两者只是简单的对BaseDexClassLoader做了一下封装,具体的实现还是在父类里。不过这里也可以看出,PathClassLoader的 optimizedDirectory 只能是null,进去BaseDexClassLoader看看这个参数是干什么的
BaseDexClassLoader
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.originalPath = dexPath; this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}
主要是创建了一个DexPathList对象
DexPathList
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { …… this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);}private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory) { ArrayList<Element> elements = new ArrayList<Element>(); for (File file : files) { ZipFile zip = null; DexFile dex = null; String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile(file, optimizedDirectory); } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) || name.endsWith(ZIP_SUFFIX)) { zip = new ZipFile(file); } …… if ((zip != null) || (dex != null)) { elements.add(new Element(file, zip, dex)); } } return elements.toArray(new Element[elements.size()]);}private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { if (optimizedDirectory == null) { return new DexFile(file); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0); }}/** * Converts a dex/jar file path and an output directory to an * output file path for an associated optimized dex file. */private static String optimizedPathFor(File path, File optimizedDirectory) { String fileName = path.getName(); if (!fileName.endsWith(DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath();}
前面介绍虚拟机的时候说过,odex和oat都是存在/data/dalvik-cache 目录,这也是optimizedDirectory为null时,默认的内部存储路径,也证明PathClassLoader是用来加载已经安装应用的 dex
。
从创建代码就可以窥测出一些实现热修复的关键, BaseDexClassLoader 中会创建一个对象 DexPathList ,DexPathList中会使用 dexElements 来存储已加载的dex信息, DexFile 。
2.4 双亲代理模型
既然知道dex信息存储对象,那来看看如何加载dex到。
ClassLoader.loadClass
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 1 通过调用c层findLoadedClass检查该类是否被加载过,若加载过则返回class对象(缓存机制),是通过BootClassLoader加载的缓存 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //2 各种类型的类加载器在构造时都会传入一个parent类加载器 //2 若parent类不为空,则调用parent类的loadClass方法 c = parent.loadClass(name, false); } else { //3 查阅了PathClassLoader、DexClassLoader并没有重写该方法,默认是返回null c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { //4 如果父ClassLoader不能加载该类才由自己去加载,这个方法从本ClassLoader的搜索路径中查找该类 long t1 = System.nanoTime(); c = findClass(name); } } return c;}protected final Class<?> findLoadedClass(String name) { ClassLoader loader; //BootClassLoader加载的缓存 if (this == BootClassLoader.getInstance()) loader = null; else loader = this; return VMClassLoader.findLoadedClass(loader, name);}
系统类会优先通过 BootClassLoader 进行加载,反正串改系统类。
parent.loadClass(name, false)这步就是双亲委派机制的妙处了。优先从父类读取dex,父类找不到再由自己用findClass去加载。
BaseDexClassLoader#findClass
@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c;}
可以看出,它是通过 pathList 来加载类的。
DexPathList#findClass
public Class findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { //调用到c层defineClassNative方法进行查找 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null;}
前面创建对象的时候,BaseDexClassLoader构造函数中创建了pathList对象,DexPathList对象中makeElements方法通过dex路径创建了dexElements对象。而加载类时也是通过遍历dexElements,通过其 DexFile 的 loadClassBinaryName 来加载类。
所以这里是==一个热修复的点==, 你可以将需要热修复的dex文件插入到dexElements数组前面 ,这样遍历的时候查到你最新插入的则返回,从而实现动态替换有问题类
DexFile#loadClassBinaryName
public Class loadClassBinaryName(String name, ClassLoader loader) { return defineClass(name, loader, mCookie);}private native static Class defineClass(String name, ClassLoader loader, int cookie);
最终还是调用了Native方法defineClass加载类。有趣的是,标准JVM中,ClassLoader是用defineClass加载类的,而Android中defineClass被弃用了,改用了loadClass方法,而且加载类的过程也挪到了DexFile中,在DexFile中加载类的具体方法也叫defineClass,不知道是Google故意写成这样的还是巧合。
2.5 热修复方案
2.5.1 向PathClassLoader的dexElements进行插入新的dex(目前最常见的方式)
从上面的ClassLoader#loadClass方法你就会知道,初始化的时候会进入BaseDexClassLoader#findClass方法中通过遍历dexElements进行查找dex文件,因为 dexElements 是一个数组,所以我们可以通过反射的形式,将需要热修复的dex文件插入到数组首部,这样遍历数组的时候就会优先读取你插入的dex,从而实现热修复。
image
我们可以通过创建一个DexClassLoader对象,来获取要加载dex的dexElements对象,然后插入PathClassLoader的dexElements中。这也是公司框架的热修复实现方式。
实现细节,创建热修复中的对象
//1.创建DexClassLoader对象,这里optimizedDirectory为libsDexClassLoader loader = new DexClassLoader(dexPath, context.getDir("libs", 0).getAbsolutePath(), libraryPath, context.getClassLoader());//2.获取PathClassLoader对象PathClassLoader pathLoader = context.getClassLoader();//3.通过反射获取响应的PathList对象Object pathLoaderPathList = getField(classLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");Object dexLoaderPathList = getField(classLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");//4.通过反射获取相应的DexElements对象Object pathDexElements = getField(pathLoaderPathList, pathLoaderPathList.getClass(), "dexElements");Object dexDexElements = getField(dexLoaderPathList, dexLoaderPathList.getClass(), "dexElements");//5.将要加载的dex插入PathClassLoader的dexElements中,比较dexFile的mFileName,如果有相同的,先置空以去除重复的dexElements,再将两个dexElements合并Object dexElements = combine(pathDexElements, dexDexElements);//6.将合并后的dexElements放入pathLoaderPathList中setFieldValue(pathLoaderPathList, pathLoaderPathList.getClass(), "dexElements", dexElements);//7.获取要创建的类Class,创建DexClassLoader对象,因为双亲委派机制,会由PathClassLoader加载类(直接使用PathClassLoader加载也行)loader = new DexClassLoader(dexPath, context.getDir("libs", 0).getAbsolutePath(), libraryPath, context.getClassLoader());Class clazz = loader.loadClass(classPath);//8.通过反射创建对象Constructor<?> constructor = clazz.getConstructor(Context.class);Object ret = constructor.newInstance(c);//通过反射获取变量private Object getField(Object obj, Class<?> classObject, String fieldName) { Field localField = null; try { if (obj != null && classObject != null && fieldName != null) { localField = classObject.getDeclaredField(fieldName); if (localField != null) { localField.setAccessible(true); Object var7 = localField.get(obj); return var7; } } } catch (Exception var10) { } finally { } return null;}//合并两个dexElements,并去除重复的dexElementprivate Object combine(Object local, Object tar) { Class<?> localClass = null; int len = 0; int len_i = 0; int len_local = false; int len_tar = false; Object locObj = null; Object tarObj = null; Object loc = null; Object result = null; Object obj_loc = null; Object obj_loc_name = null; Object obj_tar = null; Object obj_tar_name = null; try { if (local != null && tar != null) { int len_local = Array.getLength(local); int len_tar = Array.getLength(tar); int i; //1.去除重复的dexElement if (len_local > 0 && len_tar > 0) { for(i = 0; i < len_local; ++i) { locObj = Array.get(local, i); if (locObj != null) { obj_loc = this.a(locObj, locObj.getClass(), "dexFile"); if (obj_loc != null) { obj_loc_name = this.a(obj_loc, obj_loc.getClass(), "mFileName"); if (obj_loc_name != null) { for(int j = 0; j < len_tar; ++j) { tarObj = Array.get(tar, j); if (tarObj != null) { obj_tar = this.a(tarObj, tarObj.getClass(), "dexFile"); if (obj_tar != null) { obj_tar_name = this.a(obj_tar, obj_tar.getClass(), "mFileName"); if (obj_tar_name != null && obj_tar_name.equals(obj_loc_name)) { Array.set(local, i, (Object)null); break; } } } } } } } } } for(i = 0; i < len_local; ++i) { locObj = Array.get(local, i); if (locObj != null) { ++len; } } localClass = local.getClass().getComponentType(); if (localClass != null) { if (len > 0) { loc = Array.newInstance(localClass, len); if (loc != null) { for(i = 0; i < len_local; ++i) { if (Array.get(local, i) != null) { Array.set(loc, len_i, Array.get(local, i)); ++len_i; } } } } result = Array.newInstance(localClass, len + len_tar); if (result != null) { for(i = 0; i < len + len_tar; ++i) { if (i < len) { Array.set(result, i, Array.get(loc, i)); } else { Array.set(result, i, Array.get(tar, i - len)); } } Object var19 = result; return var19; } } } } catch (Exception var22) { } return null;}//通过反射将值存入field中private void setFieldValue(Object obj, Class<?> classObject, String field, Object value) { Field localField = null; try { if (obj != null && classObject != null && field != null && value != null) { localField = classObject.getDeclaredField(field); if (localField != null) { localField.setAccessible(true); localField.set(obj, value); } } } catch (Exception var10) { } }
2.5.2 通过自定义ClassLoader实现class拦截替换
我们知道 PathClassLoader 是加载已安装的apk的dex,那我们可以
在 PathClassLoader 和 BootClassLoader 之间插入一个 自定义的MyClassLoader,而我们通过ClassLoader#loadClass方法中的第2步知道,若parent不为空,会调用parent.loadClass方法,固我们可以在MyClassLoader中 重写loadClass方法 ,在这个里面做一个判断去拦截替换掉我们需要修复的class。
也就是说,利用双亲委派模式,在PathClassLoader加载类时,先调用MyClassLoader的loadClass,在自定义的loadClass方法中,加载热修复的类。
具体实现
创建MyClassLoader,parent要设置为pathClassLoader的Parent
ClassLoader pathClassLoader = MyApplication.getContext().getClassLoader();MyClassLoader myClassLoader = new MyClassLoader(pathClassLoader.getParent());
将pathClassLoader的Parent设为MyClassLoader,将MyClassLoader插入PathClassLoader 和 BootClassLoader 之间
Field parentField = ClassLoader.class.getDeclaredField("parent");parentField.setAccessible(true);parentField.set(classLoader, newParent);
利用DexClassLoader加载热修复的类。
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, context.getDir("dex",context.MODE_PRIVATE).getAbsolutePath(),null, context.getClassLoader());Class<?> herosClass = dexClassLoader.getClass().getSuperclass();Method m1 = herosClass.getDeclaredMethod("findClass", String.class);m1.setAccessible(true);Class newClass = (Class) m1.invoke(dexClassLoader, className);
存入自定义的MyClassLoader中,在其loadClass加载相应的热加载类
myClassLoader.registerClass(className, newClass);
2.6 CLASS_ISPREVERIFIED标记
android5.0之前,在 Dalvik虚拟机下,执行 dexopt 时,会对类进行扫描,如果类里面所有直接依赖的类都在同一个 dex 文件中,那么这个类就会被打上 CLASS_ISPREVERIFIED 标记,表示这个类已经 预先验证过了 。如果一个类有 CLASS_ISPREVERIFIED标记,那么在热修复时,它加载了其他 dex 文件中的类,会报经典的Class ref in pre-verified class resolved to unexpected implementation异常
为了解决这个问题,QQ空间给出的解决方案就是,准备一个 AntilazyLoad 类,这个类会单独打包成一个 hack.dex,然后在所有的类的构造方法中增加这样的代码:
if (ClassVerifier.PREVENT_VERIFY) { System.out.println(AntilazyLoad.class);}
总结
Dalvik与art虚拟机,
jit即时编译与aot静态编译,
dex与odex与oat格式文件。
PathClassLoader,DexClassLoader,BaseDexClassLoader的关系;
双亲委派机制加载类;
BaseDexClassLoader构造函数中创建了pathList对象,DexPathList对象中makeElements方法通过dex路径创建了dexElements对象。而加载类时也是通过遍历dexElements,通过其DexFile的loadClassBinaryName来加载类。
利用加载机制实现热修复的两种方式:1.插入PathClassLoader的dexElements,利用其记载类的顺序;2.利用双亲委派机制,自定义ClassLoader插入PathClassLoader 和 BootClassLoader 之间,通过自定义的loadClass来加载类。
参考
Android虚拟机的JIT编译器
Dalvik和Art,JIT ,AOT, oat, dex, odex
说一说Android的Dalvik,ART与JIT,AOT
解读:华为方舟编译器的。。性到底体现在哪里?
9102年了,还不知道Android为什么卡?
Android动态加载基础 ClassLoader工作机制
剖析ClassLoader深入热修复原理
2018 深入解析Android热修复技术
用户评论
这个热修复功能极大地提高了我们的开发效率。
有17位网友表示赞同!
Android ClassLoader的热修复使得我们能够在不重启应用的情况下更改代码。
有18位网友表示赞同!
对于我们团队来说,这是提升游戏稳定性和快速响应需求的关键技术。
有17位网友表示赞同!
使用Classloader进行热修复后,bug解决速度明显快了很多。
有17位网友表示赞同!
这款功能对于优化用户的游戏体验非常有帮助,减少了因更新带来的不便和等待时间。
有6位网友表示赞同!
对开发人员来说,这是一次学习新技术和提高生产力的机会。
有13位网友表示赞同!
游戏的维护变得更加轻松便捷,特别是在处理兼容性问题时。
有14位网友表示赞同!
热修复技术让我们的团队能够快速响应玩家反馈并提供即时修正。
有18位网友表示赞同!
通过Android ClassLoader实现的热修复简化了我们在多平台上的开发流程。
有18位网友表示赞同!
对于持续改进的开发者来说,这是一种强大的工具。
有5位网友表示赞同!
这使得我们能够在任何时间推出更新,而不影响现有用户的游戏进行。
有7位网友表示赞同!
对于我们而言,是提高服务质量和用户满意度的一个重要步骤。
有5位网友表示赞同!
热修复功能确保了游戏在不断地增强而不会中断用户的体验。
有17位网友表示赞同!
这对于保持用户粘性至关重要,因为它避免了一些常见的下载/重装问题。
有11位网友表示赞同!
这使我们能够快速调整逻辑和优化代码,提高了游戏的整体性能。
有8位网友表示赞同!
对于我们开发者来说,这样的技术是增强项目能力和市场竞争力的必要工具。
有9位网友表示赞同!
随着热修复功能的应用,我们的玩家群体对新版本保持了更高的期待和满意度。
有19位网友表示赞同!
这为未来的技术创新开启了大门,让我们的游戏始终保持在行业前沿。
有10位网友表示赞同!
通过实现这一关键更新,我们成功地提高了用户反馈率,在社区中获得了积极评价。
有19位网友表示赞同!
对于寻求不断迭代和优化的游戏团队来说,这个功能是最有价值的解决方案之一。
有5位网友表示赞同!