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自動化打包補丁