Android 热修复之.class文件转为.dex文件

Android热修复之中有一步需将待修复的java文件转化为class文件再打包成dex文件,在测试过程中遇到一些问题,因此做个记录。

一、生成.class文件

1.先Build-->Clean Project 工程,展开build--->intermediates目录,此时有些目录是没有生成的

2.再build--Rebuild Project工程,此时会在intermediates目录下生成一个javac的文件夹

在javac文件夹下找到classes目录,找到自己需要修复的类,将其连带完整的包名路径复制到任意目录下,如将其复制到桌面的dex文件夹,删除其它不用修复的类

二、使用dx.bat工具生成dex

1.sdk安装目录下的buiild-tools--->任意sdk版本可找到dx.bat工具

2.使用命令生成dex文件,在dx.bat所在目录下打开命令窗口---->Shift+目录空白处点鼠标右键--->在此处打开命令窗口

3.输入命令:

dx --dex --output=D:\桌面\dex\classes3.dex D:\桌面\dex
命令解释:
   --output=D:\桌面\dex\classes3.dex   指定输出路径
   D:\桌面\dex   存放前面复制出来的class文件所在目录(注意是包括全路径的文件夹,也可以有多个class)

回车后成功在指定文件夹下生成dex文件

4.拿到待修复的dex文件就可以验证之后的热修复了

三、使用dx.bat工具时出现的错误

1.ERROR: No suitable Java found. In order to properly use the Android Developer

出现改错误的原因是我安装的jdk版本不对,我之前的安装的是jdk 12 但是在使用sdk工具时报上面的错不能使用,之后重现下载安装了jdk-8u211就可以了

四、热修复注意点:

1.应用每次杀进程后再次重启,已经修复的会失效,需要重新修复,因此需放在application中进行修复操作

2.修复的dex最好放在应用包名下的文件夹中,可通过文件输入输出流将sd卡中的文件搬迁到指定目录下

3.在AndroidManifest.xml声明读写权限后,还需动态申请读写权限

4.调试安装应用时,取消掉instant run这里的按钮,在setting--->build,Executio中,因为这个本身就有热修复的功能,所以为了不影响测试效果每次都重新安装应用,不勾选该按钮

五、

1.附上热修复工具类

package com.dex.main;

import android.content.Context;
import android.util.Log;

import com.dn.fixutils.MyConstants;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * @描述 热修复工具(只认后缀是dex、apk、jar、zip的补丁)
 */
public class DexFixUtils {
    private static final String TAG = "xuwei";
    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 补丁所在目录 Environment.getExternalStorageDirectory()
     */
    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 fileDir = patchFilesDir != null ? patchFilesDir : context.getDir(DEX_DIR,Context.MODE_PRIVATE);// data/data/包名/files/odex(这个可以任意位置)
        Log.d(TAG, "loadFixedDex: 查询的文件路径:"+fileDir.getAbsolutePath());
        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);// 存入集合

                Log.d(TAG, "loadFixedDex: 存入的文件:"+file.getName());
            }
        }
        // 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);
                setField(pathList, pathList.getClass(), "dexElements", dexElements);
                Log.d(TAG, "doDexInject: (合并)修复完成!");
            }
        } 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;
    }

    /**
     * 两个数组合并,与上面的数组合并方法等同
     * @param arrayLhs
     * @param arrayRhs
     * @return
     */
    private static Object combineArray2(Object arrayLhs, Object arrayRhs) {
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
        int j = i + Array.getLength(arrayRhs);// 得到原dex数组长度
        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;
    }
}

 

2.在application中的调用方式

package com.dex.main;

import android.app.Application;
import android.content.Context;
import android.os.Environment;
import android.support.multidex.MultiDex;

public class MyApplication extends Application{
   @Override
   public void onCreate() {
      super.onCreate();
   }
   @Override
   protected void attachBaseContext(Context base) {
      // TODO Auto-generated method stub
        //修复默认路径下的dex文件(待修复的文件都需要移入该目录中)  data/data/包名/files/odex
      DexFixUtils.loadFixedDex(base);
        //直接在拿sd卡中的dex文件进行修复,但是应用在每次重启后(杀进程)都需要重写修复一次,所以最好是将修复的dex文件放在包名下的文件目录中
//    DexFixUtils.loadFixedDex(base,Environment.getExternalStorageDirectory());
      super.attachBaseContext(base);

   }
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值