Android热修复——自己动手打补丁实现热修复

需要首先了解一下Activity启动流程源码分析和类的加载流程,上篇文章有介绍到,https://www.jianshu.com/p/d2732de5fef0

思路

整体思路

  1. 先获取已经运行的 dexElement
  2. 获取下载好的补丁的 dexElement
  3. 把补丁的dexElement 插到 已经运行的 dexElement 的最前面 合并

FixDexBug工具类

public class FixDexBug {
    private Context mContext;
    private File mDexDir;
    private String TAG = "FixDexManager";


    public FixDexBug(Context context) {
        this.mContext = context;
        // 获取应用可以访问的dex目录
        this.mDexDir = context.getDir("odex", Context.MODE_PRIVATE);
    }


    /**
     * 修复dex包
     *
     * @param fixDexPath
     */
    public void fixDex(String fixDexPath) throws Exception {
        // 2. 获取下载好的补丁的 dexElement
        // 2.1 移动到系统能够访问的  dex目录下   ClassLoader

        File srcFile = new File(fixDexPath);

        if (!srcFile.exists()) {
            throw new FileNotFoundException(fixDexPath);
        }

        File destFile = new File(mDexDir, srcFile.getName());

        if (destFile.exists()) {
            Log.d(TAG, "patch [" + fixDexPath + "] has be loaded.");
            return;
        }

        copyFile(srcFile, destFile);

        // 2.2 ClassLoader读取fixDex路径  为什么加入到集合  已启动可能就要修复 BaseApplication
        List<File> fixDexFiles = new ArrayList<>();
        fixDexFiles.add(destFile);

        fixDexFiles(fixDexFiles);
    }

    /**
     * 把dexElements注入到classLoader中
     *
     * @param classLoader
     * @param dexElements
     */
    private void injectDexElements(ClassLoader classLoader, Object dexElements) throws Exception {
        // 1.先获取 pathList
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        // IOC 熟悉反射
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(classLoader);

        // 2. pathList里面的dexElements
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);

        dexElementsField.set(pathList, dexElements);
    }


    /**
     * 合并两个数组
     *
     * @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);
        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;
    }


    /**
     * copy file
     *
     * @param src  source file
     * @param dest target file
     * @throws IOException
     */
    public static void copyFile(File src, File dest) throws IOException {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            if (!dest.exists()) {
                dest.createNewFile();
            }
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dest).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }

    /**
     * 从classLoader中获取 dexElements
     *
     * @param classLoader
     * @return
     */
    private Object getDexElementsByClassLoader(ClassLoader classLoader) throws Exception {

        // 1.先获取 pathList
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        // IOC 熟悉反射
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(classLoader);

        // 2. pathList里面的dexElements
        Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);

        return dexElementsField.get(pathList);
    }

    /**
     * 加载全部的修复包
     */
    public void loadFixDex() throws Exception {
        File[] dexFiles = mDexDir.listFiles();

        List<File> fixDexFiles = new ArrayList<>();

        for (File dexFile : dexFiles) {
            if (dexFile.getName().endsWith(".dex")) {
                fixDexFiles.add(dexFile);
            }
        }

        fixDexFiles(fixDexFiles);
    }

    /**
     * 修复dex
     *
     * @param fixDexFiles
     */
    private void fixDexFiles(List<File> fixDexFiles) throws Exception {
        // 1. 先获取已经运行的 dexElement
        ClassLoader applicationClassLoader = mContext.getClassLoader();

        Object applicationDexElements = getDexElementsByClassLoader(applicationClassLoader);

        File optimizedDirectory = new File(mDexDir, "odex");

        if (!optimizedDirectory.exists()) {
            optimizedDirectory.mkdirs();
        }

        // 修复
        for (File fixDexFile : fixDexFiles) {
            // dexPath  dex路径
            // optimizedDirectory  解压路径
            // libraryPath .so文件位置
            // parent 父ClassLoader
            ClassLoader fixDexClassLoader = new BaseDexClassLoader(
                    fixDexFile.getAbsolutePath(),// dex路径  必须要在应用目录下的odex文件中
                    optimizedDirectory,// 解压路径
                    null,// .so文件位置
                    applicationClassLoader // 父ClassLoader
            );

            Object fixDexElements = getDexElementsByClassLoader(fixDexClassLoader);

            // 3. 把补丁的dexElement 插到 已经运行的 dexElement 的最前面  合并
            // applicationClassLoader 数组 合并 fixDexElements 数组

            // 3.1 合并完成
            applicationDexElements = combineArray(fixDexElements, applicationDexElements);
        }

        // 3.2 把合并的数组注入到原来的类中 applicationClassLoader
        injectDexElements(applicationClassLoader, applicationDexElements);
    }
}

使用

BaseApplication

   try {
            FixDexBug fixDexBug=new FixDexBug(this);
            fixDexBug.loadFixDex();
        } catch (Exception e) {
            e.printStackTrace();
        }

MainActivity

public class MainActivity extends BaseSkinActivity {


    @Override
    protected void initData() {
        fixDexBug();
    }

    @Override
    protected void initView() {

        Button button= (Button) findViewById(R.id.test_tv);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(TestActivity.class);
            }
        });
    }

    /**
     * 修复bug
     */
    private void fixDexBug() {
        File fixFile = new File("/sdcard/Download/", "fix.dex");
        Log.e("TAG",fixFile.exists()+"");
        if (fixFile.exists()) {
            FixDexBug fixDexBug = new FixDexBug(this);
            try {
                fixDexBug.fixDex(fixFile.getAbsolutePath());
                Toast.makeText(this, "修复成功", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this,"修复失败",Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    protected void initTitle() {

    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }


}

TestActivity

public class TestActivity extends BaseSkinActivity implements View.OnClickListener {
    
    @Override
    protected void initData() {

    }

    @Override
    protected void initView() {
        // 初始化View
        Button button= (Button) findViewById(R.id.test_tv);
        button.setOnClickListener(this);
    }

    @Override
    protected void initTitle() {
        // 初始化头部
    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onClick(View v) {
        Toast.makeText(this, 2/0+ "测试", Toast.LENGTH_LONG).show();
        // Activity   启动流程
    }

}

最后将正确的apk,重命名为fix.zip,然后解压选择fix.dex,拷贝即可

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容