Android带你了解热修复

热修复的简单实现

热修复没你想的那么难,只要了解了热修复的原理,你也可以轻轻松松自己实现个简单的热修复demo。我们在实现热修复功能前,先要了解几个类的关系和作用,classLoader、BaseDexClassLoader、DexclassLoader、PathClassLoader、DexPahtList、Elements。我画了一张流程图阐述了它们的关系。
在这里插入图片描述

DexclassLoader和PathClassLoader都继承BaseDexClassLoader,而DexclassLoader是可以加载指定路径下的dex、jar、apk文件的,而PathClassLoader只能加载安装的apk里边的dex文件。BaseDexClassLoader里有个成员变量叫DexPathLIst,dex文件的加载都在这个类里,它有个方法叫findclass,看它的源码:(搜索源码可地址:http://androidxref.com/7.1.2_r36/),它会遍历Element数组去找到相应的class文件。

   /**
402     * Finds the named class in one of the dex files pointed at by
403     * this instance. This will find the one in the earliest listed
404     * path element. If the class is found but has not yet been
405     * defined, then this method will define it in the defining
406     * context that this instance was constructed with.
407     *
408     * @param name of class to find
409     * @param suppressed exceptions encountered whilst finding the class
410     * @return the named class or {@code null} if the class is not
411     * found in any of the dex files
412     */
413    public Class findClass(String name, List<Throwable> suppressed) {
414        for (Element element : dexElements) {
415            DexFile dex = element.dexFile;
416
417            if (dex != null) {
418                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
419                if (clazz != null) {
420                    return clazz;
421                }
422            }
423        }
424        if (dexElementsSuppressedExceptions != null) {
425            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
426        }
427        return null;
428    }

所以我们知道了两个很重要的类:private final DexPathList pathList; private Element[] dexElements; 贴出来方便我们反射用。
那么这个逻辑就很清晰了:PathClassLoader加载apk的内部的dex,它是通过DexPathList的findclass方法去遍历Element数组去加载dex。那么我们可以通过反射去给Element重新赋值得到一个包含外部和内部的新的Element数组,而外部Elements数组我们可以通过DexclassLoader得到。ok我们上代码:
在这里插入图片描述
首先我们创建一个DexclassLoader和PahtClassLoader,关于fixed.jar的生成可参考我的另一篇文章:跳转

public DexClassLoader getDexClassLoader(){
        File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator + "fixed.jar");
        File dexOutputDir = this.getDir(".dex", 0);
        DexClassLoader dexClassLoader = new DexClassLoader(path.getAbsolutePath(),dexOutputDir.getAbsolutePath(),null,getPathClassLoader());
        return dexClassLoader;
    }
    public PathClassLoader getPathClassLoader(){
        return (PathClassLoader) this.getClassLoader();
    }

之后通过反射拿到它俩的成员变量pathList和pathList的成员变量dexElements

 public void getPathAndDexElements() throws Exception{
        DexClassLoader dexClassLoader = getDexClassLoader();
        Object objDexCLassLoaderPathList = reflectPathList(dexClassLoader);
        Object objDexCLassLoaderPathListElements = reflectDexElements(objDexCLassLoaderPathList);
        if(objDexCLassLoaderPathListElements!=null){
            //Toast.makeText(this,"得到了DexCLassLoaderPathListElements",Toast.LENGTH_LONG).show();
        }
        PathClassLoader pathClassLoader = getPathClassLoader();
        Object objPathClassLoaderPathList = reflectPathList(pathClassLoader);
        Object objPathClassLoaderPathListElements = reflectDexElements(objPathClassLoaderPathList);
        if(objPathClassLoaderPathListElements!=null){
            //Toast.makeText(this,"得到了objPathClassLoaderPathListElements",Toast.LENGTH_LONG).show();
        }
        Object objCombine = combineElements(objDexCLassLoaderPathListElements,objPathClassLoaderPathListElements);
        fixComplete(objCombine);
    }
    public Object reflectPathList(Object obj) throws Exception{
        return  getField(obj,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
    }
    public Object reflectDexElements(Object obj)throws Exception{
        return  getField(obj,obj.getClass(),"dexElements");
    }
    public Object getField(Object obj,Class<?> c ,String field)throws Exception{
        Field f = c.getDeclaredField(field);
        f.setAccessible(true);
        return  f.get(obj);
    }

这样我们就得到了DexClassLoader和PathClassLoader的pathlist对象和pathlist中的dexElements对象,接下来我们需要对这两个Element进行合并得到一个新的Element并重新赋给PathClassLoader的Elements对象。

public Object combineElements(Object dexElementsObj,Object pathElementsObj) throws Exception{
        Class<?> cla = pathElementsObj.getClass().getComponentType();
        int dexElementsLength  = Array.getLength(dexElementsObj);
        int pathElementsLength  = Array.getLength(pathElementsObj);
        //Toast.makeText(this,"........."+dexElementsLength+"....."+pathElementsLength,Toast.LENGTH_LONG).show();
        int newLength = dexElementsLength+pathElementsLength;
        Object elements = Array.newInstance(cla,newLength);
        for (int i=0;i<newLength;i++){
            if (i < dexElementsLength) {
                Array.set(elements, i, Array.get(dexElementsObj, i));
            } else {
                Array.set(elements, i, Array.get(pathElementsObj, i - dexElementsLength));
            }
        }
        //Toast.makeText(this,"合并成功,新数组长度="+Array.getLength(elements),Toast.LENGTH_LONG).show();
        return elements;
    }

    public void fixComplete(Object newElements)throws Exception{
        PathClassLoader pathClassLoader = getPathClassLoader();
        Object objPathClassLoaderPathList = reflectPathList(pathClassLoader);
        Class cla = objPathClassLoaderPathList.getClass();
        Field field = cla.getDeclaredField("dexElements");
        field.setAccessible(true);
        field.set(objPathClassLoaderPathList,newElements);
        Toast.makeText(this,"修复完成",Toast.LENGTH_SHORT).show();
    }

这样我们就完成了。完整代码如下:

Activity相关:

import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.example.lenovo.testwidget.bean.HotFixBean;
import com.example.lenovo.testwidget.bean.HotFixBean2;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

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

public class HotFixActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hot_fix);
        findViewById(R.id.bt_damaged).setOnClickListener(this);
        findViewById(R.id.bt_damaged2).setOnClickListener(this);
        findViewById(R.id.bt_fixgood).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_damaged:
                new HotFixBean(this);
                break;
            case R.id.bt_damaged2:
                new HotFixBean2(this);
                break;
            case R.id.bt_fixgood:
                try {
                    getPathAndDexElements();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }
    public Object reflectPathList(Object obj) throws Exception{
        return  getField(obj,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
    }
    public Object reflectDexElements(Object obj)throws Exception{
        return  getField(obj,obj.getClass(),"dexElements");
    }
    public Object getField(Object obj,Class<?> c ,String field)throws Exception{
        Field f = c.getDeclaredField(field);
        f.setAccessible(true);
        return  f.get(obj);
    }
    public DexClassLoader getDexClassLoader(){
        File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator + "fixed.jar");
        File dexOutputDir = this.getDir(".dex", 0);
        DexClassLoader dexClassLoader = new DexClassLoader(path.getAbsolutePath(),dexOutputDir.getAbsolutePath(),null,getPathClassLoader());
        return dexClassLoader;
    }
    public PathClassLoader getPathClassLoader(){
        return (PathClassLoader) this.getClassLoader();
    }
    public void getPathAndDexElements() throws Exception{
        DexClassLoader dexClassLoader = getDexClassLoader();
        Object objDexCLassLoaderPathList = reflectPathList(dexClassLoader);
        Object objDexCLassLoaderPathListElements = reflectDexElements(objDexCLassLoaderPathList);
        if(objDexCLassLoaderPathListElements!=null){
            //Toast.makeText(this,"得到了DexCLassLoaderPathListElements",Toast.LENGTH_LONG).show();
        }
        PathClassLoader pathClassLoader = getPathClassLoader();
        Object objPathClassLoaderPathList = reflectPathList(pathClassLoader);
        Object objPathClassLoaderPathListElements = reflectDexElements(objPathClassLoaderPathList);
        if(objPathClassLoaderPathListElements!=null){
            //Toast.makeText(this,"得到了objPathClassLoaderPathListElements",Toast.LENGTH_LONG).show();
        }
        Object objCombine = combineElements(objDexCLassLoaderPathListElements,objPathClassLoaderPathListElements);
        fixComplete(objCombine);
    }

    public Object combineElements(Object dexElementsObj,Object pathElementsObj) throws Exception{
        Class<?> cla = pathElementsObj.getClass().getComponentType();
        int dexElementsLength  = Array.getLength(dexElementsObj);
        int pathElementsLength  = Array.getLength(pathElementsObj);
        //Toast.makeText(this,"........."+dexElementsLength+"....."+pathElementsLength,Toast.LENGTH_LONG).show();
        int newLength = dexElementsLength+pathElementsLength;
        Object elements = Array.newInstance(cla,newLength);
        for (int i=0;i<newLength;i++){
            if (i < dexElementsLength) {
                Array.set(elements, i, Array.get(dexElementsObj, i));
            } else {
                Array.set(elements, i, Array.get(pathElementsObj, i - dexElementsLength));
            }
        }
        //Toast.makeText(this,"合并成功,新数组长度="+Array.getLength(elements),Toast.LENGTH_LONG).show();
        return elements;
    }

    public void fixComplete(Object newElements)throws Exception{
        PathClassLoader pathClassLoader = getPathClassLoader();
        Object objPathClassLoaderPathList = reflectPathList(pathClassLoader);
        Class cla = objPathClassLoaderPathList.getClass();
        Field field = cla.getDeclaredField("dexElements");
        field.setAccessible(true);
        field.set(objPathClassLoaderPathList,newElements);
        Toast.makeText(this,"修复完成",Toast.LENGTH_SHORT).show();
    }
}

javabean相关,我直接修复了两个类

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

/**
 * Created by lenovo on 2019/5/5.
 */

public class HotFixBean {
    public HotFixBean(Context context){
        int i = 1000;
        int j = 0;
        int k = i/j;
        Toast.makeText(context,"HotFixBean结果为:"+k,Toast.LENGTH_SHORT).show();
    }
}

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

/**
 * Created by lenovo on 2019/5/5.
 */
public class HotFixBean2 {
    public HotFixBean2(Context context){
        int i = 10;
        int j =  0;
        int k = i/j;
        Toast.makeText(context,"HotFixBean2结果为:"+k,Toast.LENGTH_SHORT).show();
    }
}

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.lenovo.testwidget.HotFixActivity">

    <Button
        android:textSize="20sp"
        android:text="损坏的1"
        android:id="@+id/bt_damaged"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:textSize="20sp"
        android:text="损坏的2"
        android:id="@+id/bt_damaged2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:textSize="20sp"
        android:layout_marginTop="100dp"
        android:text="修复它"
        android:id="@+id/bt_fixgood"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

别忘了权限,6.0要动态申请,我省事是在手机上手动开启的。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

还有就是,要先点击修复才生效,因为类一旦被加载就更改不了了,正常情况是要把代码放在Application下的。
不要直接把项目run在手机上,要使用apk安装到手机上才能生效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值