Android热修复学习

上传人:m**** 文档编号:187615450 上传时间:2023-02-16 格式:DOCX 页数:12 大小:56.14KB
返回 下载 相关 举报
Android热修复学习_第1页
第1页 / 共12页
Android热修复学习_第2页
第2页 / 共12页
Android热修复学习_第3页
第3页 / 共12页
点击查看更多>>
资源描述
Android热修复学习之旅HotFix完全解析Android dex 分包原理介绍QQ 空间热修复方案基于 Android dex 分包基础之上,简单概述 android dex 分包的原理就是: 就是把多个 dex 文件塞入到 app 的 classloader 之中,但是 android dex 拆包方案中的类是没 有重复的,如果 classes.dex 和 classes1.dex 中有重复的类,当 classes.dex 和 classes1.dex 中都 具有同一个类的时候,那么classloader会选择加载哪个类呢?这要从classloader的源码入手, 加载类是通过classloader的loadClass方法实现的,所以我们看一下loadClass的源码:/* Loads the class with the specified name. Invoking this method is* equivalent to calling code loadClass(className, false).* * Note: In the Android reference implementation, the* second parameter of link #loadClass(String, boolean) is ignored* anyway.* * return the code Class object.* param className* the name of the class to look for.* throws ClassNotFoundExceptionif the class can not be found.*/public Class loadClass(String className) throws ClassNotFoundException return loadClass(className, false);protected Class loadClass(String className, boolean resolve) throwsClassNotFoundException Class clazz = findLoadedClass(className);if (clazz = null) ClassNotFoundException suppressed = null; try 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 用 loadClass 方法调用了 findClass 方法,点进去发现 findClass 是 抽象方法,而这个方法的实现是在它的子类 BaseDexClassLoader 中,而 BaseDexClassLoader 重载了这个方法,得到 BaseDexClassLoader,进入到 BaseDexClassLoader 类的 findClass 方 法中#BaseDexClassLoaderOverrideprotected Class findClass(String name) throws ClassNotFoundException Class clazz = pathList.findClass(name);if (clazz = null) throw new ClassNotFoundException(name);return clazz;#DexPathListpublic 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;#DexFilepublic Class loadClassBinaryName(String name, ClassLoader loader) return defineClass(name, loader, mCookie);private native static Class defineClass(String name, ClassLoader loader, int cookie);一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列 成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历 的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类所以,QQ空闾正是基TClassLoader的这个原理,把有问题的类订包到一个(patch, dex )中去然后把这个d妙插展, 到Elements的摄前面)aElements关于如何进行dex分包后面再单独开一篇博客进行分析。CLASS_ISPREVERIFIED 的问题采用dex分包方案会遇到的问题,也就是CLASS_ISPREVERIFIED的问题,简单来概括就 是:在虚拟机启动的时候,当verify选项被打开的时候,如果static方法、private方法、构造函 数等,其中的直接引用(第一层关系)到的类都在同一个dex文件中,那么该类就会被打上 CLASS_ISPREVERIFIED 标志。那么,我们要做的就是,阻止该类打上CLASS_ISPREVERIFIED的标志。注意下,是阻止引用者的类,也就是说,假设你的app里面有个类叫做AClass,再其内部 引用了 BClass。发布过程中发现BClass有编写错误,那么想要发布一个新的BClass类,那 么你就要阻止AClass这个类打上CLASS_ISPREVERIFIED的标志。也就是说,你在生成apk之前,就需要阻止相关类打上CLASS_ISPREVERIFIED的标志了。 如何阻止,简单来说,让AClass在构造方法中,去引用别的dex文件,比如:C.dex中的某 个类即可。所以总结下来,防止这个错误,只需要:1、动态改变BaseDexClassLoader对象间接引用的dexElements; 2、在app打包的时候,阻 止相关类去打上CLASS_ISPREVERIFIED标志。热修复框架HotFix解析采用QQ空间的热修复方案而实现的开源热修复框架就是HotFix,说到了使用dex分包方案 会遇到CLASS_ISPREVERIFIED问题,而解决方案就是在dx工具执行之前,将所有的class 文件,进行修改,再其构造中添加 System.out.println(dodola.hackdex.AntilazyLoad.class),然 后继续打包的流程。注意:AntilazyLoad.class这个类是独立在hack.dex中。dex分包方案实现需要关注以下问题:1. 如何解决 CLASS_ISPREVERIFIED 问题2. 如何将修复的.dex文件插入到dexElements的最前面那么如何达到这个目的呢?在HotFix中采用的javassist来达到这个目的,以下是HotFix中 的 PatchClass.groovy 代码public class PatchClass /* 植入代码* param buildDir 是项目的 build class 目录,就是我们需要注入的 class 所在地* param lib 这个是 hackdex 的目录,就是 AntilazyLoad 类的 class 文件所在地*/public static void process(String buildDir, String lib) println(lib)ClassPool classes = ClassPool.getDefault() classes.appendClassPath(buildDir) classes.appendClassPath(lib)/下面的操作比较容易理解,在将需要关联的类的构造方法中插入引用代码CtClass c = classes.getCtClass(dodola.hotfix.BugClass)if (c.isFrozen() c.defrost()println(=添加构造方法=)def constructor = c.getConstructors()0; constructor.insertBefore(System.out.println(dodola.hackdex.AntilazyLoad.class);) c.writeFile(buildDir)CtClass c1 = classes.getCtClass(dodola.hotfix.LoadBugClass)if (c1.isFrozen() c1.defrost()printin(”=添加构造方法=)def constructor1 = c1.getConstructors()0; constructor1.insertBefore(System.out.printin(dodoia.hackdex.AntiiazyLoad.ciass);) c1.writeFiie(buiidDir)static void growi(String titie, String message) def proc = osascript, -e, dispiay notification $message with titie $titie.execute()if (proc.waitFor() != 0) printin WARNING $proc.err.text.trim()其实内部做的逻辑就是:通过ClassPool对象,然后添加classpath。然后从classpath中找到 LoadBugClass,拿到其构造方法,在其中插入一行代码。到这里插入代码的操作已经完成,但是还存在另外一个问题,那就是如何在dx之前去进行 上述脚本的操作?答案就在 HotFix 的 app/build.gradle 中apply plugin: com.android.applicationtask(processWithJavassist) variant.dex.dependsOn 0) dexWriter.write(buf, 0, len);dexWriter.close();bis.close();return true; catch (IOException e) if (dexWriter != null) try dexWriter.close(); catch (IOException ioe) ioe.printStackTrace();if (bis != null) try bis.close(); catch (IOException ioe) ioe.printStackTrace();return false;接下来 HotFix.patch 就是去反射去修改 dexElements 了 public static void patch(Context context, String patchDexFile, String patchClassName) if (patchDexFile != null & new File(patchDexFile).exists() try if (hasLexClassLoader() injectInAliyunOs(context, patchDexFile, patchClassName); se if (hasDexClassLoader() injectAboveEqualApiLevel14(context, patchDexFile, patchClassName); else injectBelowApiLevel14(context, patchDexFile, patchClassName); catch (Throwable th) 可以看到 patch 方法中有几个分支,说白了是根据不同的系统中 ClassLoader 的类型来做相 应的处理private static void injectInAliyunOs(Context context, String patchDexFile, String patchClassName)throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,InstantiationException, NoSuchFieldException PathClassLoader obj = (PathClassLoader) context.getClassLoader();String replaceAll = new File(patchDexFile).getName().replaceAll(.a-zA-Z0-9+, .lex);Class cls = Class.forName(dalvik.system.LexClassLoader);Object newInstance =cls.getConstructor(new Class String.class, String.class, String.class, ClassLoader.class).newInstance(new Object context.getDir(dex, 0).getAbsolutePath() + File.separator + replaceAll,context.getDir(dex, 0).getAbsolutePath(), patchDexFile, obj);cls.getMethod(loadClass, new Class String.class).invoke(newInstance, new Object patchClassName);setField(obj, PathClassLoader.class, mPaths,appendArray(getField(obj, PathClassLoader.class, mPaths), getField(newInstance, cls, mRawDexPath);setField(obj, PathClassLoader.class, mFiles,combineArray(getField(obj, PathClassLoader.class, mFiles), getField(newInstance, cls, mFiles);setField(obj, PathClassLoader.class, mZips,combineArray(getField(obj, PathClassLoader.class, mZips), getField(newInstance, cls, mZips);setField(obj, PathClassLoader.class, mLexs, combineArray(getField(obj, PathClassLoader.class, mLexs), getField(newInstance, cls, mDexs);上述方法中的LexClassLoader应该是阿里自己的ClassLoader,可以看到上面将修复的文件 的结尾都换成了.lex的结尾,这些文件就是专门需要通过LexClassLoader进行加载的我们分 API 14以上和以下进行分析API 14 以下private static void injectBelowApiLevel14(Context context, String str, String str2)throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException PathClassLoader obj = (PathClassLoader) context.getClassLoader();DexClassLoader dexClassLoader =new DexClassLoader(str, context.getDir(dex, 0).getAbsolutePath(), str, context.getClassLoader();dexClassLoader.loadClass(str2);setField(obj, PathClassLoader.class, mPaths, appendArray(getField(obj, PathClassLoader.class, mPaths), getField(dexClassLoader, DexClassLoader.class,mRawDexPath) );setField(obj, PathClassLoader.class, mFiles, combineArray(getField(obj, PathClassLoader.class, mFiles), getField(dexClassLoader, DexClassLoader.class,mFiles) );setField(obj, PathClassLoader.class, mZips, combineArray(getField(obj, PathClassLoader.class, mZips), getField(dexClassLoader, DexClassLoader.class,mZips);setField(obj, PathClassLoader.class, mDexs, combineArray(getField(obj, PathClassLoader.class, mDexs), getField(dexClassLoader, DexClassLoader.class,mDexs);obj.loadClass(str2);通过 setField 方法将 mPaths 属性,修改为通过 appendArray 方法创造的新元素private static Object getField(Object obj, Class cls, String str)throws NoSuchFieldException, IllegalAccessException Field declaredField = cls.getDeclaredField(str); declaredField.setAccessible(true);return declaredField.get(obj);private static Object appendArray(Object obj, Object obj2) Class componentType = obj.getClass().getComponentType();int length = Array.getLength(obj);Object newInstance = Array.newInstance(componentType, length + 1); Array.set(newInstance, 0, obj2);for (int i = 1; i length + 1; i+) Array.set(newInstance, i, Array.get(obj, i - 1); return newInstance;而appendArray中就是创建一个新的Array,把obj2插入到obj的前面,注意这里的obj2长 度只有1所以,在injectBelowApiLevell4的以下方法中,就是把mRawDexPath的元素插入到mPaths 中所有元素之前,而重新组合而成的新mPaths替换掉旧的mPathssetField(obj, PathClassLoader.class, mPaths,appendArray(getField(obj, PathClassLoader.class, mPaths), getField(dexClassLoader, DexClassLoader.class,mRawDexPath);接下来的替换,是通过combineArray生成的新元素替换掉旧元素,这里分别是mFiles, mZips, mDexssetField(obj, PathClassLoader.class, mFiles,combineArray(getField(obj, PathClassLoader.class, mFiles), getField(dexClassLoader, DexClassLoader.class,mFiles);setField(obj, PathClassLoader.class, mZips,combineArray(getField(obj, PathClassLoader.class, mZips), getField(dexClassLoader, DexClassLoader.class,mZips);setField(obj, PathClassLoader.class, mDexs,combineArray(getField(obj, PathClassLoader.class, mDexs), getField(dexClassLoader, DexClassLoader.class,mDexs);于是我们需要看一下 combineArray 方法里面做了什么private static Object combineArray(Object obj, Object obj2) Class componentType = obj2.getClass().getComponentType();int length = AtLength(obj2);int length2 = Array.getLength(obj) + length;Object newInstance = Array.newInstance(componentType, length2);for (int i = 0; i length2; i+) if (i length) Array.set(newInstance, i, Array.get(obj2, i); else Array.set(newInstance, i, Array.get(obj, i - length);return newInstance;逻辑也很简单,也就是两个数组的合并而已API14 以上private static void injectAboveEqualApiLevel14(Context context, String str, String str2) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader(); Object a = combineArray(getDexElements(getPathList(pathClassLoader), getDexElements(getPathList(new DexClassLoader(str, context.getDir(dex, 0).getAbsolutePath(), str, context.getClassLoader();Object a2 = getPathList(pathClassLoader);setField(a2, a2. getClass(), dexElements, a); pathClassLoader.loadClass(str2);根 据 context 拿 到 PathClassLoader , 然 后 通 过 getPathList(pathClassLoader) , 拿 到 PathClassLoader 中的 pathList 对象,在调用 getDexElements 通过 pathList 取到 dexElements 对象。private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException return getField(obj, obj.getClass(), dexElements);private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,IllegalAccessException return getField(obj, Class.forName(dalvik.system.BaseDexClassLoader), pathList);同样是通过 combineArray 方法,对数组进行合并,合并完成后,将新的数组通过反射的方 式设置给 pathList.通过上面的一系列流程,那么 hack_dex.jar 已经插入到 dexElements 最前面了,补丁插入的 过程也和 hack_dex.jar 的插入流程是一致的到这里,dex分包方案实现热修复的HotFix的分析就已经完毕了。
展开阅读全文
相关资源
相关搜索

最新文档


当前位置:首页 > 建筑环境 > 机械电气


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!