替换dex实现热修复

来源:互联网 发布:幂等矩阵的秩和迹 编辑:程序博客网 时间:2024/06/05 20:22

安卓App热补丁动态修复技术介绍

本文就是对上面的原理进行简单实现。
主要思想:
首先一个确保apk是由多个dex组成的,dex1、dex2、dex3等。
dex1一般会包含application等。

假如dex2中出现了bug,那么我们可以修复相应的bug,生成对应的newdex2,然后将newdex2放置到dexements数组的前面。

那么其他方法调用dex2中的方法时,会先从数组的由前往后遍历,如果在newdex2中找到了对应的方法,就不再向后读取dex2.

所以实现的关键在于:
1.apk多分包
2.修复bug后,生成patch.dex
3.将patch.dex插入到dex2前面

apk多分包

这里写图片描述

这里写图片描述

上面124步骤,即可实现多dex分包。

那么3是什么意思呢?
首先dex.keep

com.study.dexhotfix/MainActivity.classcom.study.dexhotfix/MyApplication.class

multiDexKeepFile file(‘dex.keep’)的作用就是将dex.keep定义的类放到第一个dex中

生成patch.dex

先看一下新建项目后的目录结构
这里写图片描述

TestBug.java
修复前

public class TestBug {    public int calculate(){        int i = 0;        int j = 10;        return j/i;    }}

修复后

public class TestBug {    public int calculate(){        int i = 1;        int j = 10;        return j/i;    }}

修复后,Build->Rebuild Project

然后在/Users/likuan/Desktop/ST/DexHotFix/app/build/intermediates/classes/debug/com/study/dexhotfix/TestBug.class中找到TestBug.class

在桌面新建一个文件夹bug,然后将TestBug.class拷贝到bug/com/study/dexhotfix文件下
这里写图片描述
bug文件下的路径,一定要和项目中的路径一致

打开终端,找到/Users/xxx/Library/Android/sdk/build-tools/26.0.2目录(其他也可以)
然后执行如下命令:

./dx --dex --output=/Users/xxx/Desktop/bug/patch.dex /Users/xxx/Desktop/bug

./dx –dex –output={生成的dex目录} {要加载的class文件目录,目录下可以有多个class文件}

然后在bug目录下生成:patch.dex,这就是我们后面要用到的补丁dex

将patch.dex插入到bugdex前面

关键都在BugFixUtils.java

package com.study.dexhotfix;import android.content.Context;import android.os.Environment;import android.util.Log;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.List;import dalvik.system.DexClassLoader;import dalvik.system.PathClassLoader;/** * Created by ygdx_lk on 17/12/4. */public class BugFixUtils {    public static final String DEX_DIR = "odex";    public static final String dexName = "patch.dex";    private static final String TAG = "BugFixUtils";    public static void fixbug(Context context){        String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + dexName;        downLoadPatch(context, patchPath);        loadPatch(context);    }    //加载补丁dex    public static void loadPatch(Context context) {        List<File> dexs = getPatchDexs(context);        if(dexs != null && dexs.size() > 0){            inject(context, dexs);        }    }    //注入    private static void inject(Context context, List<File> dexs) {        Log.e(TAG, "inject: ");        //dex存储目录        File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);        //dex加载后的缓存目录        String optimizeDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";        File optFile = new File(optimizeDir);        if(!optFile.exists()){            optFile.mkdirs();        }        //获取app的类加载器        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();        for (File dex : dexs) {            Log.i(TAG, "inject: 获取dex。。。" + dex.getAbsolutePath());            //加载修复的dex文件            //public DexClassLoader(            //String dexPath, dex路径            // String optimizedDirectory, dex加载的缓存路径            // String librarySearchPath,            // ClassLoader parent)            DexClassLoader dexClassLoader = new DexClassLoader(dex.getAbsolutePath(), optFile.getAbsolutePath(), null, pathClassLoader);            try {                //获取DexClassLoader和PathClassLoader中的DexElements                Object dexObj = getPathList(dexClassLoader);                Object pathObj = getPathList(pathClassLoader);                Object dexElements = getDexElements(dexObj);                Object pathElements = getDexElements(pathObj);                //合并                Object mergeElements = combineArray(dexElements, pathElements);                //将mergeElements覆盖pathClassLoader中的elements                setField(pathObj,"dexElements", mergeElements);                Log.e(TAG, "inject: merge");            }catch (Exception e){                Log.e(TAG, "inject: " + e.getMessage());                e.printStackTrace();            }        }    }    //获取dalvik.system.BaseDexClassLoader中的DexPathList pathList    private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {        return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");    }    private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {        Field localField = cl.getDeclaredField(field);        localField.setAccessible(true);        return localField.get(obj);    }    //获取DexPathList中的Element[] dexElements    private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {        return getField(paramObject, paramObject.getClass(), "dexElements");    }    private static void setField(Object obj, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {        Class<?> cl = obj.getClass();        Field localField = cl.getDeclaredField(field);        localField.setAccessible(true);        localField.set(obj, value);    }    /**     *     * @param arrayLhs 放到前面的数组     * @param arrayRhs 放到后面的数组     * @return     */    private static Object combineArray(Object arrayLhs, Object arrayRhs) {        Class<?> localClass = arrayLhs.getClass().getComponentType();        int i = Array.getLength(arrayLhs);        int j = i + Array.getLength(arrayRhs);        Log.i(TAG, "combineArray: i" + i + "  j" + (j - i));        Object result = Array.newInstance(localClass, j);        for (int k = 0; k < j; ++k) {            if (k < i) {                Array.set(result, k, Array.get(arrayLhs, k));            } else {                Array.set(result, k, Array.get(arrayRhs, k - i));            }        }        return result;    }    //获取DEX_DIR下的补丁dexs    private static List<File> getPatchDexs(Context context) {        List<File> patchDexs = new ArrayList<>();        File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);        if(fileDir != null && fileDir.exists()){            File[] files = fileDir.listFiles();            if(files != null){                for (File file : files) {                    if (file != null) {                        String fileName = file.getName();                        //判断是否是补丁dex(patch.dex)可能有多个                        if(fileName.startsWith("patch") && fileName.endsWith(".dex")){                            patchDexs.add(file);                        }                    }                }            }        }        return patchDexs;    }    //从服务器或本地下载补丁dex    private static void downLoadPatch(Context context, String patchPath) {        // /data/data/packagename/odex        File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);        String filePath = fileDir.getAbsolutePath() + File.separator + dexName;        File file = new File(filePath);        if(file.exists()){            file.delete();        }        //拷贝补丁到/data/data/packagename/odex下        FileInputStream is = null;        FileOutputStream os = null;        try {            is = new FileInputStream(patchPath);            os = new FileOutputStream(file);            int len = 0;            byte[] buffer = new byte[1024];            while ((len = is.read(buffer)) != -1){                Log.e(TAG, "downLoadPatch: " + len);                os.write(buffer, 0, len);            }            Log.e(TAG, "downLoadPatch: " + file.getAbsolutePath());        }catch (Exception e){            Log.e(TAG, "downLoadPatch: " + e.getMessage());            e.printStackTrace();        }finally {            if(os != null){                try {os.close();} catch (IOException e) {e.printStackTrace();}                os = null;            }            if(is != null){                try {is.close();} catch (IOException e) {e.printStackTrace();}                is = null;            }        }    }}

然后在MyApplication.java中添加loadPatch方法,这样每次启动应用,都会加载之前的补丁

    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        MultiDex.install(base);        new BugFixUtils().loadPatch(base);    }

MainActivity.java

public class MainActivity extends AppCompatActivity {    private TextView tv_result;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv_result = (TextView)findViewById(R.id.tv_result);    }    public void calculate(View view) {        int result = new TestBug().calculate();        tv_result.setText("计算结果:" + result);    }    public void fixbug(View view) {        BugFixUtils.fixbug(this);    }}

测试

1.首先运行程序,点击计算执行calculate方法,报错。
2.然后,点击修复bug执行fixbug方法。
3.这时候点击calculate方法,依然会报错。
4.重启,点击计算calculate,发现计算结果为10,修复成功。

源码下载

原创粉丝点击