Android Class加載機制
使用該種方式實現就是要了解Android class的加載機制。然後利用反射進行hook。
通過類關係可以看出主要有,PathClassLoader,DexClassLoader兩個loader。
- PathClassLoader:主要用於加載/data/app/中的apk
- DexClassLoader:可以加載指定路徑的apk,dex,jar
從源碼可以看出主要功能實現還是在BaseDexClassLoader中
BaseDexClassLoader中查找class方法
private final DexPathList pathList;
...
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 該方法又調用了DexPathList中的方法
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
DexPathList中查找class的代碼
private Element[] dexElements;
...
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
// 如果找到目標class就不會執行後面的把我們要修改的class放到前面即可完成替換
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
Element則主要保存dex有關的信息,可以看看系統給我們的對象裏面都有哪些信息,對照着創建一個就好了。
實驗查看數據
try {
ClassLoader classLoader = getClassLoader();
L.i(classLoader.getClass().getSimpleName());
Object pathList = ReflexUtils.getProperty(classLoader, "pathList");
L.i(pathList.toString());
} catch (Exception e) {
e.printStackTrace();
}
04-09 18:34:13.900 I/[TS-FH]: [ onCreate ]PathClassLoader(MainActivity.java:18)
04-09 18:34:13.901 I/[TS-FH]: [ onCreate ]DexPathList[[zip file "/data/app/com.wxfjava.struggle-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]](MainActivity.java:22)
打修復包
# --output=f.dex輸出文件名稱 源class,就目錄下所有class文件打包
dx --dex --output=f.dex **/*.class
Android中目錄說明
- /system/app/系統自帶應用存儲位置
- /data/app/用戶應用目錄
- /data/data/應用數據存儲目錄
- /data/dalvik-cache/dex文件存儲位置
應用安裝:將apk複製到/data/app/下,dex文件放到/data/dalvik-cache/目錄,並在數據目錄創建對應的數據信息。
代碼實現
try {
// 將修復dex複製到工作目錄下
File srcFile = new File(FIX_DEX_FILE);
File dexFile = new File(dexPath.getAbsolutePath(), srcFile.getName());
FileUtils.copyFile(srcFile, dexFile);
ClassLoader pathClassLoader = mContext.getClassLoader();
Object pathList = ReflexUtils.getProperty(pathClassLoader, "pathList");
Object dexElements = ReflexUtils.getProperty(pathList, "dexElements");
int length = Array.getLength(dexElements);
DexClassLoader dexClassLoader = new DexClassLoader(dexFile.getAbsolutePath(), foptPath, null, pathClassLoader);
Object fixPathList = ReflexUtils.getProperty(dexClassLoader, "pathList");
Object fixDexElements = ReflexUtils.getProperty(fixPathList, "dexElements");
int fixLength = Array.getLength(fixDexElements);
Object newDexElements = Array.newInstance(Array.get(dexElements, 0).getClass(), length + fixLength);
System.arraycopy(fixDexElements, 0, newDexElements, 0, fixLength);
L.i("l:" + length + ",fixl:" + fixLength);
System.arraycopy(dexElements, 0, newDexElements, fixLength, length);
boolean result = ReflexUtils.setProperty(pathList, "dexElements", newDexElements);
L.i(result + "-" + pathList.toString());
} catch (Exception e) {
L.e(e);
}
後記
這種方式只能在有問題的class加載之前進行替換修復纔會有效。如果已經加載了有問題的class,進行替換是無效的,所以修復的代碼要放到Application中進行執行。
要實現實時修復,就要用到Andfix的實現方式了。直接修改問題方法的內存地址指向完成實時修復。