Android热修复学习之旅——HotFix完全解析.doc_第1页
Android热修复学习之旅——HotFix完全解析.doc_第2页
Android热修复学习之旅——HotFix完全解析.doc_第3页
Android热修复学习之旅——HotFix完全解析.doc_第4页
Android热修复学习之旅——HotFix完全解析.doc_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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 ClassNotFoundException * if the class can not be found. */public Class loadClass(String className) throws ClassNotFoundException return loadClass(className, false);protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException 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文件的类关于如何进行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() println(=添加构造方法=) def constructor1 = c1.getConstructors()0; constructor1.insertBefore(System.out.println(dodola.hackdex.AntilazyLoad.class);) c1.writeFile(buildDir) static void growl(String title, String message) def proc = osascript, -e, display notification $message with title $title.execute() if (proc.waitFor() != 0) println 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所以,在injectBelowApiLevel14的以下方法中,就是把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 ClassNotFoundExcept

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论