跳到主要内容

设计插件化框架

问题

如何设计一个 Android 插件化框架,实现动态加载未安装的 APK?

答案

核心挑战

插件化要让未安装的 APK 像正常 App 一样运行,需要解决三个核心问题:

问题原因方案
类加载插件的 dex 不在宿主 ClassLoader 中为插件创建独立 DexClassLoader
资源加载插件的资源不在宿主 AssetManager通过反射添加插件 APK 的资源路径
组件生命周期未注册的 Activity 无法被 AMS 启动Hook Instrumentation 或用占坑 Activity 代理

架构设计

类加载

class PluginClassLoader(
pluginApkPath: String,
optimizedDir: String,
nativeLibDir: String,
parent: ClassLoader
) : DexClassLoader(pluginApkPath, optimizedDir, nativeLibDir, parent) {

// 可覆写 findClass 实现"双亲委派"或"先插件后宿主"策略
override fun loadClass(name: String, resolve: Boolean): Class<*> {
// 先查自身缓存
var clazz = findLoadedClass(name)
if (clazz != null) return clazz

// 系统类交给父 ClassLoader
if (name.startsWith("java.") || name.startsWith("android.")) {
return parent.loadClass(name)
}

// 优先从插件 dex 中查找
return try {
findClass(name)
} catch (e: ClassNotFoundException) {
parent.loadClass(name)
}
}
}

Activity 代理(占坑方案)

在宿主 AndroidManifest.xml 中预注册若干占坑 Activity,启动插件 Activity 时用占坑 Activity 欺骗 AMS:

// Hook Instrumentation,拦截 startActivity
class PluginInstrumentation(
private val base: Instrumentation
) : Instrumentation() {

// 1. 发起启动 → 将目标替换为占坑 Activity,骗过 AMS
fun execStartActivity(/*...*/intent: Intent/*...*/): ActivityResult? {
val targetClass = intent.component?.className
// 把真正要启动的插件 Activity 信息保存到 extra
intent.putExtra("plugin_target", targetClass)
// 替换为宿主中已注册的占坑 Activity
intent.setClassName(hostPackage, "com.host.StubActivity")
return base.execStartActivity(/*...*/)
}

// 2. AMS 校验通过后,在真正创建 Activity 时换回插件类
override fun newActivity(cl: ClassLoader, className: String, intent: Intent): Activity {
val pluginTarget = intent.getStringExtra("plugin_target")
if (pluginTarget != null) {
// 用插件的 ClassLoader 加载真正的 Activity 类
return pluginClassLoader.loadClass(pluginTarget)
.newInstance() as Activity
}
return super.newActivity(cl, className, intent)
}
}
局限性
  • Android 高版本对反射系统 API(@hide)加了限制
  • Google Play 明确禁止动态代码加载
  • 维护成本高,每个 Android 版本都可能需要适配
  • 主要用于国内超级 App(如微信、支付宝的小程序容器)

常见面试问题

Q1: 插件化和组件化的区别?

答案

  • 组件化:编译时将 App 拆分为多个 module,最终打包为一个 APK。主要解决工程管理问题(编译隔离、独立开发)
  • 插件化:运行时动态加载未安装的 APK。主要解决动态化问题(热更新、功能按需下载)

组件化是工程架构方案,插件化是运行时加载方案。

Q2: 为什么需要占坑 Activity?

答案

AMS(ActivityManagerService)在启动 Activity 时会检查 AndroidManifest.xml 中是否注册了该组件。插件 APK 未安装,其组件不在系统的 PackageManagerService 记录中,直接启动会抛 ActivityNotFoundException。占坑 Activity 是宿主中预注册的"空壳",用它通过 AMS 的校验,创建时再替换为真正的插件 Activity。

相关链接