Android的在线热更新方案_自实现

26 篇文章 0 订阅
26 篇文章 0 订阅

Android的在线热更新方案_自实现

01.参考文章:https://blog.csdn.net/qq_39799899/article/details/102478355
02.Java层修复(dex文件覆盖)
03.Native层修复:
04.参考文章:https://blog.csdn.net/u013132758/article/details/96644721


1、创建工具类FixDexUtil:

注意:设置补丁文件存放位置REPAIR_FILE_PATH

public class FixDexUtil {

    //这下面两个属性可自己修改
    private final static String REPAIR_FILE_NAME = "BugClass";  //修复文件名
    private final static String REPAIR_FILE_PATH = "fix";      //修复文件路径(默认初始路径为根目录)

    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";
    private 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();
    }

    /**
     * 开启修复
     *
     * @param context
     */
    public static void startRepair(final Context context) {
        File externalStorageDirectory = context.getExternalCacheDir();
        // 遍历所有的修复dex , 因为可能是多个dex修复包
        File fileDir = externalStorageDirectory != null ?
                new File(externalStorageDirectory, FixDexUtil.REPAIR_FILE_PATH) :
                new File(context.getFilesDir(), FixDexUtil.DEX_DIR);// data/user/0/包名/files/odex(这个可以任意位置)
        if (!fileDir.exists()) {//如果目录不存在就创建所有目录,这里需要添加权限
            fileDir.mkdirs();
        }
        if (FixDexUtil.isGoingToFix(context)) {
            FixDexUtil.loadFixedDex(context, context.getExternalCacheDir());
            Log.i("GT_", "正在修复");
        }
    }


    /**
     * 加载补丁,使用默认目录:data/data/包名/files/odex
     *
     * @param context
     */
    public static void loadFixedDex(Context context) {
        loadFixedDex(context, null);
    }

    /**
     * 加载补丁
     *
     * @param context       上下文
     * @param patchFilesDir 补丁所在目录
     */
    public static void loadFixedDex(Context context, File patchFilesDir) {
        // dex合并之前的dex
        doDexInject(context, loadedDex);
    }

    /**
     * @author bthvi
     * @time 2019/10/10 11:42
     * @desc 验证是否需要热修复
     */
    public static boolean isGoingToFix(@NonNull Context context) {
        boolean canFix = false;
        File externalStorageDirectory = context.getExternalCacheDir();

        // 遍历所有的修复dex , 因为可能是多个dex修复包
        File fileDir = externalStorageDirectory != null ?
                new File(externalStorageDirectory, REPAIR_FILE_PATH) :
                new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)

        File[] listFiles = fileDir.listFiles();
        if (listFiles != null) {
            for (File file : listFiles) {
                if (file.getName().startsWith(REPAIR_FILE_NAME) &&
                        (file.getName().endsWith(DEX_SUFFIX)
                                || file.getName().endsWith(APK_SUFFIX)
                                || file.getName().endsWith(JAR_SUFFIX)
                                || file.getName().endsWith(ZIP_SUFFIX))) {

                    loadedDex.add(file);// 存入集合
                    //有目标dex文件, 需要修复
                    canFix = true;
                }
            }
        }
        return canFix;
    }

    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的Loader
            PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
            for (File dex : loadedDex) {
                // 2.加载指定的修复的dex文件的Loader
                DexClassLoader dexLoader = new DexClassLoader(
                        dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
                        fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
                        null,// 加载dex时需要的库
                        pathLoader// 父类加载器
                );
                // 3.开始合并
                // 合并的目标是Element[],重新赋值它的值即可

                /**
                 * BaseDexClassLoader中有 变量: DexPathList pathList
                 * DexPathList中有 变量 Element[] dexElements
                 * 依次反射即可
                 */

                //3.1 准备好pathList的引用
                Object dexPathList = getPathList(dexLoader);
                Object pathPathList = getPathList(pathLoader);
                //3.2 从pathList中反射出element集合
                Object leftDexElements = getDexElements(dexPathList);
                Object rightDexElements = getDexElements(pathPathList);
                //3.3 合并两个dex数组
                Object dexElements = combineArray(leftDexElements, rightDexElements);

                // 重写给PathList里面的Element[] dexElements;赋值
                Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错
                setField(pathList, pathList.getClass(), "dexElements", dexElements);

            }
            Toast.makeText(appContext, "修复完成", Toast.LENGTH_SHORT).show();
        } 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<?> clazz = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
        int j = Array.getLength(arrayRhs);// 得到原dex数组长度
        int k = i + j;// 得到总数组长度(补丁数组+原dex数组)
        Object result = Array.newInstance(clazz, k);// 创建一个类型为clazz,长度为k的新数组
        System.arraycopy(arrayLhs, 0, result, 0, i);
        System.arraycopy(arrayRhs, 0, result, i, j);
        return result;
    }

}

2、获取原始Apk和获取补丁Apk:

01.获取原始Apk

在这里插入图片描述

02.获取补丁Class文件

在这里插入图片描述

03.Class文件转换为dex文件

 .\dx --dex --no-strict --output=C:\Users\...\Desktop\dex\BugClass.dex C:\Users\...\Desktop\dex\BugClass.class

04.adb命令push生成的dex文件到手机指定位置

adb push C:\Users\...\Desktop\test\BugClass.dex /sdcard/Android/data/com.gsls.thermalremediation/cache/fix

3、项目下载地址:

https://download.csdn.net/download/huye930610/37076176

4、存在的问题:

  • 01.无法成功修复Release版本的Apk(加上了混淆)
    混淆会使修复失败(Apk签名没有影响)
    在这里插入图片描述
    尝试:使用重复混淆的方式------Project的build.gradle中添加依赖
    在这里插入图片描述

使用重复混淆的方式------proguard文件中设置复用混淆规则
proguard文件中添加混淆的规则

-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {native ;}
-keep class com.alipay.euler.andfix.** { *; }

在这里插入图片描述
结果:没什么卵用!!!!!

  • 02.项目中使用了Dagger2注解,无法获取到Class文件

1-1、Native层修复:

创建工具类FixDexManager

public class FixDexManager {
    private final static String TAG = "FixDexUtil";
    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 Context context;

    public FixDexManager(Context context) {
        this.context = context;
    }

    public void isGoingToFix() {
        File externalStorageDirectory = context.getExternalCacheDir();

        // 遍历所有的修复dex , 因为可能是多个dex修复包
        File fileDir = externalStorageDirectory != null ?
                new File(externalStorageDirectory,"007"):
                new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)
        Log.i("yh55", "1---->>>>这里");
        File[] listFiles = fileDir.listFiles();
        if (listFiles != null){
            Log.i("yh55", "2---->>>>这里" + listFiles.length);
            System.out.println("TAG==目录下文件数量="+listFiles.length);
            for (File file : listFiles) {
                System.out.println("TAG==文件名称="+file.getName());
                Log.i("yh55", "3---->>>>这里" + file.getName());
                if (file.getName().startsWith("BugClass") &&
                        (file.getName().endsWith(DEX_SUFFIX))) {
                    loadDex(file);// 开始修复
                    //有目标dex文件, 需要修复
                }
            }
        }
    }
    /**
     * 加载Dex文件
     * @param file
     */
    public void loadDex(File file) {
        try {
            DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
                    new File(context.getCacheDir(), "opt").getAbsolutePath(), Context.MODE_PRIVATE);
            //当前的dex里面的class 类名集合
            Enumeration<String> entry=dexFile.entries();
            while (entry.hasMoreElements()) {
                //拿到Class类名
                String clazzName= entry.nextElement();
                //通过加载得到类  这里不能通过反射,因为当前的dex没有加载到虚拟机内存中
                Class realClazz= dexFile.loadClass(clazzName, context.getClassLoader());
                if (realClazz != null) {
                    Log.i("yh55", "4---->>>>这里");
                    fixClazz(realClazz);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 修复有bug的方法
     * @param realClazz
     */
    private void fixClazz(Class realClazz) {
        //得到类中所有方法
        Method[] methods=realClazz.getMethods();
        //遍历方法 通过注解 得到需要修复的方法
        for (Method rightMethod : methods) {
            //拿到注解
            Replace replace = rightMethod.getAnnotation(Replace.class);
            if (replace == null) {
                continue;
            }
            //得到类名
            String clazzName=replace.clazz();
            //得到方法名
            String methodName=replace.method();
            try {
                //反射得到本地的有bug的方法的类
                Class wrongClazz = Class.forName(clazzName);
                //得到有bug的方法(注意修复包中的方法参数名和参数列表必须一致)
                Method wrongMethod = wrongClazz.getDeclaredMethod(methodName, rightMethod.getParameterTypes());
                Log.i("yh55", "5---->>>>这里");
                //调用native方法替换有bug的方法
                replace(wrongMethod, rightMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }



    }

    public native static void replace(Method wrongMethod, Method rightMethod) ;
}

1-2、创建Native方法native-lib.cpp:

#include <jni.h>
#include <string>
#include "art_method.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_example_bthvi_mycloassloaderapplication_FixDexManager_replace(JNIEnv *env, jclass type, jobject wrongMethod,
                                             jobject rightMethod) {

    //ArtMethod存在于Android 系统源码中,只需要导入我们需要的部分(art_method.h)
    art::mirror::ArtMethod *wrong=  (art::mirror::ArtMethod *)env->FromReflectedMethod(wrongMethod);
    art::mirror::ArtMethod *right=  (art::mirror::ArtMethod *)env->FromReflectedMethod(rightMethod);
    //    method   --->class ----被加载--->ClassLoader
    //错误的成员变量替换为正确的成员变量
    wrong->declaring_class_ = right->declaring_class_;
    wrong->dex_cache_resolved_methods_ = right->dex_cache_resolved_methods_;
    wrong->access_flags_ = right->access_flags_;
    wrong->dex_cache_resolved_types_ = right->dex_cache_resolved_types_;
    wrong->dex_code_item_offset_ = right->dex_code_item_offset_;
    //    这里   方法索引的替换
    wrong->method_index_ = right->method_index_;
    wrong->dex_method_index_ = right->dex_method_index_;
}

1-3、创建ArtMethod:

namespace art {
    namespace mirror {
        class Object{
            // The Class representing the type of the object.
            uint32_t klass_;
            // Monitor and hash code information.
            uint32_t monitor_;

        };
        //简化ArtMethod  只需要关注我们需要的,只需要成员变量声明
        class ArtMethod : public Object {
        public:

            uint32_t access_flags_;
            uint32_t dex_code_item_offset_;
            // Index into method_ids of the dex file associated with this method
            //方法再dex中的索引
            uint32_t method_dex_index_;
            uint32_t dex_method_index_;
            //在方法表的索引
            uint32_t method_index_;
            const void *native_method_;

            const uint16_t *vmap_table_;
            // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
            uint32_t dex_cache_resolved_methods_;
            //方法 自发
            // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
            uint32_t dex_cache_resolved_types_;


            // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
            // The class we are a part of.
            //所属的函数
            uint32_t declaring_class_;
        };
    }
}

1-4、存在的问题:同上


1-5、项目Demo下载资源

https://download.csdn.net/download/huye930610/40128562

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值