更新 2026-02-03 00:28 UTC+8
前言
动态加载技术广泛用于插件加载、热更新、模块化
搭配 Hook 技术可实现运行前动态注入
本章不作底层深入研究
所有测试在 Android 8.1(SDK 27, MIUI 11) 和 Android 12(SDK 31, MIUI 14) 通过
动态加载 Dex
加载 Dex 到新的 ClassLoader
Java1 2 3 4 5 6
| String dexFilePath = "path/to/file.dex";
File odexDir = getDir("dex", Context.MODE_PRIVATE); DexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, odexDir.getAbsolutePath(), null, getClassLoader());
Class<?> clazz = dexClassLoader.loadClass("tc.InternalClass");
|
加载 Dex 到全局 ClassLoader
通过反射把 Dex 文件添加到应用程序的 dexPath
Java1 2 3 4 5 6 7 8 9 10 11 12
| String dexFilePath = "path/to/file.dex";
ClassLoader classLoader = getClassLoader();
Field pathListField = classLoader.getClass().getSuperclass().getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader);
Method addDexPath = pathList.getClass().getDeclaredMethod("addDexPath", String.class, File.class); addDexPath.invoke(pathList, dexFilePath, null);
Class<?> clazz = classLoader.loadClass("tc.InternalClass");
|
公有目录(/sdcard/)下的 Dex 文件具有可写权限,出于安全,一般安卓设备不被允许加载可写文件,因此需要复制到内部私有目录再设置只读后加载
Java1 2 3 4
| String internalDexFilePath = getCacheDir().getAbsolutePath() + "/a.dex"; fileCopy(dexFilePath, internalDexFilePath); new File(internalDexFilePath).setReadOnly(); addDexPath.invoke(pathList, internalDexFilePath, null);
|
动态加载 SO (原生共享库)
直接加载
Java1
| System.load("/path/to/libinternal.so");
|
添加 SO 文件目录到全局
通过反射把 SO 文件目录添加到应用程序的 nativePath
此方法需要 Android 9.0(SDK 28) 及以上系统
Java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| String soFilesDir = "path/to/libs/";
ClassLoader classLoader = getClassLoader();
Field pathList = classLoader.getClass().getSuperclass().getDeclaredField("pathList"); pathList.setAccessible(true);
Method addNativePath = pathList.getClass().getDeclaredMethod("addNativePath", Collection.class); ArrayList<String> dirList = new ArrayList<>(); dirList.add(soFilesDir); addNativePath.invoke(pathList, dirList);
Field libDirsField = pathList.getClass().getDeclaredField("nativeLibraryDirectories"); libDirsField.setAccessible(true); List<File> dirList2 = (List<File>) libDirsField.get(pathList); dirList2.add(new File(soFilesDir)); libDirsField.set(pathList, dirList2);
System.loadLibrary("internal");
|
已经加载过的类使用的是旧的 pathList,因此无法使用 System.loadLibrary() 加载 SO
目录里的所有 SO 文件都可被加载
公有目录 SO 文件的处理方法同 Dex 文件
动态加载资源文件(Assets, Resources)
通过反射添加外部 APK/ZIP 的资源文件到全局
此方法会覆盖应用原有资源
此方法会把 APK/ZIP 中的 assets/, res/, resources.arsc 添加到应用,因此需要处理资源 ID 重复的问题
Java1 2 3 4 5 6 7 8 9
| String apkPath = "/sdcard/test.apk";
AssetManager am = getAssets(); Method addAssetPath = am.getClass().getDeclaredMethod( "addAssetPath", String.class); addAssetPath.invoke(am, apkPath);
|
添加外部资源不用处理是否可写的问题,因此可以直接添加公有目录的 APK/ZIP 文件
处理资源 ID 重复问题
在模块级 build.gradle 的 android.aaptOptions 中添加 additionalParameters
app/build.gradle1 2 3 4 5
| android { aaptOptions { additionalParameters '--package-id', '0x02', '--allow-reserved-package-id' } }
|
资源 ID 前缀可用的范围: 0x02 ~ 0x7F,确保应用程序资源前缀和外部资源前缀不重复
参考文献