首页 > 其他分享 >Android热修复简易实现

Android热修复简易实现

时间:2024-05-27 10:00:38浏览次数:15  
标签:修复 void Object 简易 new import Android android String

https://www.jianshu.com/p/b65e5da3dff2 

  • 先了解一下原理和实现方式

Java编译为class

javac xxx.java.

class打包为jar包

jar cvf xxx.jar x/x/x/class #可用.来代替目录 意思为当前目录和所有的子目录打包

将as编译好的jar包拆开

unzip a.jar -d outfile
jar -xvf a.jar

jar包转为dex

./d8 --output xxx-file xxx.jar

代码

工具类

package com.example.hotfix;

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

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

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

public class MyFix {

    public static void fixDexFile(Context context, String fixDexFilePath) {
        if (fixDexFilePath != null && new File(fixDexFilePath).exists()) {
            try {
                injectDexToClassLoader(context, fixDexFilePath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static void injectDexToClassLoader(Context context, String fixDexFilePath)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException{
        // 旧数组
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object basePathList = getPathList(pathClassLoader);
        Object baseElements = getDexElements(basePathList);
//        System.out.println("原数组为:" + Arrays.toString((Object[]) baseElements));
        // 修复数组
        String baseDexAbsolutePath = context.getDir("dex", 0).getAbsolutePath();
        DexClassLoader fixDexClassLoader = new DexClassLoader(
                fixDexFilePath, baseDexAbsolutePath, fixDexFilePath, context.getClassLoader());
        Object fixPathList = getPathList(fixDexClassLoader);
        Object fixElements = getDexElements(fixPathList);
        // 新数组
        Object newElements = combineArray(baseElements, fixElements);
        System.out.println("新数组为:" + Arrays.toString((Object[]) newElements));
        Object basePathList2 = getPathList(pathClassLoader);
        //新数组替换旧数组
        setField(basePathList2, basePathList2.getClass(), "dexElements", newElements);
    }
    public static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException {
        return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }
    public static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException {
        return getField(obj, obj.getClass(), "dexElements");
    }
    private static Object getField(Object obj, Class cls, String str)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问
        return declaredField.get(obj);
    }
    private static void setField(Object obj, Class cls, String str, Object obj2)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问
        declaredField.set(obj, obj2);
    }
    private static Object combineArray(Object baseElements, Object fixElements) {
        Class componentType = fixElements.getClass().getComponentType();
        int length = Array.getLength(fixElements);
        int length2 = Array.getLength(baseElements) + length;
        Object newInstance = Array.newInstance(componentType, length2);
        for (int i = 0; i < length2; i++) {
            if (i < length) {
                //修复数在新数组的“0”
                Array.set(newInstance, i, Array.get(fixElements, i));
            } else {
                Array.set(newInstance, i, Array.get(baseElements, i - length));
            }
        }
        return newInstance;
    }
}

MainActivity

package com.example.hotfix;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.lang.reflect.Array;
import java.util.Arrays;

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

public class MainActivity extends AppCompatActivity {
    private static final String DEX_DIR = "patch";
    private static String TAG = "Mainactivity";
    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn_run = findViewById(R.id.btn_run);
        Button btn_repair = findViewById(R.id.btn_repair);
         context = MainActivity.this;
        checkFix();

        init();
        btn_run.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new BugTest().getbug();
            }
        });
        btn_repair.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    checkFix();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void init() {

        File patchDir = new File(this.getExternalFilesDir(null), DEX_DIR);
        if (!patchDir.exists()) {
            patchDir.mkdirs();
        }
    }
//    private void checkStoragePermission() {
//        if (ContextCompat.checkSelfPermission(this,
//                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
//                || ContextCompat.checkSelfPermission(this,
//                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//            // 如果权限没有被授予,则请求权限
//            ActivityCompat.requestPermissions(this,
//                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
//                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
//                    REQUEST_CODE_STORAGE_PERMISSION);
//            Log.e(TAG, "开始请求权限");
//        } else {
//            Log.e(TAG, "权限已经拿到");
//            // 如果权限已经被授予,则可以执行相应的操作
//            // 例如读取或写入文件
//        }
//    }
//
//    @Override
//    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//        if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
//            if (grantResults.length > 0
//                    && grantResults[0] == PackageManager.PERMISSION_GRANTED
//                    && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
//
//            } else {
//                // 如果用户拒绝了权限请求,则可以显示一个提示信息,或者采取其他措施
//                if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
//                        || shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//                    // 再次请求权限
//                    ActivityCompat.requestPermissions(this,
//                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
//                                    Manifest.permission.WRITE_EXTERNAL_STORAGE},
//                            REQUEST_CODE_STORAGE_PERMISSION);
//                } else {
//                    // 如果用户拒绝了权限请求,并且选择了“不再询问”选项,可以显示一个提示信息
//                    Toast.makeText(this, "权限被拒绝,某些功能可能无法正常工作", Toast.LENGTH_SHORT).show();
//                }
//            }
//        }
//    }
//
//    public Context getContext() {
//        Context context = MainActivity.this;
//        return context;
//    }
    private void checkFix() {
        try {
//            String dexPath = Environment.getExternalStorageDirectory() + "/classes.dex";
//            String dexPath = String.valueOf(context.getExternalFilesDir(DEX_DIR+"/classes.dex"));
            String dexPath = "/storage/emulated/0/Android/data/com.example.hotfix/files/patch/classes.dex";
//            String dexPath="/data/data/com.example.hotfix/cache/classes.dex";
            Log.e(TAG, "dex文件路径为"+dexPath);
            new MyFix().fixDexFile(this, dexPath);
        } catch (Exception e) {
            Toast.makeText(this, "修复失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
    private void getarrly() {
        try {
            PathClassLoader pathClassLoader = (PathClassLoader) MainActivity.this.getClassLoader();
            Object basePathList = new MyFix().getPathList(pathClassLoader);
            Object baseElements = new MyFix().getDexElements(basePathList);
            System.out.println("原数组为:" + Arrays.toString((Object[]) baseElements));
        }catch (Exception e){
            e.printStackTrace();
        }


    }
    @Override
    protected void onStart() {
        super.onStart();
        getarrly();

    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

}

补充

  • 修改后的dex数组因为双亲模型的原因 需要尽快的进行加载 重写attachBaseContext 将加载放置于之中

  • 双亲模型特性同一个classloader,同一个class 只会加载一次

标签:修复,void,Object,简易,new,import,Android,android,String
From: https://blog.csdn.net/L2798537495/article/details/139180595

相关文章

  • ftplib库-制作简易ftp客户端
    #获取上一次的参数try:withopen("./FtpGUI-Python-Configure.cache","r",encoding="utf-8")asf:cfglist=f.read().split("\n")except:cfglist=["","","",""]importtk......
  • 8-1 【Python0031】简易带参计算器
    设计一个简易的参数计算器。【输入格式】第一行输入待计算的带变量参数的计算式第二行输入各变量参数的赋值序列【输出格式】输出带变量参数的计算式的计算结果【输入样例】a+ba=1,b=10【输出样例】11 defparse_and_compute(expression,values):#创建一个字......
  • android体系结构的组成
    安卓系统架构主要分为四个层次,从高层到低层依次是:应用程序层:这一层包含了Android系统预装的核心应用程序,如电子邮件客户端、短信程序、日历、地图、浏览器、联系人管理程序等。这些应用程序通常使用Java语言编写。应用程序框架层:这一层为开发者提供了丰富的API......
  • android测试常用的adb命令以及进行Monkey测试
    1,什么是adb:ADB全称为AndroidDebugBridge,起到调试桥的作用,是一个客户端-服务器端程序。其中客户端是用来操作的电脑,服务端是Android设备。ADB也是AndroidSDK中的一个工具,可以直接操作管理Android模拟器或者真实的Android设备。2,为什么要用adb:运行设备的shell(命......
  • JS-简易ATM制作,--continue和break的区别
    1.简易ATM制作可使用两种策略:switch-case方法或if的方法,实质上都是一样的。我在写的时候用flag来控制 while循环,如果选择退出则修改flag=0,循环也就是程序结束,其他选择则flag不变,可继续选择其他的操作。money初始的值可以自定义。注意:由于prompt获取来的是字符(串)类型,进......
  • Android查看/proc目录下的系统信息
    目录Android查看/proc目录下的系统信息1.获取读取权限2.读取/proc目录信息3.读取特定文件信息4.注意事项1.缓冲功能2.读取方法3.使用示例4.关闭资源Android查看/proc目录下的系统信息在Android系统中,/proc目录是一个特殊的虚拟文件系统,用于向用户空间提供......
  • Android.mk变量解析
    前言Android.mk是Android提供的一种makefile文件,用来指定诸如编译生成so库名、引用的头文件目录、需要编译的.c/.cpp文件和.a静态库文件等。要掌握jni,就必须熟练掌握Android.mk的语法规范。LOCAL_PATH:=$(callmy-dir)一个Android.mkfile首先必须定义好LOCAL_PATH变量......
  • Android跨进程通信--Binder机制及AIDL是什么?
    文章目录Binder机制Binder是什么?Binder相对于其他几种跨进程通信方式,有什么区别?谈一下BinderIPC通信过程:具体的通讯过程是什么?Binder如何处理发送请求与接收请求?Binder是通过什么方式来进行内存映射的?Binder是如何进行管理的?Binder、Socket的数据限制是多少?自己APP如......
  • android git提交代码命令以及常见命令的使用
    安装GitUbuntu:sudoapt-getinstallgit-core创建代码仓库:配置身份:gitconfig--globaluser.name"Tony"gitconfit--globaluser.email"[email protected]"查看身份:gitconfig--globaluser.namegitconfit--globaluser.email提交代码流程创建本地仓库:进......
  • Android Studio下载Gradle超时
    在AndroidStudio打开项目时,出现了下载Gradle超时,导致项目无法运行.首先在gradle-wrapper.properties文件中,查看Gradle版本和zipStorePath:然后查看Gradle包存放的位置:到https://mirrors.cloud.tencent.com/gradle/下载相应的Gradle包:下载路径是Gradle包存放的位置和z......