单个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
参考资料: