前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ClassLoader实现热修复的示例

ClassLoader实现热修复的示例

作者头像
103style
发布2022-12-19 13:44:58
2560
发布2022-12-19 13:44:58
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自:103style的博客


效果图

修复之前
修复之前
修复之后
修复之后

实现思路

主要实现思路主要是:

  • 先编写一个有 bug 的程序, 运行安装到手机。
  • 修正bug之后,重新 rebuild, 然后找到 app - build - intermediates - dex - debug - mergeProjectDexDebug - out - classes.dex 移动到 修复包 下载的目录 , 这里放在 assets 目录下,并重命名 classes.dexclasses2.dex
base on AndroidStudio 3.5.1
base on AndroidStudio 3.5.1
  • 然后点击程序上的 Move Dex, 将修正bug之后的dex包 移动到 android/data/packagename/ 目录下,在这里目录才有加载dex权限
  • 然后重启程序,在继承自 MultiDexApplicationApplication 中加载对应的 dex 文件,获取对应的dexElements,然后合并到应用的dexElements之前。

相关代码

demo 源码下载 demo.apk download

MyApplication

代码语言:javascript
复制
public class MyApplication extends MultiDexApplication {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        new FixDemo().loadFixedDex(base);
    }
}

MainActivity

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {
    private TextView bugTv;
    private int i = 10;
    private int a = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        bugTv = findViewById(R.id.bug);

        bugTv.setText("Bug: " + i + " / " + a);

        findViewById(R.id.bug).setOnClickListener(v -> bugMethod());
        findViewById(R.id.move).setOnClickListener(v -> moveDex("classes2.dex"));
    }

    private void bugMethod() {
        Toast.makeText(this, "res = " + i / a, Toast.LENGTH_SHORT).show();
    }


    private void moveDex(String name) {
        //目录 data/data/packageName/odex
        File fileDir = getDir(FixDemo.DEX_DIR, Context.MODE_PRIVATE);
        AssetManager am = getResources().getAssets();
        try {
            InputStream is = am.open(name);
            String filePath = fileDir.getAbsolutePath() + File.separator + name;
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
            FileOutputStream os = new FileOutputStream(filePath);
            int len;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            os.close();
            is.close();

            //粘贴完文件
            File f = new File(filePath);
            if (f.exists()) {
                //文件从sk卡赋值到应用运行目录下,成功则toast提示
                Toast.makeText(this, "dex移动成功,请重启应用", Toast.LENGTH_SHORT).show();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FixDemo

代码语言:javascript
复制
public class FixDemo {

    public static String DEX_DIR = "odex";

    private File odexDir;

    /**
     * 开始替换dex
     */
    public void loadFixedDex(Context context) {
        if (null == context) {
            return;
        }
        //遍历所有的修复的dex
        odexDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE);
        File[] listFiles = odexDir.listFiles();
        if (listFiles == null) {
            return;
        }
        HashSet<File> loadedDex = new HashSet<>();
        for (File file : listFiles) {
            if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")) {
                //先将补丁文件放到一个集合里,然后再进行合并
                loadedDex.add(file);
            }
        }
        //dex合并
        doDexInject(context, loadedDex);
    }

    /**
     * dex 合并
     */
    private void doDexInject(Context appContext, HashSet<File> loadedDex) {
        try {
            String filesDirPath = odexDir.getAbsolutePath() + File.separator + "opt_dex";
            File fopt = new File(filesDirPath);
            if (!fopt.exists()) {
                fopt.mkdir();
            }
            //1.加载应用程序的dex
            BaseDexClassLoader pathLoader = (BaseDexClassLoader) appContext.getClassLoader();
            for (File dex : loadedDex) {
                //2.加载指定的修复的dex文件
                DexClassLoader classLoader = new DexClassLoader(
                        dex.getAbsolutePath(),
                        fopt.getAbsolutePath(),
                        null,
                        pathLoader);
                //3.合并
                Object dexObj = getPathList(classLoader);
                Object pathObj = getPathList(pathLoader);
                Object mDexElementsList = getDexElements(dexObj);
                Object pathDexElementsList = getDexElements(pathObj);
                //将两个list合并为一个
                Object dexElements = combineArray(mDexElementsList, pathDexElementsList);
                //重写给PathList里面的Element[] dexElements赋值
                Object pathList = getPathList(pathLoader);
                ReflectUtils.setClassFiled(pathList, pathList.getClass(), "dexElements", dexElements);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object getPathList(ClassLoader classLoader) {
        try {
            return ReflectUtils.getDeclaredField(classLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    private Object getDexElements(Object obj) {
        return ReflectUtils.getClassField(obj, "dexElements");
    }

    /**
     * 合并数组
     */
    private Object combineArray(Object arrayLbs, Object arrayRhs) {
        Class localClass = arrayLbs.getClass().getComponentType();
        int i = Array.getLength(arrayLbs);
        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(arrayLbs, k));
            } else {
                Array.set(result, k, Array.get(arrayRhs, k - i));
            }
        }
        return result;
    }

}

以上

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 效果图
  • 实现思路
  • 相关代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档