版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
DexClassLoader
DexClassLoader 可以加载任意路径下的 dex,或者 jar、apk、zip 文件(包含classes.dex)。常用于插件化、热修复以及 dex 加壳。
源码如下:
scala
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
参数说明
参数 | 类型 | 说明 |
---|---|---|
dexPath | String | 需要加载的 dex、apk、jar、zip 文件的路径,多个路径用 : 分隔。支持任意目录下的文件。 |
optimizedDirectory | String | 用于存放优化后的 dex 文件(即 .odex),在 API level 26 已弃用(Android 8.0 Oreo)。 |
librarySearchPath | String | 指定本地库(native library,.so 文件)搜索路径,多个路径用 : 分隔。 |
parent | ClassLoader | 父类加载器,用于实现类加载的委托机制。 |
动态加载
动态加载 = 运行时按需加载代码或资源
动态加载 是实现 dex加壳、插件化、热更新、热修复 的基础。比如阿里的 AndFix 、腾讯 tinker、美团 Robust 等热修复框架的基础。
使用 DexClassLoader 加载一个外部 .dex 或 .apk 文件,然后反射调用里面的类和方法。
步骤如下:
-
准备好外部的 dex / apk / jar;
-
将它放在你 app 可以访问的路径(如 /data/data/包名/files/);
-
用 DexClassLoader 加载它;
-
使用反射调用其中的类和方法。
1. 插件工程示例
插件工程主要包含这两个类:

PluginActivity 源码:使用 Jetpack Compose 创建一个白色背景、居中显示文本的界面,文本内容为 "PluginActivity from plugin" 加上 ClassLoader 信息
kotlin
package com.cyrus.example.plugin
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class PluginActivity : ComponentActivity() {
private val TAG = "PluginActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate")
val classLoaderInfo = this.javaClass.classLoader.toString()
setContent {
PluginActivityContent(classLoaderInfo)
}
}
@Composable
fun PluginActivityContent(classLoaderInfo: String) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "PluginActivity from plugin\n\n$classLoaderInfo",
fontSize = 18.sp,
color = Color.Black
)
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
}
}
PluginClass 源码:
kotlin
package com.cyrus.example.plugin
class PluginClass {
fun getString(): String {
return "String from plugin."
}
}
编译 apk

把 apk 推送到设备 sdcard
bash
adb push plugin-debug.apk /sdcard/Android/data/com.cyrus.example/files
2. 动态加载示例
创建 DexClassLoader 实例,加载指定路径下的 APK/DEX 文件
less
val apkPath = "/sdcard/Android/data/com.cyrus.example/files/plugin-debug.apk"
// 创建 DexClassLoader 加载 sdcard 上的 apk
val classLoader = DexClassLoader(
apkPath,
null,
[email protected],
context.classLoader // parent 设为当前 context 的类加载器
)
调用示例:
ini
// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String
output = "动态加载:${pluginPath}\n\ncall ${method}\n\nreuslt=${result}"
效果如下,可以看到正常调用了 apk 中 PluginClass 的 getString 方法并拿到了返回值

组件类 ClassNotFoundException
在 AndroidManifest.xml 中声明 PluginActivity
ini
<activity
android:name="com.cyrus.example.plugin.PluginActivity"
android:exported="true">
</activity>
通过自定义 ClassLoader 加载 PluginActivity 类并启动
less
// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
[email protected](intent)
报错如下:
less
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.cyrus.example/com.cyrus.example.plugin.PluginActivity}: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44)
at android.app.Instrumentation.newActivity(Instrumentation.java:1250)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3182)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
断点调试看看,PluginActivity 类是正常加载了的

原因是:
-
动态加载的 dex 不具有生命周期特征,APP 中的 Activity 、Service 等组件无法正常工作,只能完成一般函数的调用;
-
需要对 ClassLoader 进行修正,APP 才能够正常运行。
生命周期类处理
DexClassLoader 加载的类是没有组件生命周期的,也就是说即使 DexClassLoader 通过对 APK 动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常。
为什么组件类被动态加载入虚拟机,但系统却出现加载类失败呢?
因为 系统在启动组件(如 Activity、Service)时,是通过 AMS → ActivityThread → Instrumentation 最终调用 Class.forName(组件类名) 来加载组件类的,而这个过程默认使用的是系统的 ClassLoader(通常是 PathClassLoader),而不是我们自定义的 DexClassLoader。
从 ClassLoader 来看,两种解决方案:
-
替换系统组件类加载器为我们的 DexClassLoader,同时设置 DexClassLoader 的 parent 为系统组件类加载器
-
打破原有的双亲关系,在系统组件类加载器和 BootClassLoader 的中间插入我们自己的 DexClassLoader 即可
或者可以对 PathClassLoader 中的 Elements 进行合并(常用于热修复框架(如 Tinker、Robust))。
相关文章:Android 下的 ClassLoader 与 双亲委派机制
方案 1:替换 ClassLoader 为 自定义ClassLoader
变化前结构(系统默认)
csharp
[BootClassLoader]
↑
[PathClassLoader] ← 原来的 ClassLoader
↑
Activity、Application 加载组件类
变化后结构(方案1)
csharp
[BootClassLoader]
↑
[PathClassLoader] ← 原来的 ClassLoader
↑
[DexClassLoader] ← 反射替换 LoadedApk.mClassLoader
↑
Activity、Application 加载组件类
通过 反射替换掉 系统的 ClassLoader 为自定义的 ClassLoader,同时设置 parent 为 系统的 ClassLoader
示例代码:
kotlin
private fun replaceClassLoader(context: Context): ClassLoader? {
try {
// 1. 创建自定义 ClassLoader 实例,加载 sdcard 上的 apk
val classLoader = DexClassLoader(
apkPath,
null,
[email protected],
context.classLoader // 设置 parent 为 系统的 ClassLoader
)
// 2. 拿到 ActivityThread
val activityThreadClass = Class.forName("android.app.ActivityThread")
val currentActivityThread = activityThreadClass
.getMethod("currentActivityThread")
.invoke(null)
// 3. 拿到 mPackages 字段: Map<String, WeakReference<LoadedApk>>
val mPackagesField = activityThreadClass.getDeclaredField("mPackages")
mPackagesField.isAccessible = true
val mPackages = mPackagesField.get(currentActivityThread) as Map<*, *>
// 4. 拿到当前包名对应的 LoadedApk 实例
val loadedApkRef = mPackages[context.packageName] as? WeakReference<*>
?: throw IllegalStateException("LoadedApk not found for package: ${context.packageName}")
val loadedApk = loadedApkRef.get()
?: throw IllegalStateException("LoadedApk is null")
// 5. 替换 LoadedApk.mClassLoader
val loadedApkClass = loadedApk.javaClass
val mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader")
mClassLoaderField.isAccessible = true
mClassLoaderField.set(loadedApk, classLoader)
// ✅ 替换成功
Log.d(TAG, "✅ ClassLoader has been replaced successfully!")
return classLoader
} catch (e: Exception) {
e.printStackTrace()
Log.d(TAG, "❌ Failed to replace ClassLoader: ${e.message}")
}
return null
}
-
Android 每个 App 都有一个 LoadedApk 对象负责管理 dex 加载;
-
mClassLoader 是决定类加载的实际执行者;
-
修改它就等于修改了整个 App 的"类查找逻辑"。
调用示例:
kotlin
// 方案 1:替换 ClassLoader 为 自定义ClassLoader
val classLoader = replaceClassLoader(context)
if (classLoader == null) {
Log.d(TAG, "❌ Failed to replace ClassLoader")
return@Button
}
// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String
// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
[email protected](intent)
output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"
成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

日志输出如下,生命周期相关方法也是正常执行

方案 2:插入中间 ClassLoader / 打破双亲委派
变化前结构
csharp
[BootClassLoader]
↑
[PathClassLoader] ← 原始 ClassLoader
变化后结构(方案2)
csharp
[BootClassLoader]
↑
[DexClassLoader] ← 反射插入自定义的 DexClassLoader
↑
[PathClassLoader] ← 原始 ClassLoader
在 BootClassLoader 和 PathClassLoader 中间 通过反射插入 自定义的 DexClassLoader
通过反射修改 PathClassLoader 的 parent 为 自定义ClassLoader,并设置 自定义ClassLoader 的 parent 为 BootClassLoader
目标结构如下:
-
PathClassLoader.parent = 自定义ClassLoader
-
自定义ClassLoader.parent = BootClassLoader
通过反射获取并修改 PathClassLoader 的 parent 字段
kotlin
private fun injectClassLoader(context: Context): DexClassLoader? {
try {
// 拿到当前 PathClassLoader
val appClassLoader = context.classLoader
val pathClassLoaderClass = ClassLoader::class.java
// 反射访问 parent 字段
val parentField = pathClassLoaderClass.getDeclaredField("parent")
parentField.isAccessible = true
val bootClassLoader = ClassLoader.getSystemClassLoader().parent
// 自定义ClassLoader.parent = BootClassLoader
val classLoader = DexClassLoader(
apkPath,
null,
[email protected],
bootClassLoader // 设置 parent 为 BootClassLoader
)
// PathClassLoader.parent = 自定义ClassLoader
parentField.set(appClassLoader, classLoader)
Log.d(TAG, "✅ 成功将 ${classLoader} 注入到 PathClassLoader.parent")
return classLoader
} catch (e: Exception) {
e.printStackTrace()
Log.d(TAG, "❌ 注入失败:${e.message}")
}
return null
}
调用示例:
kotlin
// 方案 2:插入中间 ClassLoader / 打破双亲委派
val classLoader = injectClassLoader(context)
if (classLoader == null) {
Log.d(TAG, "❌ Failed to replace ClassLoader")
return@Button
}
// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String
// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
[email protected](intent)
output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"
成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

日志输出如下,生命周期相关方法也是正常执行

这样你就"无缝拦截"了类加载流程,同时也保留系统 PathClassLoader 的全部能力,比完全替换更可靠 💯
加壳应用的运行流程
参考文章:
壳执行的时机必须比 app 原来的逻辑早。
在 APP启动流程中我们最终可以得出结论,app 最先获得执行权限的是 app 中声明的 Application 类中的 attachBaseContext 和 onCreate 函数。因此,壳要想完成应用中加固代码的解密以及应用执行权的交付就都是在这两个函数上做文章。
加壳应用运行流程:
scss
→ AMS 发起启动
→ Zygote fork → ActivityThread.main
→ ActivityThread.handleBindApplication()
→ 壳Application.attachBaseContext(base)
↳ 解密 dex
↳ 自定义 ClassLoader 加载解密 dex
↳ 反射设置 LoadedApk.mClassLoader 为自定义 ClassLoader
→ 壳Application.onCreate()
↳ 反射生成真实的 Application 对象
↳ 反射替换 ActivityThread.mInitialApplication 为真实 Application 对象
↳ 已进入原始 app 世界