Android热修复——简单实现

原创 2018年04月17日 11:41:30

上一篇文章《Android热修复——实现原理解析》已经分析了android热修复的实现原理,这里来做一个简单的实现。

实现步骤

  1. 拿到修复好的class文件
  2. 转成dex包
  3. 加载dex补丁

先来看一下错误代码

public void clickUserLogin(View view) {
        int i = 1/0;
}

很简单,然后点击的时候会报错,提示0不能是除数

java.lang.IllegalStateException: Could not execute method for android:onClick
Caused by: java.lang.ArithmeticException: divide by zero

然后我们修改成正确代码,编译拿到class文件

public void clickUserLogin(View view) {
        Toast.makeText(this, "正确了", Toast.LENGTH_SHORT).show();
    }

build->rebuild 然后在app->build->intermediates->classes文件夹下找到自己修复好的class,连同整个包名文件夹复制出来(一般是android文件夹下那个)删除掉别的class文件,这里我们复制到桌面的dex文件夹里面。

class文件打包成dex文件

进入sdk build-tools目录。打开当前目录的命令行窗口,输入dx –-dex –-output=C:\Users\Administrator\Desktop\dex\classes2.dex C:\Users\Administrator\Desktop\dex

会生成一个dex文件。

完成补丁加载

这里模拟服务器下发补丁,直接手动把dex文件放在项目文件夹下,然后通过代码完成补丁的加载,这里提供一个大佬写的工具类

public class FixDexUtils {

    private static final String DEX_SUFFIX = ".dex";
    private static final String APK_SUFFIX = ".apk";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    public static final String DEX_DIR = "odex";
    private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
    private static HashSet<File> loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    /**
     * 加载补丁,使用默认目录:data/data/包名/files/odex
     *
     * @param context
     */
    public static void loadFixedDex(Context context) {
        loadFixedDex(context, null);
    }

    /**
     * 加载补丁
     *
     * @param context       上下文
     * @param patchFilesDir 补丁所在目录
     */
    public static void loadFixedDex(Context context, File patchFilesDir) {
        if (context == null) {
            return;
        }
        // 遍历所有的修复dex
        File fileDir = patchFilesDir != null ? patchFilesDir : new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)
        File[] listFiles = fileDir.listFiles();
        for (File file : listFiles) {
            if (file.getName().startsWith("classes") &&
                    (file.getName().endsWith(DEX_SUFFIX)
                            || file.getName().endsWith(APK_SUFFIX)
                            || file.getName().endsWith(JAR_SUFFIX)
                            || file.getName().endsWith(ZIP_SUFFIX))) {
                loadedDex.add(file);// 存入集合
            }
        }
        // dex合并之前的dex
        doDexInject(context, loadedDex);
    }

    private static void doDexInject(Context appContext, HashSet<File> loadedDex) {
        String optimizeDir = appContext.getFilesDir().getAbsolutePath() + File.separator + OPTIMIZE_DEX_DIR;// data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)
        File fopt = new File(optimizeDir);
        if (!fopt.exists()) {
            fopt.mkdirs();
        }
        try {
            // 1.加载应用程序的dex
            PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
            for (File dex : loadedDex) {
                // 2.加载指定的修复的dex文件
                DexClassLoader dexLoader = new DexClassLoader(
                        dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
                        fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
                        null,// 加载dex时需要的库
                        pathLoader// 父类加载器
                );
                // 3.合并
                Object dexPathList = getPathList(dexLoader);
                Object pathPathList = getPathList(pathLoader);
                Object leftDexElements = getDexElements(dexPathList);
                Object rightDexElements = getDexElements(pathPathList);
                // 合并完成
                Object dexElements = combineArray(leftDexElements, rightDexElements);
                // 重写给PathList里面的Element[] dexElements;赋值
                Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错
                setField(pathList, pathList.getClass(), "dexElements", dexElements);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 反射给对象中的属性重新赋值
     */
    private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cl.getDeclaredField(field);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }

    /**
     * 反射得到对象中的属性值
     */
    private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException {
        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        return localField.get(obj);
    }


    /**
     * 反射得到类加载器中的pathList对象
     */
    private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    /**
     * 反射得到pathList中的dexElements
     */
    private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {
        return getField(pathList, pathList.getClass(), "dexElements");
    }

    /**
     * 数组合并
     */
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> componentType = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
        int j = Array.getLength(arrayRhs);// 得到原dex数组长度
        int k = i + j;// 得到总数组长度(补丁数组+原dex数组)
        Object result = Array.newInstance(componentType, k);// 创建一个类型为componentType,长度为k的新数组
        System.arraycopy(arrayLhs, 0, result, 0, i);
        System.arraycopy(arrayRhs, 0, result, i, j);
        return result;
    }
}
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/duolaimila/article/details/79972611

android热修复

  • 2018年03月20日 22:29
  • 36.89MB
  • 下载

浅谈Android主流热修复技术

热修复 热修复作为当下热门的技术,在业界内比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补丁技术和微信的Tinker。最近阿里百川推出的HotFix热修复服务就基于...
  • yangxi_001
  • yangxi_001
  • 2017-02-08 17:35:32
  • 3471

深入探索Android热修复技术原理6.29b-final

  • 2018年01月05日 15:27
  • 25.84MB
  • 下载

Android热修复原理普及

Android热修复原理普及这段时间比较难闲,就抽空研究一下Android热修复的原理。自从Android热修复这项技术出现之后,随之而现的是多种热修复方案的出现。前两天又看到一篇文章分析了几种热修复...
  • u012943767
  • u012943767
  • 2016-08-29 17:40:51
  • 16533

关于Android热修复的几种解决方案

文中引用到的一些博客: http://www.jianshu.com/p/0a31d145cad2https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&...
  • wy12345432452
  • wy12345432452
  • 2017-08-22 17:00:19
  • 402

Android 热修复原理篇及几大方案比较

热修复说白了就是”即时无感打补丁”,比如你们公司上线一个app,用户反应有重大bug,需要紧急修复。2015年以来 ,15年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现...
  • u011200604
  • u011200604
  • 2017-03-03 17:30:33
  • 9539

Android热修复生成patch补丁工具

  • 2018年01月11日 10:54
  • 5.02MB
  • 下载

《深入探索Android热修复技术原理》安卓热修复原理宝典出炉,阿里技术大牛联袂推荐

继《阿里巴巴Java开发手册》后,阿里为开发者带来了第二份重磅大礼:业界首部安卓热修复原理书籍——《深入探索Android热修复技术原理》,该书为阿里巴巴手淘技术团队撰写,现已免费开放下载。 ...
  • Jason_996
  • Jason_996
  • 2017-07-03 12:41:53
  • 795

Android热修复动手实现

一、前言        最近看了很多第三方的热修复框架的实现,比如阿里的AndFix,对于我们在自己的app里面接入SDK很是方便,至于内部的实现基本不需要我们怎么关注都可以。如此,...
  • luoshaoyun
  • luoshaoyun
  • 2017-01-07 21:42:58
  • 719

最全面的Android热修复技术

希望大家通过本文不仅能够全面的了解各项热补丁技术的优缺点,同时也能对它的应用场景有着更加全面的认识。在此基础上,大家或许能更容易的决定是否在自己的项目中使用热补丁技术,以及应当如何使用它。...
  • u010299178
  • u010299178
  • 2016-07-26 01:10:35
  • 6349
收藏助手
不良信息举报
您举报文章:Android热修复——简单实现
举报原因:
原因补充:

(最多只允许输入30个字)