关于安卓简单补丁的制作和热修复的代码实现

 

一、制作补丁

1.先将没有bug的apk重命名为zip

2..dex这个文件就是我们的补丁通常我们会把这个补丁放到服务器,当项目出现bug那么app就会走动从服务器获取补丁,最后完成自我修复,让用户感觉体验很流畅。

1.release签名打包作为发布版本,每次release打包都会重新生成hash.txt和mapping.txt(开启混淆的情况下才有mapping)
2.每次debug运行的时候(直接运行项目或者buildapk),都会通过校验hash.txt和mapping.txt生成已签名补丁包。 
直接将补丁包放到sdcard中即可完成热修复
3.加载补丁的时候需要进行签名校验,防止恶意代码注入
 

二、具体的代码实现

现在有很多热修复框架的原理也是这样的,比如 Tinker、Bugly 等。

首先我们写一个会出异常的方法.

public class Test {
    int a=10;
    int b=0;
    //点击第一个按钮调用这个方法,很明显出现了bug
    public void showToast(Context context){
        Toast.makeText(context, "输出:"+(a/b), Toast.LENGTH_SHORT).show();
    }
}

之后我们写一个修复的类

原理就是处理了ClassLoader.pathList.dexElements[]

 

/**
 * 修复bug的类
 * 创建一个加载的dex文件的File对象的集合
 */
public class HotTestManager {
private static HashSet<File> loadedDex=new HashSet<>();

static {
    //调用之前 先请空这个集合
    loadedDex.clear();
}
    //首先 要获取到系统的dexElement
    //其次 获取到补丁的dexElement
    //然后 进行dexElement的合并
    //最后 把合并后的dexElement赋值给系统的dexElement的变量

    public static void loadDex(Context context){
        if (context == null){
            return;
        }

        File fileDir = context.getDir("odex", Context.MODE_PRIVATE);
        File[] files = fileDir.listFiles();
        for ( File file:files) {

            if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")){
            loadedDex.add(file);
            }

            //创建一个目录 用来 装载解压的文件
           String optimizeDir= fileDir.getAbsolutePath()+File.separator+"opt_dex";

            File fpot =new File(optimizeDir);
            if (!fpot.exists()){
                fpot.mkdir();
            }
            //遍历所有补丁dex文件的File对象
            for (File dex : loadedDex) {
                //---------------------获取系统的
                try {
                    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
                    Class<?> baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
                    Field pathListField = baseDexClassLoader.getDeclaredField("pathList");
                    pathListField.setAccessible(true);
                    Object pathListValue = pathListField.get(pathClassLoader);
                    Class<?> systemPathListClass = pathListValue.getClass();
                    Field dexElementsField = systemPathListClass.getDeclaredField("dexElements");
                    dexElementsField.setAccessible(true);
                    Object systemElement = dexElementsField.get(pathListValue);

                    DexClassLoader dexClassLoader = new DexClassLoader(dex.getAbsolutePath()
                            , fpot.getAbsolutePath()
                            , null
                            , context.getClassLoader());

                    Field myPathListField = baseDexClassLoader.getDeclaredField("pathList");
                    myPathListField.setAccessible(true);
                    Object myPathListValue = myPathListField.get(dexClassLoader);
                    Class<?> myPathListClass = myPathListValue.getClass();
                    Field myDexElementsField = myPathListClass.getDeclaredField("dexElements");
                    myDexElementsField.setAccessible(true);
                    Object myElement = myDexElementsField.get(myPathListValue);

                    int systemLength = Array.getLength(systemElement);
                    int myLength = Array.getLength(myElement);
                    int newElementLength =systemLength+myLength;
                    Class<?> componentType = systemElement.getClass().getComponentType();
                    Object newElement = Array.newInstance(componentType, newElementLength);

                    for (int i = 0; i <newElementLength ; i++) {
                        if (i<myLength){
                            Array.set(newElement,i,Array.get(myElement,i));
                        }else {
                            Array.set(newElement,i,Array.get(systemElement,i-myLength));
                        }
                    }

                    //将合并好的数组 赋值给dexElement的成员变量
                    dexElementsField.set(pathListValue,newElement);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

        }
    }
}

最后我们把自己的补丁读取进来,我们的修复类就实现了这一步,将异常类替换掉,让程序读取我们的补丁不去读取异常的类然后我们读取补丁,把这个补丁存到我们的app里面。

 

  //修复bug的
    public void hotTest(View view) {
        //先将补丁复制到指定目录下
        File odex = this.getDir("odex", Context.MODE_PRIVATE);
        //创建补丁的名字
        String name="out.dex";
        //创建
        String filePath=new File(odex,name).getAbsolutePath();
        File file =new File(filePath);
        if (file.exists()){
            file.delete();
        }

        InputStream is=null;
        FileOutputStream os=null;
        try {
            is=new FileInputStream(new File(Environment.getExternalStorageDirectory(),name));
            os=new FileOutputStream(filePath);
            int len =0;
            byte[] bytes=new byte[1024];
            while ((len=is.read(bytes))!=-1) {
                os.write(bytes,0,len);
            }
            File f=new File(filePath);
            HotTestManager.loadDex(this);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

 

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值