最近公司打算本身在Tinker的基礎上作二次開發,由於TinkerPatch是要收費的,並且價格不菲,說白了就是想白嫖!android
在進入主題以前咱們先來了解下Android的 ClassLoder,android 的 ClassLoader 主要又如下幾種:PathClassLoader、DexClassLoader等,咱們在Application中經過getClassLoader()得知ClassLode是 PathClassLoader ,Tinker就是經過處理PathClassLoader來實現類的熱修復。先來看看 PathClassLoader 源碼,PathClassLoader是繼承BaseDexClassLoader重要的操做都在裏面,ClassLoader 重要的方法就是findClass,以下:數組
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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的findClass()裏面,以下:bash
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
複製代碼
經過遍歷dexElements執行 Element 的 findClass() ,只要找到就返回。Element能夠理解爲一個dex文件的表示,Tinker就是經過發射修改 dexElements 這個 Element 數組,將補丁裏面的dex對應的 Element 插入到 dexElements 前面來實現類的熱修復;好比 patch.dex 裏面有 A.class ,原來的舊的apk裏面也有 A.class ,因爲 patch.dex 對應的 Element 在前面,每次調用 A.class 的方法時都是走的 patch.dex 裏面的邏輯。如下是 Tinker 裏扒下來的關鍵代碼,作了部分刪減,這個是 android6.0 及其以上的設備:app
public class SystemClassLoaderAdder {
private static final String TAG = "SystemClassLoaderAdder";
public static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Log.d(TAG, "ClassLoader =" + loader.getClass().getCanonicalName());
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makePathElement", e);
throw e;
}
}
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makePathElements}.
*/
private static Object[] makePathElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makePathElements = null;
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
List.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
} catch (NoSuchMethodException e1) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
}
}
return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
}
複製代碼
在 HotFixApplication 的 attachBaseContext() 中增長以下代碼:ide
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
List<File> fileList = new ArrayList<>(1);
File patchFile = new File(getCacheDir().getAbsolutePath() + "/classes.dex");
if (patchFile.exists()) {
fileList.add(patchFile);
}
patchFile = new File(getCacheDir().getAbsolutePath() + "/classes2.dex");
if (patchFile.exists()) {
fileList.add(patchFile);
}
if (fileList.size() > 0) {
try {
SystemClassLoaderAdder.install(getClassLoader(), fileList, new File(getDataDir().getAbsolutePath() + "/odex"));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
在classes.dex、classes2.dex是修改以後的apk解壓以後出來,這裏爲了圖方便,直接把補丁的 dex 放在 cache 目錄下,殺掉進程,重啓就會生效。ui