前言
在 Android 开发中,动态化一直是一个热门话题。腾讯开源的 Shadow 框架以其“无侵入”、“无需 Root”、“兼容性好”著称。很多人觉得 Shadow 源码复杂难懂,但其实其核心思想非常纯粹:把插件 Activity 伪装成一个普通类,通过宿主容器代理转发生命周期。
本文将剥离复杂的编译期字节码修改(AOP),手动实现一个简化版的 ShadowLite。我们将通过三个模块(接口层、插件层、宿主层),亲手还原插件化的三大核心难题:类加载、资源加载、生命周期分发,并深度解析其中四个最关键的“魔法”方法。
核心结论提前看:
Shadow 的核心 trick 在于:插件里的 Activity 其实不是真正的 Activity,它只是一个继承了普通类的 POJO。 真正注册在 Manifest 里的是宿主的“壳子 Activity”(Container),由壳子持有插件对象,手动调用其生命周期方法。
简单的版本shadow主要是解决:类加载,资源加载,Activity的生命周期问题!
一、核心设计思路
要实现一个简单的插件化框架,我们需要解决三个问题:
- 类加载 (ClassLoader) :如何加载未安装在系统中的 APK 中的类? ->
DexClassLoader - 资源加载 (Resources) :如何加载插件 APK 中的图片和布局? -> 反射构建
AssetManager - 生命周期 (Lifecycle) :插件 Activity 未在 Manifest 注册,系统无法回调其生命周期怎么办? -> 代理模式 + 接口回调
ShadowLite 的核心思路就是将这三点分离并重新组合。我们将插件 Activity 降级为一个实现了特定生命周期接口的普通对象。宿主的容器 Activity 负责提供真实的 Context、Resources 和 ClassLoader,并将系统回调的事件(如 onCreate)转发给插件对象。
架构设计
我们将工程拆分为三个 Module:
module_interface:公共接口库。定义生命周期回调接口,宿主和插件都依赖它。module_plugin:插件工程。包含具体的业务 Activity,但它们不继承android.app.Activity,而是继承我们定义的普通基类。shadow_lite(宿主) :宿主工程。包含PluginContainerActivity(壳子)和####PluginManagerImpl``(加载器)。
下图展示了 ShadowLite 的模块依赖与核心交互流程:
二、手机 ShadowLite 实战编码
都是根据这个架构图写的:
2.1 定义生命周期接口 (module_interface)
这是宿主与插件通信的桥梁。插件 Activity 实现此接口,宿主容器通过此接口调用插件的方法。
public interface LifecyclerInterface {
// 绑定宿主上下文(关键:让插件获取到宿主的 Context)
void attach_Inner(Activity hostActivity);
// 模拟生命周期回调
void onCreate_Inner(Bundle savedInstanceState);
void onStart_Inner();
void onResume_Inner();
void onPause_Inner();
void onStop_Inner();
void onDestroy_Inner();
// 常用能力代理
LayoutInflater getLayoutInflater_Inner();
}
2.2 编写插件基类与业务类 (module_plugin)
这是 Shadow 思想的精髓。插件中的 Activity 不需要在 Manifest 注册,因此它不能直接继承系统的 Activity(否则安装时会报错或无法启动)。我们让它继承一个普通类 ShadowActivity。
2.2.1 插件基类 ShadowActivity
这个类实现了 LifecyclerInterface,内部持有一个真正的 Activity 对象(即宿主传进来的容器),将所有依赖 Context 的操作委托给它。
public class ShadowActivity implements LifecyclerInterface {
// 宿主容器 Activity,插件的所有 Context 相关操作都委托给它
protected Activity hostActivity;
@Override
public void attach_Inner(Activity activity) {
this.hostActivity = activity;
}
// --- 生命周期空实现,供子类重写 ---
@Override
public void onCreate_Inner(Bundle save) {}
@Override
public void onStart_Inner() {}
@Override
public void onResume_Inner() {}
@Override
public void onPause_Inner() {}
@Override
public void onStop_Inner() {}
@Override
public void onDestroy_Inner() {}
// --- 上下文能力代理 ---
@Override
public LayoutInflater getLayoutInflater_Inner() {
if (hostActivity != null) {
return hostActivity.getLayoutInflater();
}
return null;
}
// 代理其他常用方法,确保插件代码以为自己在 Activity 环境中运行
public ApplicationInfo getApplicationInfo() {
return hostActivity.getApplicationInfo();
}
public Resources getResources() {
// 注意:这里应该返回插件的 Resources,但在简单版中,
// 我们通过重写宿主容器的 getResources 来间接实现,或者在此处做特殊处理
return hostActivity.getResources();
}
public Window getWindow() {
return hostActivity.getWindow();
}
public void setContentView(int layoutResID) {
if (hostActivity != null) {
hostActivity.setContentView(layoutResID);
}
}
}
2.2.2 插件业务类 PluginActivity
注意:它继承的是 ShadowActivity (普通类),而不是 android.app.Activity。
public class PluginActivity extends ShadowActivity {
@Override
public void onCreate_Inner(Bundle savedInstanceState) {
super.onCreate_Inner(savedInstanceState);
// 这里的 R.layout 是插件工程的 R,setContentView 委托给了宿主
// 关键点:宿主容器的 getResources 必须被替换为插件的 Resources,否则找不到插件的 layout
setContentView(R.layout.activity_main);
}
@Override
public void onStart_Inner() {
// 业务逻辑
}
}
2.3 宿主核心实现 (shadow_lite)
宿主负责“脏活累活”:加载 APK、创建 ClassLoader、反射创建 AssetManager、以及充当容器。
2.3.1 插件管理器 PluginManagerImpl
负责加载插件 APK 并初始化 DexClassLoader 和 Resources。
public class PluginManagerImpl {
private Resources pluginResources;
private DexClassLoader pluginClassLoader;
private Context hostContext;
private static class Holder {
private static final PluginManagerImpl INSTANCE = new PluginManagerImpl();
}
public static PluginManagerImpl getInstance() {
return Holder.INSTANCE;
}
public void setContext(Context context) {
this.hostContext = context;
}
public Resources getPluginResources() {
return pluginResources;
}
public DexClassLoader getPluginClassLoader() {
return pluginClassLoader;
}
/**
* 加载插件 APK
* @param apkPath 插件 APK 的绝对路径
*/
public void loadPlugin(String apkPath) {
if (hostContext == null) return;
// 1. 初始化 DexClassLoader
File dexOutputDir = hostContext.getDir("dex", Context.MODE_PRIVATE);
pluginClassLoader = new DexClassLoader(
apkPath,
dexOutputDir.getAbsolutePath(),
null,
hostContext.getClassLoader()
);
// 2. 初始化 Resources (核心难点:反射创建 AssetManager)
try {
AssetManager assetManager = createAssetManager(apkPath);
pluginResources = new Resources(
assetManager,
hostContext.getResources().getDisplayMetrics(),
hostContext.getResources().getConfiguration()
);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Load plugin resources failed", e);
}
}
/**
* 【核心魔法 1】反射创建指向特定 APK 的 AssetManager
*/
private AssetManager createAssetManager(String apkPath) throws Exception {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
int cookie = (int) addAssetPathMethod.invoke(assetManager, apkPath);
if (cookie == 0) {
throw new RuntimeException("Failed to add asset path: " + apkPath);
}
return assetManager;
}
}
2.3.2 宿主容器 PluginContainerActivity
这是真正注册在 AndroidManifest.xml 中的 Activity。它像一个“壳”,内部实例化插件类,并转发生命周期。
public class PluginContainerActivity extends Activity {
private static final String TARGET_CLASS_NAME = "com.example.plugin.PluginActivity";
private LifecyclerInterface pluginInstance;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
ClassLoader pluginLoader = PluginManagerImpl.getInstance().getPluginClassLoader();
if (pluginLoader == null) {
PluginManagerImpl.getInstance().loadPlugin("/sdcard/plugin.apk");
pluginLoader = PluginManagerImpl.getInstance().getPluginClassLoader();
}
Class<?> clazz = pluginLoader.loadClass(TARGET_CLASS_NAME);
Object obj = clazz.getDeclaredConstructor().newInstance();
if (obj instanceof LifecyclerInterface) {
pluginInstance = (LifecyclerInterface) obj;
pluginInstance.attach_Inner(this);
pluginInstance.onCreate_Inner(savedInstanceState);
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Load Plugin Failed: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
@Override
protected void onStart() {
super.onStart();
if (pluginInstance != null) pluginInstance.onStart_Inner();
}
@Override
protected void onResume() {
super.onResume();
if (pluginInstance != null) pluginInstance.onResume_Inner();
}
@Override
protected void onPause() {
super.onPause();
if (pluginInstance != null) pluginInstance.onPause_Inner();
}
@Override
protected void onStop() {
super.onStop();
if (pluginInstance != null) pluginInstance.onStop_Inner();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (pluginInstance != null) pluginInstance.onDestroy_Inner();
}
// ================= 【核心魔法 2 & 3】关键重写 =================
@Override
public ClassLoader getClassLoader() {
ClassLoader loader = PluginManagerImpl.getInstance().getPluginClassLoader();
return loader != null ? loader : super.getClassLoader();
}
@Override
public Resources getResources() {
Resources res = PluginManagerImpl.getInstance().getPluginResources();
return res != null ? res : super.getResources();
}
@Override
public LayoutInflater getLayoutInflater() {
Resources res = getResources();
LayoutInflater inflater = super.getLayoutInflater();
return inflater.cloneInContext(new ContextThemeWrapper(this, 0) {
@Override
public Resources getResources() {
return res;
}
});
}
}
为了更直观地理解整个调用流程,下图展示了从加载插件到界面显示的关键时序:
sequenceDiagram
participant 宿主App
participant PluginManager
participant PluginContainerActivity
participant PluginActivity
participant 系统
宿主App->>PluginManager: loadPlugin("/sdcard/plugin.apk")
PluginManager-->>PluginManager: 创建 DexClassLoader
PluginManager-->>PluginManager: 反射创建 AssetManager<br/>并添加插件路径
PluginManager-->>PluginManager: 构建插件 Resources
PluginManager-->>宿主App: 加载完成
宿主App->>系统: startActivity(Intent 指向<br/>PluginContainerActivity)
系统->>PluginContainerActivity: onCreate()
PluginContainerActivity->>PluginManager: getPluginClassLoader()
PluginManager-->>PluginContainerActivity: 返回 DexClassLoader
PluginContainerActivity->>PluginManager: getPluginResources()
PluginManager-->>PluginContainerActivity: 返回插件 Resources
PluginContainerActivity->>PluginContainerActivity: 重写 getResources() 返回插件 Resources
PluginContainerActivity->>PluginContainerActivity: 重写 getClassLoader() 返回插件 ClassLoader
PluginContainerActivity->>PluginContainerActivity: 加载 PluginActivity 类并实例化
PluginContainerActivity->>PluginActivity: attach_Inner(this)
PluginActivity-->>PluginActivity: 保存 hostActivity 引用
PluginContainerActivity->>PluginActivity: onCreate_Inner(savedInstanceState)
PluginActivity->>PluginActivity: setContentView(R.layout.activity_main)
PluginActivity->>PluginActivity: 内部调用 hostActivity.setContentView()
PluginActivity->>PluginContainerActivity: 实际执行 setContentView()
PluginContainerActivity->>PluginContainerActivity: getLayoutInflater()
PluginContainerActivity->>PluginContainerActivity: 返回的 LayoutInflater 内部使用插件 Resources
PluginContainerActivity->>PluginContainerActivity: 加载插件布局文件
PluginContainerActivity-->>PluginActivity: 布局设置完成
PluginActivity-->>PluginContainerActivity: onCreate_Inner 返回
Note over PluginContainerActivity,系统: 后续系统生命周期回调<br/>继续转发给 PluginActivity
系统->>PluginContainerActivity: onStart()
PluginContainerActivity->>PluginActivity: onStart_Inner()
系统->>PluginContainerActivity: onResume()
PluginContainerActivity->>PluginActivity: onResume_Inner()
2.4 配置文件 AndroidManifest.xml
重点:只需要注册宿主的 PluginContainerActivity。插件中的 PluginActivity 绝对不要在这里注册。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.taoduoduo.shadow">
<application ... >
<!-- 宿主主入口 -->
<activity android:name=".TaoduoduoMainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 唯一的容器 Activity,用于承载所有插件页面 -->
<activity android:name=".PluginContainerActivity" />
</application>
</manifest>
三、四大核心方法深度解析
这四个方法是 ShadowLite 的“心脏”和“血管”。如果不理解它们的内部实现细节,插件化就只是一堆黑盒代码。
1. createAssetManager(String apkPath):资源的“任意门”
-
核心作用:Android 系统默认只加载宿主 APK 的资源。这个方法通过反射,强行创建一个新的
AssetManager实例,并将插件 APK 的路径“注入”进去,使其能够读取插件内部的图片、布局和字符串。 -
为什么需要反射:
AssetManager的构造函数是隐藏的,且addAssetPath方法被标记为@hide(非公开 API)。系统希望应用只管理自己的资源,但插件化必须打破这个限制。 -
内存模型:
- 宿主 AssetManager:
[ /data/app/host.apk ]-> 只能读宿主的R.layout.main - 插件 AssetManager (新建) :
[ /sdcard/plugin.apk ]-> 能读插件的R.layout.main - 关键点: 这两个 Manager 是独立的。如果我们用宿主的
Context去setContentView,它会用宿主的 Manager 去找布局,当然找不到插件的布局。所以必须替换 Context 中的 Resources。
- 宿主 AssetManager:
2. loadPlugin(String apkPath):代码的“搬运工”
-
核心作用:初始化双核引擎:DexClassLoader(负责加载类)和 Resources(负责加载资源)。它是插件初始化的入口。
-
双亲委派模型的“破坏” :
- 正常情况:
BootClassLoader->SystemClassLoader->PathClassLoader(宿主)。 - 插件化情况:当插件代码请求一个类时,
DexClassLoader先问父加载器(宿主)。宿主说“没有”,DexClassLoader转身去/sdcard/plugin.apk里找。 - 反之:如果插件代码引用了宿主的类(如
Activity或接口),父加载器(宿主)会说“有!”直接返回。这保证了接口的一致性,避免ClassCastException。
- 正常情况:
-
资源串联:在
loadPlugin中,我们用新建的AssetManager构建全新的Resources对象,并复用宿主的屏幕密度配置(Metrics/Configuration),让插件 UI 表现与宿主一致。
3. getResources():狸猫换太子
-
核心作用:这是最关键的一步重写。当插件代码调用
setContentView(R.layout.xxx)时,底层最终会调用Context.getResources()来获取资源。如果返回的是宿主的 Resources,插件布局必挂。 -
执行流程推演:
- 插件代码执行:
setContentView(R.layout.activity_plugin) - 调用链最终到达
LayoutInflater.inflate(...) LayoutInflater从context中获取Resources。- 拦截:因为
PluginContainerActivity重写了getResources(),此时返回的是pluginResources。 inflate方法拿着pluginResources(内部指向插件 APK) 去解析 XML,成功找到布局和圖片。
- 插件代码执行:
-
如果没有这一步:
inflate会拿着宿主的AssetManager去宿主 APK 里找activity_plugin.xml,结果当然是Resources$NotFoundException。
4. getClassLoader():类的“导航仪”
-
核心作用:确保在容器 Activity 内部(以及其衍生的子组件中)加载类时,优先去插件 APK 中寻找。
-
应用场景:
Fragment的状态恢复。- 序列化机制 (
Serializable/Parcelable) 反序列化时。 - 依赖注入框架 (如 ARouter, Dagger) 查找实现类时。
-
潜在坑点:必须保证父子加载器顺序正确。插件的
DexClassLoader父加载器必须是宿主的ClassLoader,这样才能共享接口类定义,避免类型转换异常。
四、实战操作步骤
-
构建工程:
- 创建
module_interface(Java Library)。 - 创建
module_plugin(Android Library),依赖 interface,编写PluginActivity。 - 创建
shadow_lite(Android Application),依赖 interface,编写PluginManagerImpl和PluginContainerActivity。
- 创建
-
打包插件:
- 单独编译
module_plugin所在的工程(或者将其作为独立 App 编译),生成plugin.apk。 - 注意:为了简化,插件工程也需要有自己的
Application和Manifest(虽然里面的 Activity 不注册,但需要声明包名和资源)。
- 单独编译
-
部署测试:
- 将生成的
plugin.apk推送到手机/sdcard/plugin.apk。 - 安装并运行宿主
shadow_lite。 - 点击按钮启动
PluginContainerActivity。 - 现象:界面显示了插件
PluginActivity中定义的布局和内容。
- 将生成的
五、总结与扩展
通过这个 ShadowLite,我们理解了插件化的骨架:
- ClassLoader 隔离:实现代码的热更新。
- Resources 隔离:实现资源的独立加载。
- 生命周期代理:绕过系统限制,实现未注册组件的运行。
真实 Shadow 框架做了什么扩展?
- AOP 字节码修改:手动写
ShadowActivity太麻烦且容易出错。真实的 Shadow 在编译期通过 Gradle 插件,自动将插件中所有的extends Activity修改为extends ShadowActivity,并将所有super.onCreate()调用修改为调用代理逻辑。这对开发者完全透明。 - 多进程支持:处理不同进程间的 ClassLoader 共享和资源同步。
- Intent 匹配:模拟系统的 Intent 解析机制,支持
startActivity(new Intent(this, PluginActivity.class))这种标准写法,而不是手动loadClass。
源码的地址: