前言

(废话) 最近发现了一个问题,一些平时博客写的很多的程序员,反倒在日常的工作中,却是业务写的很一般,只会摆理论的人,甚至还跑出来教别人如何找工作,如何做架构,其实自己都没搞明白。但是受众的分层导致了输出者的分层,教出清北学生的老师并不一定来自比这更好的学校,因此,对于博客的输出,一个是作为对自己学习的一个记录,非常仔细的梳理可以非常方便的让我们在需要的时候拿起来,再就是即使这个知识现在不用,无法深入下去,可能会遗忘的比较快,其细节可能会忘记,但是核心思想我们还是有印象的,再就是站在读者的角度上来看,由于读者的差异性,我们的博客在保证无误的前提下,一定是可以帮助到很多同学的,本着这些原则,一周一篇的输出,希望可以坚持下去。

(正题) 最近在做热修复的相关调研,接着博客的专题,可以好好的发一波文章了,今天要分析的是ClassLoader方案最简单的一个Nvwa,本篇文章将会从class查找过程到Nvwa的实现,以及在实现的时候解决了什么问题,这几个方面展开,逐步讲解。

基础使用

初始化

Nuwa.init(this);
复制代码

装载补丁包

Nuwa.loadPatch(this,patchFile)
复制代码

源码分析

类的查找

对于类的加载,在通过DexClassLoader进行加载的时候,通过DexPathList进行加载,其中维护了一个Element的数组,在查找的类的时候,会遍历数组查找类,如果找到则返回。对于数组遍历查找的代码如下所示。

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;
}
复制代码
  • Nvwa的初始化
public static void init(Context context) {File dexDir = new File(context.getFilesDir(), DEX_DIR);dexDir.mkdir();String dexPath = null;try {//拷贝Asset目录下的Hack.apk到指定路径dexPath = AssetUtils.copyAsset(context, HACK_DEX, dexDir);} catch (IOException e) {e.printStackTrace();}//从拷贝后的指定路径加载apkloadPatch(context, dexPath);
}
复制代码

在nvwaw的init方法中进行的操作是将asset中的一个hack.apk拷贝出来,然后将其作为补丁进行装载。

  • 补丁的加载
public static void loadPatch(Context context, String dexPath) {if (context == null) {return;}if (!new File(dexPath).exists()) {return;}File dexOptDir = new File(context.getFilesDir(), DEX_OPT_DIR);dexOptDir.mkdir();try {DexUtils.injectDexAtFirst(dexPath, dexOptDir.getAbsolutePath());} catch (Exception e) {e.printStackTrace();}
}
复制代码

由上面代码,可以看出核心的实现在对DexUtilsinjectDexAtFirst调用上。

public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));Object newDexElements = getDexElements(getPathList(dexClassLoader));Object allDexElements = combineArray(newDexElements, baseDexElements);Object pathList = getPathList(getPathClassLoader());ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
}
复制代码

将两个Dex进行合并,将补丁dex塞在数组的前面,然后通过反射的方式设置进去,通过这种方式,根据上面的类加载逻辑,可以知道,对于类的加载是从数组的最开始的位置进行查找加载的,当前面的dex查找到相应的类之后,就会停止后面的查找,这样,我们通过补丁的替换的类就会生效。

private static Object combineArray(Object firstArray, Object secondArray) {Class<?> localClass = firstArray.getClass().getComponentType();int firstArrayLength = Array.getLength(firstArray);int allLength = firstArrayLength + Array.getLength(secondArray);Object result = Array.newInstance(localClass, allLength);for (int k = 0; k < allLength; ++k) {if (k < firstArrayLength) {Array.set(result, k, Array.get(firstArray, k));} else {Array.set(result, k, Array.get(secondArray, k - firstArrayLength));}}return result;
}
复制代码

存在问题

通过上述的方式,我们将差异补丁单独打一个包,然后进行下发,从而使得我们的类得到修复。但是在这样实现的时候,存在一个问题,就是 在Dalvik 虚拟机对于dex的一个优化。Dalvik 虚拟机在启动的时候,会有许多的启动参数,其中有一项就是verify,当verify被打开的时候,doVerify变量为true,则进行类的校验(dvmVerifyClass方法调用)。若校验成功,则这个类会被打上标记:CLASS_ISPREVERIFIED。

这么做,是防止外部DEX注入的一个安全方案,即保证运行期的Class与其直接引用类之间所在的DEX关系要与安装时候一致,也是为了防止类被篡改校验类的合法性。Dalvik 虚拟机在安装期间,为Class 打上 CLASS_ISPREVERIFIED 是为了提高性能,下次使用时,则会省去校验操作,提高访问效率。dvm在运行期载入Class时候,会对其内存中对应的直接引用类进行校验,如果该类存在与直接引用类所在的dex不是同一个,则直接报“pre-verification” 错误,该类无法加载。

由于这一个限制,导致我们的补丁包无法在被调用到的时候,就会抛出异常,因此我们需要让我们的补丁包,如何通过这次校验, 不被打上CLASS_ISPREVERIFIED,这样,我们的补丁包在被加载的时候,就不会抛出异常了。

nvwa采取的方式就是插桩的方式,在每一个类里去引用到另一个独立dex中的类,也会是在初始化的时候加载的hack.apk中的Hack.class,通过这种方式,可以让我们的类不会被打上这个标签。这样就可以继续装载其它Dex中的类。

插桩存在一个什么问题呢?由于没有打上验证标签,导致每个类的装载的时候都进行验证。

微信在对插装和不插桩做的测试中。在连续加载700个50行的类,还有统计应用启动耗时得到的数据,700个类:不插桩:84ms,插桩:685ms。启动耗时:4934ms,7240ms。

结语

每周一更,由于最近业务需求较多,更新速度明显慢了很多了,因此本篇分析的也是一很简单的框架。接下来,将会逐步深入,分析一些更为复杂的热修复方案框架。

参考资料

Dalvik中PreVerify问题

Android每周一轮子:Nvwa(热修复)相关推荐

  1. 《android基于andFix的热修复方案》思路篇

    1:需求背景 项目上线之后,发现BUG需要修复(比如安卓兼容性等测试难以发现的问题),频繁的更新影响用户体验 2:方案要求 静默下载,耗费流量少,打完补丁后立刻生效,不用重启apk 3:解决思路 3. ...

  2. Android App 线上热修复方案

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  3. Android 腾讯 Bugly 热修复

    依赖配置流程 Bugly 文档中心 引入依赖 在项目的 build.gradle 中加入依赖 buildscript {repositories {jcenter()google()}dependen ...

  4. Android App 线上热修复方案Xposed

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  5. Android开发之nuwa热修复

    相信每个开发者在app版本上线后才发现有一个致命性崩溃的bug时,心中是一万只草泥马在奔跑! 每次发现这种bug,都只好立马上个小版本修复.这种体验着实糟糕. 那我们能不能动态加载一小部分代码来修复这 ...

  6. 《android基于andFix的热修复方案》实战篇

    有篇文章说的比较简洁,大家可以参考下:AndFix使用说明 下面说说实际使用中遇到的问题 1:如何继承到gradle项目中 dependencies {compile 'com.alipay.eule ...

  7. android热补丁作用,Android热修复之 - 阿里开源的热补丁

    这里就有一个概念那就AndFix.apatch补丁用来修复方法,接下来我们看看到底是怎么实现的. 1.2 生成apatch包 假如我们收到了用户上传的崩溃信息,我们改完需要修复的Bug,这个时候就会有 ...

  8. Tinker 热修复框架模拟使用

    导言 前不久,腾讯推出了"微信小程序"这一概念,对移动原生应用的影响可谓巨大.而几乎就在同时, 腾讯在GitHub上开源了第一个项目Tinker, 这是一个Android平台的应用 ...

  9. 【大牛系列教学】Android热修复原理,满满干货指导

    优秀的战士需要出色的剑才能战斗.同样,在现代IT中,每个编码人员都需要最好的Android开发人员工具来提高他们的技能和效率.在Android应用程序开发这个残酷的竞争行业中,只有优秀的开发人员才能生 ...

最新文章

  1. 那些年值得铭记的时刻
  2. SQL Server 性能基线和监控
  3. C语言数组,这个输出模式感觉自己很厉害!_只愿与一人十指紧扣_新浪博客
  4. android自定义tab下划线变大,Android开发之设置TabLayout下方下划线的宽度
  5. 6 个“吓人”的 Linux 命令
  6. hdu5343 后缀自动机+dp
  7. eclipse提示方法已过时_提高效率,eclipse上你可能不知道的技巧
  8. 文件服务器的文件设置只读,服务器设置文件为只读
  9. Zookeeper和CAP的关系
  10. 支持javascript的ppt软件_强大CSS3的3D幻灯片工具reveal.js(推荐)
  11. channelinactive触发后不关闭channel_Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作...
  12. 出海业务网络加速方案技术能力详解
  13. 汉诺塔c 语言程序代码,汉诺塔 (C语言代码)
  14. windows 计算机 快捷键,Windows7计算器快捷键汇总
  15. C语言学生档案表设计——作业
  16. linux 磁盘隔离,Linux 磁盘坏道故障修复
  17. Unity 调用DLL
  18. Linux MTD子系统学习(二)
  19. Flutter开发IOS,上架AppStore的全部流程以及常遇到的坑
  20. 计算机休眠拖动鼠标不起作用,电脑待机后按鼠标无法唤醒怎么办

热门文章

  1. 免费素材:包含 250+ 组件的 DO UI Kit
  2. tomcat 部署指南
  3. UITableViewDataSource Protocol Reference
  4. ZOJ2833*(并查集)
  5. 美国本科申请Essay的写作注意事项
  6. 欧拉筛+埃式筛求素数
  7. Redis数据结构04-SortedSet
  8. rsync工具介绍,rsync常用选项,rsync通过ssh同步
  9. 如何结合短信和邮件有效的监控网站
  10. 局域网共享设置——权限问题