Android热更新

组件化

组件化和模块化其实一回事,都是拆分多个 module 进行开发,组件化的叫法更偏向封装系统功能,比如统一对话框封装,网络封装等,而模块化叫法更偏向业务方面,比如登录模块等。

插件化

App 的部分功能模块在打包的时候不以传统的方式打包进 apk 中,而是另一种方式封装到apk 内部,或者放在网络上适时下载,在需要的时候动态对这些功能模块进行加载,称之为插件化。

这些单独二次封装的功能模块 apk,称为插件,初始安装的 apk 称为宿主。

插件化基础是反射。

反射

Java 提供了关键字 public、private 用来限制代码之间的可见性,但又提供了反射来访问不可见方法。

可见特性的支持并不是为了保护代码不被别人使用,而是为了程序开发的简洁性。可见性的的支持提供的是 Safety 的安全,而不是 Security 的安全。可见性的支持是让程序的开发者不容易写出 bug ,而不是更容易外部被入侵。

反射的支持可以让开发者在可见性的不对外的时候,突破可见性的限制来调用自己需要的 API.

反射的使用:

package com.reflect.utils;
class Utils {private Utils(){}private int method(int a) {System.out.println("Utils.method打印了... a:" + a);return 10;}
}// 通过反射调用 Utils 中的方法
try {Class utilsClass = Class.forName("com.reflect.utils.Utils");// Utils.classConstructor constructor = utilsClass.getDeclaredConstructor();constructor.setAccessible(true);// 构造方法设置为可以访问Object utilsObj = constructor.newInstance();Method method = utilsClass.getDeclaredMethod("method", int.class);method.setAccessible(true);// 方法设置为可访问Object result = method.invoke(utilsObj, 1);System.out.println(result);
} catch (NoSuchMethodException e) {e.printStackTrace();
} catch (IllegalAccessException e) {e.printStackTrace();
} catch (InvocationTargetException e) {e.printStackTrace();
} catch (InstantiationException e) {e.printStackTrace();
} catch (ClassNotFoundException e) {e.printStackTrace();
}
DEX

class:java 编译后的文件,每一个类对应一个 class 文件。

dex:Dalvik Executable 把 class 打包到一起,一个 dex 可以包含多个 class 文件。

odex:Optimized DEX 针对系统的优化,例如某个方法的调用指令,会把虚拟机的调用转换为使用具体的 index,这样在执行的就不用再查找了。

oat:Optimized Android file Type,使用 AOT(Ahead-Of-Time compilation预先编译) 策略对 dex 预先编译成本地指令,这样在运行阶段就不需要再经历一次解释过程。

插件化原理

动态加载,通过自定义 ClassLoader 来加载新的 dex 文件,从而让程序员原本没有的类可以被使用。

private void pluginLoad() {File pluginApk = new File(getFilesDir() + "/plugin.apk");AssetManager assets = getAssets();try(Source source = Okio.source(assets.open("plugin.apk"));BufferedSink sink = Okio.buffer(Okio.sink(pluginApk))) {sink.writeAll(source);} catch (IOException e) {e.printStackTrace();}// 调用 plugin.apk 中的方法DexClassLoader classLoader = new DexClassLoader(pluginApk.getAbsolutePath(),getCodeCacheDir().getPath(),null,null);try {Class<?> utilClass = classLoader.loadClass("com.chen.hotfix.utils.Utils");Constructor<?> constructor = utilClass.getDeclaredConstructors()[0];constructor.setAccessible(true);Object utilObj = constructor.newInstance();Method getNameMethod = utilClass.getDeclaredMethod("getName");getNameMethod.setAccessible(true);Object name = getNameMethod.invoke(utilObj);Log.e("TAG", "name:" + name);} catch (ClassNotFoundException| InvocationTargetException| IllegalAccessException| InstantiationException| NoSuchMethodException e) {e.printStackTrace();}
}
插件化和热更新
  1. 插件化的内容在原 App 中没有,而热更新是原 App 中的内容做了改动
  2. 插件化在代码中有固定的入口,而热更新则可能改变了任何有一个位置的代码
热更新的原理

ClassLoader 的 dex 文件的替换,直接修改字节码

类加载过程

双亲委托机制,是一个带缓存的,从上到下(从父到子)的加载过程。

//ClassLoader.java
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 首先判断该类型是否已经被加载Class c = findLoadedClass(name);if (c == null) {//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载try {if (parent != null) {//如果存在父类加载器,就委派给父类加载器加载c = parent.loadClass(name, false);} else {//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)c = findBootstrapClass0(name);}} catch (ClassNotFoundException e) {// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能c = findClass(name);}}if (resolve) {resolveClass(c);}return c;
}

对于一个具体的 ClassLoader,先从自己的缓存中取,自己缓存中没有,就找父 ClassLoader 要(parent.loadClass()),父 View 也没有,就自己加载。(父View的加载流程和自己是一样的)

BaseDexClassLoader 的 findClass() 是通过它的 pathList.findClass(),它的 pathList.loadClass() 通过 DexPathList 的 dexElements 的 findClass(),所以热更新的关键在于,把补丁 dex 文件加载放进一个 Element,并且插入到 dexElements 这个数组的前面。

手写热更新

因为无法在更新之前就指定要更新谁,所以不能定义新的 ClassLoader,而只能选择对 ClassLoader 进行修改,让它能够加载补丁里面的类。

因为补丁中的类在之前的应用中已经存在,所以应该把补丁中的 Element 对象插入到 dexElements 的前面才行,插入到后面会被忽略掉。

具体的做法:

  1. 用补丁创建一个 PathClassLoader
  2. 把补丁 PathClassLoader 里面的 elements 插入到旧的 dexElements 前面去

尽早加载热更新,通常是把加载过程放在 Application.attachBaseContext()
热更新下载完之后需要先杀死程序才能让补丁生效
用 d8 把指定的 class 打包进 dex

sample

HotfixApplication

public class HotfixApplication extends Application {@Overridepublic void onCreate() {super.onCreate();}@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);// 通过反射将dex的dexElements插入到应用的dexElements前面,使热更新(hotfix.dex)生效File hotfixDex = new File(getFilesDir() + "/hotfix.dex");if (hotfixDex.exists()) {try {// originalLoader.pathList.dexElements = classLoader.pathList.dexElements;// originalLoader.pathList.dexElements += classLoader.pathList.dexElements;ClassLoader originalClassLoader = getClassLoader();// App的ClassLoaderDexClassLoader newClassLoader = new DexClassLoader(hotfixDex.getPath(),getCodeCacheDir().getPath(), null, null);// dex的ClassLoaderClass<BaseDexClassLoader> loaderClass = BaseDexClassLoader.class;Field pathListField = loaderClass.getDeclaredField("pathList");pathListField.setAccessible(true);Object pathListObj = pathListField.get(newClassLoader);Class<?> pathListClass = pathListObj.getClass();Field dexElementsField = pathListClass.getDeclaredField("dexElements");dexElementsField.setAccessible(true);Object dexElementsObj = dexElementsField.get(pathListObj);Object originalPathListObj = pathListField.get(originalClassLoader);Object originalDexElementsObj = dexElementsField.get(originalPathListObj);int originalLength = Array.getLength(originalDexElementsObj);int newLength = Array.getLength(dexElementsObj);Object concatDexElementsObject = Array.newInstance(dexElementsObj.getClass().getComponentType(), originalLength + newLength);for (int i = 0; i < newLength; i++) {Array.set(concatDexElementsObject, i, Array.get(dexElementsObj, i));}for (int i = 0; i < originalLength; i++) {Array.set(concatDexElementsObject, newLength + i, Array.get(originalDexElementsObj, i));}dexElementsField.set(originalPathListObj, concatDexElementsObject);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}}}

下载热更新包

private void hotfixLoad() {File hotfixDex = new File(getFilesDir() + "/hotfix.dex");AssetManager assets = getAssets();try (Source source = Okio.source(assets.open("hotfix.dex"));//TODO 需要从网络下载hotfix.dexBufferedSink sink = Okio.buffer(Okio.sink(hotfixDex))) {sink.writeAll(source);} catch (IOException e) {e.printStackTrace();}android.os.Process.killProcess(android.os.Process.myPid());// 重启生效
}

Android热更新相关推荐

  1. android热更新插件,与Android热更新方案Amigo的再次接触

    Amigo 作为一个"过气"的的热修复框架,用来学习和了解一下热修复的基本原理还是很好的.本文是本系列的第三篇. 前两篇: 与Android 热更新方案Amigo的初次接触 原作者 ...

  2. android 上下偏差怎么写_详解 Android 热更新升级如何突破底层结构差异?

    知道了 native 替换方式兼容性问题的原因,我们是否有办法寻求一种新的方式,不依赖于 ROM 底层方法结构的实现而达到替换效果呢? 我们发现,这样 native 层面替换思路,其实就是替换 Art ...

  3. bugly android8.1加固,2020-09-27 Bugly Android热更新使用指南

    戳我查看 DEMO Bugly Android热更新使用指南 官方文档 视频教程 第一步:添加插件依赖 工程根目录下"build.gradle"文件中添加: buildscript ...

  4. Android热更新初探,Bugly热更新的集成和使用(让你的应用轻松具备热更新能力)

    介绍   在介绍Bugly之前,需要先向大家简单介绍下一些热更新的相关内容.当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix.美团的Robust以及QZone的超级补丁方案.但它们都存在 ...

  5. android 热更新 方案,热更新-热更新app开发的两种系统方案!

    针对app开发工作人员来讲,除开要会编码,热更新也是一定要学好和把握的方法,从技术性视角而言,热更新对Android和iOS各自有不一样的系统软件方案,为了更好地让大伙儿掌握这二种系统方案的差别,今日 ...

  6. Android热更新十:自己写一个Android热修复

    很早之前就想深入的研究和学习一下热修复,由于时间的原因一直拖着,现在才执笔弄起来. Android而更新系列: Android热更新一:JAVA的类加载机制 Android热更新二:理解Java反射 ...

  7. Android热更新五:四大热修复方案对比分析

    很早之前就想深入的研究和学习一下热修复,由于时间的原因一直拖着,现在才执笔弄起来. Android而更新系列: Android热更新一:JAVA的类加载机制 Android热更新二:理解Java反射 ...

  8. Android热更新技术的研究与实现Sophix

    所以阿里爸爸一直在进步着呢,知道技术存在问题就要去解决问题,这不,从Dexposed-->AndFix-->HotFix-->Sophix,技术是越来越成熟了. Android热更新 ...

  9. Android热更新研究与实现

    第一部分重点是将当下热门的热更新方案实现之后再研究,第二部分则是自己动手实现一个自己的热更新框架. Android热更新技术的研究与实现之研究篇 ---概念讲解--– 热更新 相关概念 这个词出现的时 ...

最新文章

  1. Hive用户权限管理理解
  2. Java 中long类型转换成为int类型时可能会出错的地方
  3. Android 监听APP进入前台、后台
  4. 使用Lombok简化开发及无效解决方案
  5. SQLite入门之数据类型
  6. 为什么Android Geeks购买Nexus设备
  7. Android Ac 控件,Android控件--MultiAutoCompleteTextView
  8. 成都理工大学工程技术学院计算机专业收分线,2019年成都理工大学工程技术学院美术类专业录取分数线...
  9. Java之Set接口
  10. luogu P3810 【模板】三维偏序(陌上花开)
  11. 用HTML代码实现个人简历的编写
  12. d3js精通教程_d3js从基础到精通第二部分
  13. NiceChord好和弦——和弦符号全解
  14. lua 斗地主癞子牌型检测中使用递归
  15. 让canvas绘图更清晰
  16. 小记 xian80 坐标转换 wgs84
  17. VideoMAE 论文阅读
  18. 腾讯云图,让数据说话
  19. sql查询一个班级中男女各有多少人及总人数
  20. 面试篇-- Http、TCP/IP协议与Socket之间的区别

热门文章

  1. 【随笔】在CSDN的第一年,你好,桐小白~ —— 在CSDN的一岁生日
  2. 玩抖音必知的3个数据分析工具:会用它们,少走很多弯路
  3. PYNQ系列学习(三)|pynq与zynq对比(二)
  4. 【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)
  5. 再来聊聊Redis到底是什么?
  6. c语言流水调度作业,最简C语言流水灯程序,给初学者看下,高手请绕行。
  7. Matlab之保存/写矩阵数据到文本(fprintf, save, dlmwrite)
  8. 字幕滚动效果---非常酷哦
  9. 比较两个字符串s1和s2的大小,如果s1s2,则输出一个正数;若s1=s2,则输出0;若s1小于s2,则输出一个负数。要求:不用strcpy函数;两个字符串用gets函数读入。
  10. 【PADS_002】【添加泪滴】