單個Dex文件裏面方法數不能超過65536個方法。java
(1)緣由:
由於android會把每個類的方法id檢索起來,存在一個鏈表結構裏面。可是這個鏈表的長度是用一個short類型來保存的, short佔兩個字節(保存-2的15次方到2的15次方-1,即-32768~32767),最大保存的數量就是65536。android
(2)解決方案:數組
Dex分包方案主要作的是在打包時將應用代碼分紅多個Dex,將應用啓動時必須用到的類和這些類的直接引用類放到主Dex中,其餘代碼放到次Dex中。當應用啓動時先加載主Dex,等到應用啓動後再動態的加載次Dex。bash
若是Key.Class文件中存在異常,將該Class文件修復後,將其打入Patch.dex的補丁包
(1) 方案一:
經過反射獲取到PathClassLoader中的DexPathList,而後再拿到 DexPathList中的Element數組,將Patch.dex放在Element數組dexElements的第一個元素,最後將數組進行合併後並從新設置回去。在進行類加載的時候,因爲ClassLoader的雙親委託機制,該類只被加載一次,也就是說Patch.dex中的Key.Class會被加載。 微信
方案一:框架
方案二:性能
主要是在Native層替換原有方法,ArtMethod結構體中包含了Java方法的全部信息,包括執行入口、訪問權限、所屬類和代碼執行地址等。替換ArtMethod結構體中的字段或者替換整個ArtMethod結構體,就是底層替換方案。因爲直接替換了方法,能夠當即生效不須要重啓。gradle
(1)缺點ui
(2)優勢this
核心代碼:runtime/MonkeyPatcher.java
#MonkeyPatcher
public static void monkeyPatchExistingResources(@Nullable Context context,
@Nullable String externalResourceFile,
@Nullable Collection<Activity> activities) {
......
try {
// Create a new AssetManager instance and point it to the resources installed under
// (1)經過反射建立了一個newAssetManager,調用addAssetPath添加了sdcard上的資源包
AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance();
Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
mAddAssetPath.setAccessible(true);
if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) {
throw new IllegalStateException("Could not create new AssetManager");
}
// Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
// in L, so we do it unconditionally.
Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
mEnsureStringBlocks.setAccessible(true);
mEnsureStringBlocks.invoke(newAssetManager);
if (activities != null) {
//(2)反射獲取Activity中AssetManager的引用,替換成新建立的newAssetManager
for (Activity activity : activities) {
Resources resources = activity.getResources();
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
Resources.Theme theme = activity.getTheme();
try {
try {
Field ma = Resources.Theme.class.getDeclaredField("mAssets");
ma.setAccessible(true);
ma.set(theme, newAssetManager);
} catch (NoSuchFieldException ignore) {
Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
themeField.setAccessible(true);
Object impl = themeField.get(theme);
Field ma = impl.getClass().getDeclaredField("mAssets");
ma.setAccessible(true);
ma.set(impl, newAssetManager);
}
......
}
//(3)遍歷Resource弱引用的集合,將AssetManager替換成newAssetManager
for (WeakReference<Resources> wr : references) {
Resources resources = wr.get();
if (resources != null) {
// Set the AssetManager of the Resources instance to our brand new one
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
}
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
複製代碼
默認由Android SDK編譯出來的apk,其資源包的package id爲0x7f。framework-res.jar的資源package id爲0x01
本質是對native方法的修復和替換
(1)經過如下方法加載so庫
#System
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
參數爲so庫名稱,位於apk的lib目錄下
public static void load(String filename) {
Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
}
加載外部自定義so庫文件,參數爲so庫在磁盤中的完整路徑
複製代碼
private static native String nativeLoad(String filename, ClassLoader loader, String librarySearchPath);
複製代碼
最終都是調用了native方法nativeLoad,參數fileName爲so在磁盤中的完整路徑名
(2)遍歷nativeLibraryDirectories目錄
#DexPathList
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (File directory : nativeLibraryDirectories) {
File file = new File(directory, fileName);
if (file.exists() && file.isFile() && file.canRead()) {
return file.getPath();
}
}
return null;
}
複製代碼
相似於類加載的findClass方法,在數組中每個元素對應一個so庫,最終返回了so的路徑。若是將so補丁添加到數組的最前面,在調用方法加載so庫時,會先將補丁so的路徑返回。
提供方法替代System.loadLibrary方法
由於加載so庫會遍歷nativeLibraryDirectories
參考資料: