Android熱修復實現一

1. 前言

前兩天跟着享學的課學了一下熱修復的原理

這裏記錄一下

2.  知識點

  • 反射Reflect
  • 類加載器ClassLoader
  • Gradle開發
  • dex文件打包

3. 原理

在Android中所有我們運行期間需要的類都是由ClassLoader(類加載器)進行加載。因此讓ClassLoader加載全新的類替換掉出現Bug的類即可完成熱修復。

所以我們需要作的工作

1. 將需要修復的.class文件打包成一個單獨dex包

2.通過反射PathClassLoader將這個dex文件與原來的dex文件合併

具體來說

第一步:獲取到當前應用的PathClassloader

第二步:反射獲取到DexPathList屬性對象pathList

第三步:反射修改pathList的dexElements

目的是將我們的補丁dex文件插入到dexElements

4. 代碼實現

反射工具類


public class SharedReflectUtil {


    /**
     * 反射獲取屬性對象
     * @param object 類對象
     * @param fieldName 屬性名稱
     * @return 屬性對象
     */
    public static Field getField(Object object, String fieldName) throws NoSuchFieldException {
        for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {

            System.out.println("getField.cls:" + cls.getName());

            Field[] declaredFields = cls.getDeclaredFields();
            System.out.println("fileds.size:" + declaredFields.length);
            for (Field field : declaredFields) {
                System.out.println(field.getName());
            }

            try {
                Field declaredField = cls.getDeclaredField(fieldName);
                //設置訪問權限
                declaredField.setAccessible(true);
                return declaredField;
            } catch (NoSuchFieldException e) {
                //如果沒找到,就要去父類找
            }
        }

        throw new NoSuchFieldException( object.getClass().getSimpleName() + " No Such Filed:" + fieldName);
    }


    public static Method getMethod(Object object, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {

        for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {

            System.out.println("getMethod.cls:" + cls.getName());

            Method[] declaredMethods = cls.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println(method.getName());
            }

            try {
                Method declaredMethod = cls.getDeclaredMethod(methodName, parameterTypes);
                //設置訪問權限
                declaredMethod.setAccessible(true);
                return declaredMethod;
            } catch (NoSuchMethodException e) {
                //如果沒找到,就要去父類找
            }
        }

        throw new NoSuchMethodException( object.getClass().getSimpleName() + " No Such Method:" + methodName);
    }

}

補丁安裝

public static void installPatch(Context context, String patchPath) {
        File patchFile = new File(patchPath);
        if (!patchFile.exists()) {
            Log.e(TAG, "installPatch: " + patchPath + " is not exists");
            return;
        }

        Log.d(TAG, "installPatch: " + patchPath);

        File cacheFile = context.getCacheDir();

        //PathClassLoader
        ClassLoader classLoader = context.getClassLoader();

        //獲取pathList屬性對象,這個對象存在於其父類 BaseDexClassLoader中
        try {
            Field pathListField = SharedReflectUtil.getField(classLoader, "pathList");
            Log.i(TAG, "installPatch: find field:" + pathListField.getName() + "!!!");

            //通過get得到pathList的示例對象Object DexPathList
            Object pathList = pathListField.get(classLoader);

            //獲取DexPathList中的  dexElements的示例對象
            Field dexElementsField = SharedReflectUtil.getField(pathList, "dexElements");
            Log.i(TAG, "installPatch: find field:" + dexElementsField.getName() + "!!!");

            Object[] dexElements = (Object[]) dexElementsField.get(pathList);
            //執行DexPathList的makeDexElements,將我們的dex轉換成Element[]

            ArrayList<IOException> suppressedExceptions = new ArrayList<>();
            File file = new File(patchPath);
            ArrayList<File> files = new ArrayList<>();
            files.add(file);
            Method makeDexElementsMethod;
            Object[] newElements;
            if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //6.0以上
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makePathElements",
                        List.class, File.class, List.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //4.4-6.0
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
                        ArrayList.class, File.class, ArrayList.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
                //4.4 會有一個bug
                //Class ref in pre-verified class resolved to unexpected implementation
            } else {
                //4.0-4.3
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
                        ArrayList.class, File.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile);
            }

            //創建一個新的數組,將舊的Element數組跟newElement數組進行合併
            Object[] replaceDexElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + newElements.length);
            System.arraycopy(newElements, 0, replaceDexElements, 0, newElements.length);
            System.arraycopy(dexElements, 0, replaceDexElements, newElements.length, dexElements.length);

            //替換屬性值
            dexElementsField.set(pathList, replaceDexElements);
            Log.i(TAG, "installPatch: SUCCESS");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

然後在Application中執行安裝即可

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ClassLoader classLoader = getClassLoader();
        System.out.println(classLoader);

    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        HotFix.installPatch(this, "/sdcard/patch.jar");
    }
}

最後如何生成這個補丁jar文件呢

修復好bug後,編譯一次

在下面目錄中,找到修復後的.class文件

然後利用sdk中的dx工具,將class文件打包成jar或者dex文件

命令:  dx --dex --output=patch.jar MyUtils.class

然後拷貝到sdcard下就可以重啓app開始修復了

但是這樣打包補丁文件真的很蠢,萬一一個app中有10幾個文件需要修復

這樣很沒有效率

下篇文章將講述如何使用Gradle自動化打包補丁

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章