目录
  1. 1. 一、插件化的核心挑战
    1. 1.1. 1.1 插件化的技术演进
  2. 2. 二、类的加载:DexClassLoader 机制
    1. 2.1. 2.1 Android ClassLoader 体系
    2. 2.2. 2.2 Android 类加载流程
    3. 2.3. 2.3 插件化 ClassLoader 的层级设计
    4. 2.4. 2.4 DexElements 合并技术(热修复也用到的核心技术)
    5. 2.5. 2.5 类的隔离与共享
  3. 3. 三、资源的加载:AssetManager 与 addAssetPath
    1. 3.1. 3.1 Resources 加载链路
    2. 3.2. 3.2 创建插件 Resources
    3. 3.3. 3.3 资源 ID 冲突解决方案
    4. 3.4. 3.4 Android 8.0+ 的资源加载变化
  4. 4. 四、Activity 的插件化:代理模式 vs Hook 模式
    1. 4.1. 4.1 代理模式(Proxy Activity Pattern)
    2. 4.2. 4.2 Hook 模式(AMS Hook)
    3. 4.3. 4.3 代理模式 vs Hook 模式对比
  5. 5. 五、Service 和 BroadcastReceiver 的插件化
    1. 5.1. 5.1 Service
    2. 5.2. 5.2 BroadcastReceiver
    3. 5.3. 5.3 ContentProvider
  6. 6. 六、主流插件化框架深度对比
    1. 6.1. 6.1 RePlugin(360 开源)
    2. 6.2. 6.2 VirtualApp(商业级沙箱)
    3. 6.3. 6.3 Shadow(腾讯开源)
    4. 6.4. 6.4 各框架对比总结
  7. 7. 七、Android App Bundle 与插件化的关系
  8. 8. 八、面试常问题目
解读开源框架系列-插件化框架设计

一、插件化的核心挑战

插件化(Pluginization)是比组件化更激进的技术方案:业务模块在编译期完全独立,运行时从服务器下载并动态加载到宿主 App 中。这需要攻克 Android 系统设计的多个壁垒——Android 在设计之初并不支持动态加载 APK。

插件化需要解决四大问题:

  1. 类的加载:插件 APK 中的类如何被宿主 ClassLoader 找到并加载?
  2. 资源的加载:插件 APK 中的图片、布局、字符串等资源如何被访问?
  3. 四大组件的加载:插件中的 Activity/Service/BroadcastReceiver/ContentProvider 如何正常工作?这些组件必须在 AndroidManifest.xml 中注册,而插件的 Manifest 没有被系统解析。
  4. 进程隔离与安全保障:如何防止插件代码影响宿主应用的稳定性?

1.1 插件化的技术演进

2013-2015: 萌芽期
- 基于反射的动态加载(dynamic-load-apk)
- 基本的 ClassLoader 替换

2015-2017: 爆发期
- DroidPlugin(360):代理模式 + Hook AMS
- RePlugin(360):坑位机制
- VirtualAPK(滴滴):多方案融合
- Atlas(阿里):组件化 + 动态部署
- Small(林光宇):轻量级插件化

2017-2020: 成熟期
- VirtualApp:应用沙箱/多开
- Shadow(腾讯):零反射 + Transform API
- RePlugin 2.0:稳定方案

2020-至今: 官方替代
- Android App Bundle (AAB)
- Play Feature Delivery
- 插件化逐渐被 AAB + In-App Update 替代

二、类的加载:DexClassLoader 机制

2.1 Android ClassLoader 体系

Android 的 ClassLoader 继承关系:

java.lang.ClassLoader

└── dalvik.system.BaseDexClassLoader (API 26+)

├── dalvik.system.PathClassLoader
│ - 用于加载已安装 APK 中的类
│ - parent 是 BootClassLoader

└── dalvik.system.DexClassLoader
- 用于加载任意路径的 DEX/JAR/APK
- 可指定 parent
- 插件化的核心

Android 的 ClassLoader 体系中,DexClassLoader 可以加载指定路径的 DEX/JAR/APK 文件:

// java.lang.ClassLoader (AOSP libcore)
DexClassLoader pluginClassLoader = new DexClassLoader(
pluginApkPath, // 插件 APK 的路径(外存或 data/data 目录)
optimizedDirectory, // dex2oat 的输出目录
null, // native lib 目录(插件化可设为独立路径)
hostClassLoader // parent 设为宿主 ClassLoader
);

2.2 Android 类加载流程

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
private Element[] dexElements;

public Class<?> findClass(String name, List<Throwable> suppressed) {
// 遍历 dexElements 数组,返回第一个匹配的 Class
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
return null;
}

static class Element {
private final DexFile dexFile; // 已加载的 DEX 文件
private final File path;

public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null
? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
}

// libcore/libart/src/main/java/java/lang/ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve) {
// 1. 先检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
// 2. 委托给 parent ClassLoader
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name); // BootClassLoader
}
// 3. parent 没找到,自己查找
if (c == null) {
c = findClass(name);
}
}
return c;
}

2.3 插件化 ClassLoader 的层级设计

关键设计——parent 设置为宿主 ClassLoader

             BootClassLoader (Framework 类)

Host PathClassLoader (宿主 APK)
↑ ↑
Plugin DexClassLoader Plugin DexClassLoader
(插件A) (插件B)

这种设计有两个效果:

  1. 插件可以访问宿主的类和 Framework 的类(因为插件 ClassLoader 的 parent 是宿主 ClassLoader)。
  2. 宿主的 ClassLoader 找不到插件的类(宿主 ClassLoader 中不包含插件 DEX 的路径),这意味着宿主和插件之间的代码必须通过反射或接口通信。

如果需要宿主调用插件的类,可以在宿主 ClassLoader 的 DexPathList 中插入插件的 DEX 路径(通过反射修改 dexElements),但这会打破隔离性。

2.4 DexElements 合并技术(热修复也用到的核心技术)

public static void injectPluginDex(ClassLoader hostClassLoader,
ClassLoader pluginClassLoader) {
try {
// 1. 获取宿主的 DexPathList 和 dexElements
Field hostPathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
hostPathListField.setAccessible(true);
Object hostPathList = hostPathListField.get(hostClassLoader);

Field hostElementsField = DexPathList.class.getDeclaredField("dexElements");
hostElementsField.setAccessible(true);
Object[] hostElements = (Object[]) hostElementsField.get(hostPathList);

// 2. 获取插件的 dexElements
Field pluginPathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
pluginPathListField.setAccessible(true);
Object pluginPathList = pluginPathListField.get(pluginClassLoader);

Field pluginElementsField = DexPathList.class.getDeclaredField("dexElements");
pluginElementsField.setAccessible(true);
Object[] pluginElements = (Object[]) pluginElementsField.get(pluginPathList);

// 3. 创建合并后的 elements(插件在前,宿主在后)
Object[] mergedElements = (Object[]) Array.newInstance(
hostElements.getClass().getComponentType(),
pluginElements.length + hostElements.length
);
System.arraycopy(pluginElements, 0, mergedElements, 0, pluginElements.length);
System.arraycopy(hostElements, 0, mergedElements, pluginElements.length, hostElements.length);

// 4. 替换 dexElements
hostElementsField.set(hostPathList, mergedElements);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

2.5 类的隔离与共享

实际生产环境中,插件化框架通常支持两种类加载策略:

// 共享类加载器(Shared ClassLoader)
// 放在宿主 APK 中,所有插件共享(如公共库、协议类)
// 通过设置 parent 或 sharedLibrary 实现

// 隔离类加载器(Isolated ClassLoader)
// 每个插件独立加载,避免类冲突
// 每个插件有自己的 DexClassLoader,不同的 parent 链

三、资源的加载:AssetManager 与 addAssetPath

Android 的资源框架基于 ResourcesAssetManager。每个 APK 有自己的 resources.arsc 文件(资源索引表)和 res 目录。

3.1 Resources 加载链路

Resources


AssetManager (Java)
│ JNI

AssetManager (C++) —— android_content_res_AssetManager.cpp

├── resources.arsc 解析
├── ResTable —— resourceId -> value 的查找表
└── AssetPath 列表 —— 所有已加载的 APK 的资源路径

3.2 创建插件 Resources

要让宿主访问插件的资源,需要创建独立的 Resources 对象:

// 创建插件的 Resources 对象
public Resources createPluginResources(String pluginApkPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
// addAssetPath 是隐藏 API(@hide),通过反射调用
Method addAssetPath = AssetManager.class.getDeclaredMethod(
"addAssetPath", String.class);
addAssetPath.invoke(assetManager, pluginApkPath);

Resources hostResources = context.getResources();
return new Resources(
assetManager, // 插件的 AssetManager
hostResources.getDisplayMetrics(), // 屏幕信息
hostResources.getConfiguration() // 配置信息
);
} catch (Exception e) {
throw new RuntimeException("Failed to create plugin resources", e);
}
}

关键点

  • addAssetPath 调用后,AssetManager 会将插件 APK 的资源索引(resources.arsc)纳入管理。
  • 新的 Resources 对象使用插件的 AssetManager,可以访问插件的资源。
  • 但宿主的 Resources 对象仍然只能访问宿主的资源——这是资源和类的双重隔离。

3.3 资源 ID 冲突解决方案

资源 ID 冲突问题:插件的资源 ID 可能与宿主冲突。编译时修改插件的 aapt 参数,给插件的资源 ID 设置一个不同的 package ID:

// AAPT 编译插件时指定 --package-id 0x7f -> 0x7e(区别于宿主的 0x7f)
aapt package --package-id 0x7e --extra-packages com.plugin.lib ...

资源 ID 的结构:

0xPP TT EEEE
│ │ └── Entry ID (16bit): 资源在类型中的序号
│ └────── Type ID (8bit): 资源类型 (layout=0x03, string=0x01, drawable=0x02)
└────────── Package ID (8bit): APK 包标识 (系统=0x01, 应用=0x7f, 插件=0x7e)

3.4 Android 8.0+ 的资源加载变化

Android 8.0(API 26)后,ResourcesAssetManager 的实现发生了变化,一些反射方法不再可用。Google 在 API 30 正式暴露了 ResourcesProviderloadFromPath API,提供了官方的资源动态加载支持:

// API 30+ 官方支持的资源加载方式
ResourcesProvider provider = ResourcesProvider.loadFromPath(pluginApkPath);
Resources pluginResources = new Resources(provider);

四、Activity 的插件化:代理模式 vs Hook 模式

Activity 必须在 AndroidManifest.xml 中注册,这是插件化最大的难点。业界有两种主流方案:

4.1 代理模式(Proxy Activity Pattern)

在宿主 Manifest 中预注册一个占位 Activity,运行时由它代理真正的插件 Activity。

宿主 Manifest 预注册

<activity android:name=".plugin.ProxyActivity"
android:launchMode="standard"
android:configChanges="orientation|screenSize" />

代理 Activity 的生命周期转发

// 运行时:宿主 ProxyActivity 持有插件 Activity 的引用
public class ProxyActivity extends Activity {
private PluginActivity mPluginActivity; // 通过反射创建

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String pluginActivityClassName = getIntent().getStringExtra("plugin_class");
// 用插件的 ClassLoader 加载插件 Activity
Class<?> pluginClass = PluginManager.getInstance()
.getPluginClassLoader().loadClass(pluginActivityClassName);
// 反射创建实例
mPluginActivity = (PluginActivity) pluginClass.newInstance();
// 设置宿主环境
mPluginActivity.setProxy(this);
// 调用插件的生命周期
mPluginActivity.onCreate(savedInstanceState);
}

@Override
protected void onResume() {
super.onResume();
mPluginActivity.onResume();
}

// ... 所有生命周期方法都按相同模式转发
}

插件 Activity 的实现

public class PluginActivity {
private Activity mProxyActivity;
private Resources mPluginResources;

public void setProxy(Activity proxy) {
this.mProxyActivity = proxy;
}

// 设置布局:使用插件的 Resources 加载布局
protected void setContentView(int layoutResId) {
// 通过插件的 Resources 加载布局
XmlResourceParser parser = mPluginResources.getLayout(layoutResId);
View view = LayoutInflater.from(mProxyActivity)
.inflate(parser, null);
mProxyActivity.setContentView(view);
}

// 获取字符串:使用插件的 Resources
protected String getString(int resId) {
return mPluginResources.getString(resId);
}

// 启动其他插件 Activity:发送 Intent 给 ProxyActivity
protected void startPluginActivity(String className) {
Intent intent = new Intent(mProxyActivity, ProxyActivity.class);
intent.putExtra("plugin_class", className);
mProxyActivity.startActivity(intent);
}
}

4.2 Hook 模式(AMS Hook)

Hook 模式的思路更底层:拦截系统调用,在启动 Activity 的流程中偷梁换柱。

Activity 启动流程中的 Hook 点

App进程                               System Server 进程
│ │
│ startActivity(intent) │
│ │ │
│ ▼ │
│ Activity.startActivityForResult() │
│ │ │
│ ▼ │
│ Instrumentation.execStartActivity() │
│ ← Hook点1: 替换 Intent 中的目标 Activity 为占坑 Activity
│ │ │
│ ├── Binder ──────────────────►│
│ │ │
│ │ AMS.startActivity()
│ │ 验证 Manifest (通过! 因为是占坑Activity)
│ │ 创建进程/安排任务
│ │ │
│ │◄────────────────────────────┤
│ │ │
│ ▼ │
│ ApplicationThread.scheduleLaunchActivity()
│ ← Hook点2: 将占坑 Activity 替换回真正的插件 Activity
│ │
│ ▼
│ ActivityThread.performLaunchActivity()
│ 创建 Activity 实例 (插件 Activity)
│ 调用 attach, onCreate 等生命周期

Hook 点1 实现(Hook Instrumentation)

public class PluginInstrumentation extends Instrumentation {
private Instrumentation mBase;

@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread,
IBinder token, Activity target, Intent intent, int requestCode,
Bundle options) {
// 将目标 Activity 替换为占坑 Activity
String pluginActivity = intent.getComponent().getClassName();
intent.setClassName(who, "com.host.plugin.ProxyActivity");
intent.putExtra("real_activity", pluginActivity);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}
}

// 替换系统的 Instrumentation
public static void hookInstrumentation() {
ActivityThread activityThread = ActivityThread.currentActivityThread();
Field instrumentationField = ActivityThread.class.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation instrumentation = (Instrumentation) instrumentationField.get(activityThread);
PluginInstrumentation pluginInstrumentation = new PluginInstrumentation();
pluginInstrumentation.setBase(instrumentation);
instrumentationField.set(activityThread, pluginInstrumentation);
}

Hook 点2 实现(Hook H.Callback)

// 替换 ActivityThread.mH.mCallback
// mH 是 ActivityThread 的主线程 Handler
public static void hookHCallback() {
ActivityThread activityThread = ActivityThread.currentActivityThread();
Field hField = ActivityThread.class.getDeclaredField("mH");
hField.setAccessible(true);
Handler mH = (Handler) hField.get(activityThread);

Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
callbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
// 从 msg.obj 中取出 ActivityClientRecord
// 将 intent 中的 ComponentName 替换回真正的插件 Activity
Object r = msg.obj;
Field intentField = r.getClass().getDeclaredField("intent");
Intent intent = (Intent) intentField.get(r);
String realActivity = intent.getStringExtra("real_activity");
intent.setClassName(who, realActivity);
// 继续原有的消息处理流程
}
return false; // 返回 false 让 mH 继续处理
}
});
}

4.3 代理模式 vs Hook 模式对比

维度 代理模式 Hook 模式
复杂度 低(转发生命周期即可) 高(需要理解系统内部流程)
兼容性 较好(不依赖 @hide API) 差(每个 Android 版本可能需要调整 Hook 点)
插件 Activity 限制 不继承 android.app.Activity 可以继承真正的 Activity
系统特性支持 有限(theme, launchMode 等需手动处理) 完整(因为系统看到的仍是”合规的”Activity)
维护成本
代表框架 RePlugin, VirtualAPK DroidPlugin

五、Service 和 BroadcastReceiver 的插件化

5.1 Service

Service 的插件化相对简单,因为 Service 可以在运行时动态注册:

// 代理 Service 模式
public class ProxyService extends Service {
private PluginService mPluginService;

@Override
public void onCreate() {
super.onCreate();
String pluginClass = getIntent().getStringExtra("plugin_class");
mPluginService = (PluginService) PluginManager.getInstance()
.getPluginClassLoader().loadClass(pluginClass).newInstance();
mPluginService.attach(this);
mPluginService.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return mPluginService.onStartCommand(intent, flags, startId);
}
}

5.2 BroadcastReceiver

BroadcastReceiver 可以完全通过 registerReceiver() 动态注册,无需在 Manifest 中声明(除非是需要静态注册的系统广播)。

// 动态注册插件中的 Receiver
Class<?> receiverClass = pluginClassLoader.loadClass(receiverClassName);
BroadcastReceiver receiver = (BroadcastReceiver) receiverClass.newInstance();
IntentFilter filter = new IntentFilter(action);
registerReceiver(receiver, filter);

5.3 ContentProvider

ContentProvider 是最难插件化的组件,因为它必须声明在 Manifest 中,且初始化时机非常早(在 Application.attachBaseContext 之前)。主流的做法是在宿主 Manifest 中预注册一个代理 ContentProvider,通过 URI 参数来路由到不同的插件 Provider。

六、主流插件化框架深度对比

6.1 RePlugin(360 开源)

RePlugin 是 360 团队开源的插件化框架(https://github.com/Qihoo360/RePlugin),核心特色是坑位(Pit)机制——在宿主 Manifest 中预注册一系列占位 Activity,每个坑位预设了不同的 launchMode 和主题。插件 Activity 运行时被分配到对应的坑位。

RePlugin 架构:

┌──────────────────────────────────────┐
│ RePlugin │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Plugin │ │ Plugin Manager │ │
│ │ Loader │ │ (安装/卸载/升级) │ │
│ └──────────┘ └──────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ Pit Manager │ │
│ │ (坑位分配与管理) │ │
│ │ - 根据 launchMode 匹配坑位 │ │
│ │ - 管理坑位的使用状态 │ │
│ └────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ IPC Binder Bridge │ │
│ │ (宿主 ↔ 插件进程通信) │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘

坑位分配逻辑:

// RePlugin 的坑位分配
// 根据插件的 launchMode、theme、configChanges 等属性匹配最合适的坑位
String pitAlias = PluginContainers.alloc(containerType,
pluginLaunchMode, pluginTheme, pluginConfigChanges);
// 启动匹配的坑位 Activity,同时传递真实的插件 Activity 类名

RePlugin 的坑位预设示例(在宿主 Manifest 中):

<!-- 不同 launchMode 的坑位 -->
<activity android:name=".pit.PitStandard1" android:launchMode="standard" />
<activity android:name=".pit.PitStandard2" android:launchMode="standard" />
<activity android:name=".pit.PitSingleTop1" android:launchMode="singleTop" />
<activity android:name=".pit.PitSingleTask1" android:launchMode="singleTask" />
<activity android:name=".pit.PitSingleInstance1" android:launchMode="singleInstance" />

<!-- 透明主题的坑位 -->
<activity android:name=".pit.PitTransparent1"
android:theme="@android:style/Theme.Translucent" />

6.2 VirtualApp(商业级沙箱)

VirtualApp(https://github.com/asLody/VirtualApp)采用更高阶的思路——完全在用户态模拟 Android Framework 的行为。它 Hook 了大量系统服务(ActivityManagerService、PackageManagerService 等),在单进程中运行多个”虚拟应用”。这是一个重量级方案,主要用于应用多开。

核心 Hook 点:

VirtualApp Hook 的系统服务:
├── ActivityManagerService
│ ├── startActivity
│ ├── startService
│ └── bindService
├── PackageManagerService
│ ├── getPackageInfo
│ ├── queryIntentActivities
│ └── resolveIntent
├── WindowManagerService
│ └── addWindow
├── NotificationManagerService
│ └── enqueueNotificationWithTag
├── AlarmManagerService
│ └── set
└── ContentService
└── notifyChange

VirtualApp 的运行模型:

Host Process (宿主应用)

└── VirtualApp Core (虚拟引擎)

├── Virtual App 1 (运行在独立进程中)
│ ├── AMS Hook
│ ├── PMS Hook
│ └── Virtual Context

├── Virtual App 2
│ └── ...

└── Virtual App 3
└── ...

6.3 Shadow(腾讯开源)

腾讯的 Shadow(https://github.com/Tencent/Shadow)是较新的插件化框架,特点是将框架本身也作为插件加载,避免插件框架代码与宿主代码的耦合。其核心是”零反射”设计——使用 Transform API 在编译期注入代码。

Shadow 的核心理念:

传统插件化:
Host ──────► Plugin (通过反射/接口)

Shadow:
Host ──► Loader Plugin ──► Business Plugin
(框架代码) (业务代码)

Shadow 的关键技术:

  1. LoadParameters:通过 compileOnly 依赖接口,编译期确定接口签名,运行时无需反射。
  2. PluginManager 动态化:框架代码作为插件的一部分分发,宿主只需极小量的接入代码(约 100 行)。
  3. Transform API:编译期修改字节码,将插件中对 Context、Resources 的引用替换为 Shadow 提供的代理。

6.4 各框架对比总结

框架 开发者 核心方案 复杂度 兼容性 适用场景
DroidPlugin 360 Hook AMS + 代理 学习参考
RePlugin 360 坑位 + 代理 大型 App 插件化
VirtualAPK 滴滴 多方案融合 中小型 App
Atlas 阿里 组件化 + 动态部署 淘宝级应用
VirtualApp Lody 虚拟 Framework 极高 应用多开
Shadow 腾讯 零反射 + Transform 新一代插件化

七、Android App Bundle 与插件化的关系

Google 于 2018 年推出 Android App Bundle(AAB),随后推出了 Play Feature Delivery 和 Dynamic Asset Delivery:

  • AAB:将 APP 拆分为 base + configuration splits + dynamic feature modules。
  • Dynamic Delivery:按需下载功能模块(dynamic feature),用户首次安装时不下载,需要时才拉取。
  • In-App Update API:Google Play 提供的应用内更新机制。

Google 的 Dynamic Delivery 在某种程度上替代了插件化的需求——它允许应用在不重新安装的情况下获取新功能。但这与插件化的哲学不同:

  1. 动态功能模块仍需通过 Google Play 审核和签名,更新周期受平台控制。插件化可以完全自主控制更新节奏。
  2. 动态模块必须经过 Google Play Console,不能从自有服务器下发。中国市场的应用无法使用。
  3. 动态模块之间的隔离是由系统保证的(不同 ClassLoader),比插件化更安全。

八、面试常问题目

Q1: 插件化框架如何解决 Activity 的 Manifest 注册问题?

两种主流方案:(1) 代理模式——在宿主 Manifest 中预注册一个或多个占位 Activity,运行时通过该 Activity 转发所有生命周期方法给插件 Activity,插件 Activity 本身不继承 android.app.Activity,而是继承框架提供的 PluginActivity 基类。(2) Hook 模式——Hook AMS(ActivityManagerService)的 startActivity 方法,在调用系统 process 之前将目标 Activity 替换为预注册的占位 Activity,系统创建 Activity 实例后,再 Hook ActivityThread 的 mH(Handler),将占位 Activity 替换回真实 Activity。

Q2: addAssetPath 为什么可以加载插件的资源?

Android 的资源加载链路是 Resources -> AssetManager -> resources.arsc + res 目录。AssetManager 是 C++ 层的对象,通过 JNI 与 Java 层通信。addAssetPath 方法将新的 APK 路径注册到 AssetManager 的 AssetPath 列表中,使得后续的资源查询(getString、getDrawable 等)可以找到插件 APK 中的资源。每个 APK 的资源由 packageId(即资源 ID 的高 8 位,通常是 0x7f)区分,因此需要给不同插件分配不同的 packageId。

Q3: 插件化框架为什么需要进程隔离?

插件代码是不可信任的(可能来自第三方或包含未知 Bug)。如果插件和宿主在一个进程中运行,插件的内存错误(如 OOM、死循环)会直接影响宿主的稳定性。更严重的是,插件可能在同一个进程中调用 System.exit() 或 Runtime.getRuntime().halt(),导致整个应用退出。多进程隔离(将插件运行在独立进程)可有效防止这些风险。

Q4: Google 为什么”放弃”了插件化?

Google 并未直接放弃,而是通过 AAB + Dynamic Delivery 提供了替代方案。核心原因:(1) 安全——绕过 Google Play 的代码审查机制,可能引入恶意代码;(2) 兼容性——Android 每个版本都对内部 API 有所改动,Hook 方案极易在新系统上失效;(3) 碎片化——自定义 ClassLoader 和资源的 Hack 方案在不同 ROM 上的表现不一致。Google 倾向于通过平台能力(而非开发者 Hack)来解决需求。

Q5: 插件化中如何处理插件和宿主的 Context 差异?

插件中的 Context 不能直接使用宿主的 Context,因为:

  1. 宿主 Context 无法访问插件的资源(AssetManager 中没有插件的 resource path)。
  2. 宿主 Context 的包名、应用信息与插件不同。
  3. 宿主 Context 的 ClassLoader 可能加载不到插件的类。

解决方案是创建插件的专用 Context:

public Context createPluginContext(Context hostContext, String pluginApkPath,
ClassLoader pluginClassLoader) {
// 1. 创建插件专用的 AssetManager
AssetManager am = AssetManager.class.newInstance();
am.getClass().getMethod("addAssetPath", String.class).invoke(am, pluginApkPath);

// 2. 创建插件专用的 Resources
Resources pluginResources = new Resources(am,
hostContext.getResources().getDisplayMetrics(),
hostContext.getResources().getConfiguration());

// 3. 创建插件 Context(使用 ContextImpl 的隐藏 API)
Context pluginContext = new ContextWrapper(hostContext) {
@Override
public Resources getResources() {
return pluginResources;
}
@Override
public AssetManager getAssets() {
return am;
}
@Override
public ClassLoader getClassLoader() {
return pluginClassLoader;
}
@Override
public String getPackageName() {
return pluginPackageName;
}
};

return pluginContext;
}

这个插件 Context 在所有插件代码中使用(通过全局替换),确保插件所有资源访问和类加载都正确路由。

核心参考源码路径:

  • DexClassLoader:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
  • BaseDexClassLoader:libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
  • DexPathList:libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
  • ClassLoader:libcore/libart/src/main/java/java/lang/ClassLoader.java
  • AssetManager:frameworks/base/core/java/android/content/res/AssetManager.java
  • Resources:frameworks/base/core/java/android/content/res/Resources.java
  • ActivityThread:frameworks/base/core/java/android/app/ActivityThread.java
  • Instrumentation:frameworks/base/core/java/android/app/Instrumentation.java
  • AMS:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  • RePlugin:https://github.com/Qihoo360/RePlugin
  • VirtualApp:https://github.com/asLody/VirtualApp
  • Shadow:https://github.com/Tencent/Shadow
  • Android App Bundle:https://developer.android.com/guide/app-bundle
打赏
  • 微信
  • 支付宝

评论