为了账号安全,请及时绑定邮箱和手机立即绑定

热更新实践

标签:
Android

首先看效果图

  • 出现错误界面

  • 正常界面 弹出toast。

  • 详细看demo中的代码

  1. 首先是Test类。很简单。就是个测试,故意出错报个错误

public class Test {
    public static void show(Context context) {
        int i = 10;
        //这里过一会 会把j的值改成1。这样子就正确
        int j = 0;

        Toast.makeText(context, "shit:" + i / j, Toast.LENGTH_SHORT).show();
        }
}
  1. Activity的代码,具体作用会在下面写出来

    public class FixDesActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_fix);
        }
    public void inject(View view) {
        // 无bug的classes2.dex文件存放地址
        String sourceFile = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + "classes2.dex";
        // 系统的私有目录
        String targetFile = this.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath() + File.separator
                + "classes2.dex";
        try {
            // 复制文件到私有目录
            FileUtils.copyFile(sourceFile, targetFile);

            // 加载.dex文件
            FixDexUtils.loadFixDex(this.getApplication());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void test(View view) {
        Test.show(this);
    }
}
  1. FixDexUtils类的代码

public class FixDexUtils {

    private static HashSet<File> loadedDex = new HashSet<File>();

    static {
        loadedDex.clear();
    }

    public static void loadFixDex(Context context) {
        // 获取到系统的odex 目录
        File fileDir = context.getDir("odex", Context.MODE_PRIVATE);
        File[] listFiles = fileDir.listFiles();

        for (File file : listFiles) {
            if (file.getName().endsWith(".dex")) {
                //存储该目录下的.dex结尾的文件(补丁)
                loadedDex.add(file);
            }
        }
        doDexInject(context, fileDir);
    }

    /**
     * 合并之前的apk
     *
     * @param context
     * @param fileDir
     */
    private static void doDexInject(Context context, File fileDir) {
        // .dex 的加载需要一个临时目录
        String optimizeDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
        File fopt = new File(optimizeDir);
        if (!fopt.exists()) {
            fopt.mkdirs();
        }
        //加载应用程序的dex
        // 根据.dex 文件创建对应的DexClassLoader 类
        for (File file : loadedDex) {
            DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath()
                    , fopt.getAbsolutePath(), null, context.getClassLoader());
            inject(classLoader, context);
        }
    }

    private static void inject(DexClassLoader classLoader, Context context) {
        // 获取到系统的DexClassLoader 类
        PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();
        try {
            // 分别获取到补丁的dexElements和系统的dexElements
            Object dexElements = combineArray(getDexElements(getPathList(classLoader)),
                    getDexElements(getPathList(pathLoader)));
            // 获取到系统的pathList 对象
            Object pathList = getPathList(pathLoader);
            // 设置系统的dexElements 的值
            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, IllegalArgumentException, IllegalAccessException {

        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        localField.set(obj, value);
    }

    /**
     * 通过反射获取 BaseDexClassLoader中的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中dexElements
     */
    private static Object getDexElements(Object paramObject)
            throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
        return getField(paramObject, paramObject.getClass(), "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;
    }
}

原理

这里先说下 热更新吧

通常的技术有

  • 阿里巴巴的黑科技

    底层c入手。杀到进程加载的类二进制数据,把内存里面的二进制数据修改。Dexposed、andfix这些。

  • 腾讯的开源tinker

    基于android和java的类加载机制入手
    ClassLoader—-> .class文件

######这里就得知道android是如果加载classes.dex文件的

dalvik虚拟机:

PathClassLoader

用来加载应用程序的dex

DexClassLoader可以用来加载指定的某些dex文件

android studio中使用的 instant run 使用的就是。

####接下来就明白了。当用户手机上的版本出现问题时。程序员在修改好之后。拿到没问题的版本的 class打包成class.dex文件,发送到用户客户端。把classes2.dex加载dexElements(DexPathList中)。就达到目的了

  • 上面的也就是使用步骤了.

  1. 错误客户端在客户手机上面。

  2. 修改成功后,把class2.dex放到服务器,客户端下载该文件。

  3. 客户端下载完成后,执行方法实现热更新。

对应到我的demo中就是把

  1. test方法中的j=0, 出现除0异常

  2. 下载这一步直接手动放到sd卡的根目录了。这里其实应该放到包目录下。也无所谓了。然后进入app,点击inject。实现替换。

  3. 再点击test。正常谈toast。

原文链接:http://www.apkbus.com/blog-871577-63105.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消