最近看了下Shadow,别人写的博客,都是比较旧,几年前的,没基于最新的代码分析,于是我写了这篇文章分享下! 前面还有几篇Shadow实战的,太多,先跳过,后面再补上
引言
在 Android 开发中,插件化技术是实现应用模块化、动态更新与体积优化的重要手段。Shadow 是腾讯开源的一款高性能、零反射、零 Hook 的插件化框架,其核心设计目标是在不破坏 Android 系统机制的前提下,安全、稳定地运行插件代码。
本文将结合源码和官方文档,系统梳理 Shadow 启动插件 Activity 的完整流程,帮助开发者深入理解其底层原理。
一、Shadow 整体架构概览
1.1 整体架构分层设计
Shadow 的整体架构清晰分层,主要包含以下组件:
预注册代理Activity] A2[宿主Application] A3[入口Activity] end subgraph "管理器层 (Manager Layer)" B1[PluginManager
插件管理器] B2[UuidManager
插件实例管理] B3[ComponentManager
组件管理] end subgraph "加载器层 (Loader Layer)" C1[DynamicPluginLoader
动态加载器] C2[FixedPluginLoader
固定加载器] C3[PluginClassLoader
插件类加载器] end subgraph "运行时层 (Runtime Layer)" D1[ShadowActivity
代理基类] D2[PluginContainerActivity
容器Activity] D3[ShadowContext
上下文代理] end subgraph "插件层 (Plugin Layer)" E1[插件APK
业务模块] E2[插件Activity
业务逻辑] E3[插件资源
独立资源] end A3 --> B1 B1 --> C1 C1 --> D1 D1 --> D2 D2 --> E2 E2 --> E3 D3 -.-> C3 D3 -.-> E3
1.2 核心模块说明
| 模块 | 职责 |
|---|---|
| Host App | 集成 Shadow SDK,提供代理容器 Activity |
| PluginManager | 插件入口管理,负责路由与调度 |
| PluginLoader | 加载插件 APK,创建 ClassLoader 和 Resources |
| Runtime | 提供插件运行所需的基础能力(Context 代理、生命周期转发等) |
| Plugin APK | 独立业务模块,编译为标准 APK |
核心思想 :"宿主壳 + 插件实现" ------ 插件 Activity 实际由宿主中预注册的代理 Activity 承载。
二、Shadow启动Activity示例代码
typescript
public void startPlugin() {
PluginHelper.getInstance().singlePool.execute(new Runnable() {
@Override
public void run() {
HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);
Bundle bundle = new Bundle();
bundle.putString(TaoDuoduoConstant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());
bundle.putString(TaoDuoduoConstant.KEY_PLUGIN_PART_KEY, getIntent().getStringExtra(TaoDuoduoConstant.KEY_PLUGIN_PART_KEY));
bundle.putString(TaoDuoduoConstant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(TaoDuoduoConstant.KEY_ACTIVITY_CLASSNAME));
HostApplication.getApp().getPluginManager()
.enter(PluginLoadActivity.this, TaoDuoduoConstant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() {
@Override
public void onShowLoadingView(final View view) {
mHandler.post(new Runnable() {
@Override
public void run() {
mViewGroup.addView(view);
}
});
}
@Override
public void onCloseLoadingView() {
finish();
}
@Override
public void onEnterComplete() {
}
});
}
});
}
三、架构图与流程图解析
3.1 Shadow插件化整体架构图
3.2 启动插件Activity的核心流程图
包装插件信息 PluginManager->>系统AMS: 3.启动代理Activity
PluginDefaultProxyActivity 系统AMS->>代理Activity: 4.创建实例,调用onCreate() 代理Activity->>PluginLoader: 5.请求加载插件 PluginLoader->>插件APK: 6.创建插件ClassLoader PluginLoader->>插件APK: 7.加载插件资源 PluginLoader->>插件APK: 8.创建插件Activity实例 插件APK-->>PluginLoader: 9.返回插件Activity对象 PluginLoader-->>代理Activity: 10.返回插件Activity 代理Activity->>代理Activity: 11.创建ShadowContext 代理Activity->>代理Activity: 12.替换插件Activity的Context 代理Activity->>代理Activity: 13.建立生命周期同步 代理Activity->>插件Activity: 14.调用onCreate() 代理Activity->>插件Activity: 15.调用onStart() 代理Activity->>插件Activity: 16.调用onResume() 代理Activity-->>开发者: 17.显示插件界面
3.3 核心组件交互图
3.4 关键类关系图
四、关键组件位置与部署结构
4.1 具体代码位置证明
在 Shadow 源码项目中:
bash
shadow-sample/
├── host/ # 宿主模块
│ ├── src/main/
│ │ ├── AndroidManifest.xml # 包含代理Activity声明
│ │ └── java/com/tencent/shadow/sample/host/
│ │ └── SamplePluginManager.java # 管理代理Activity
│ └── build.gradle # 依赖 shadow-core-loader
│
├── plugin-manager/ # PluginManager 实现
│
└── core/
└── loader/ # 核心加载器模块
└── src/main/java/com/tencent/shadow/core/loader/delegates/
├── PluginDefaultProxyActivity.java # 代理Activity实现
├── PluginSingleTaskProxyActivity.java
└── PluginContainerActivity.java # 代理基类
4.2 实际部署时的位置
在 APK 文件结构中:
bash
宿主 APK (host.apk):
├── AndroidManifest.xml
├── classes.dex
│ └── com/tencent/shadow/core/loader/delegates/
│ └── PluginDefaultProxyActivity.class ✅ 代理Activity在这里
└── assets/plugins/
└── plugin.apk # 插件APK
插件 APK (plugin.apk):
├── AndroidManifest.xml (仅编译期使用,不安装)
├── classes.dex
│ └── com/example/plugin/
│ └── PluginActivity.class # 真正的业务Activity
└── res/ # 插件资源
五、启动流程详解(附关键源码)
5.1 第一步:PluginManager.enter() → 构造代理 Intent
启动插件 Activity 的典型调用方式如下:
java
PluginManager.getInstance().enter(
context,
"plugin-part-key", // 插件标识(partKey)
"com.example.PluginActivity", // 插件 Activity 全类名
null, // Bundle 参数
null // 回调
);
enter() 是启动流程的入口方法 ,但其背后涉及多层转发与代理机制。PluginManager 是一个接口,通常由 SamplePluginManager 实现。其内部会调用:
java
PendingIntent pendingIntent = mPluginLoader.getLaunchIntent(...);
context.startActivity(pendingIntent.getIntent());
关键点 : Shadow 不会直接启动插件中的 Activity ,而是构造一个指向宿主中预注册的占位 Activity (如 PluginDefaultProxyActivity)的 Intent,并将插件信息(类名、partKey 等)作为 extra 传入。
为什么需要占位 Activity? Android 系统要求所有 Activity 必须在
AndroidManifest.xml中声明,否则会抛出ActivityNotFoundException。Shadow 通过预注册一组通用代理 Activity 来绕过此限制。
5.2 第二步:宿主中启动代理 Activity(PluginDefaultProxyActivity)
宿主 Manifest 中预先声明了多个代理 Activity,用于支持不同 launchMode:
xml
<activity android:name="com.tencent.shadow.core.loader.delegates.PluginDefaultProxyActivity" />
<activity android:name="com.tencent.shadow.core.loader.delegates.PluginSingleTaskProxyActivity"
android:launchMode="singleTask" />
<!-- ... -->
PluginDefaultProxyActivity 是 Shadow 框架在宿主应用中预先注册的代理容器
以 PluginDefaultProxyActivity 为例,它继承自 PluginContainerActivity,而后者又继承自 ShadowActivity。
当系统启动该代理 Activity 时,会执行其 onCreate() 方法。
5.3 代理Activity的onCreate流程
当系统启动PluginDefaultProxyActivity后,其onCreate方法开始执行:
java
// PluginContainerActivity.java (基类)
@Override
final protected void onCreate(Bundle savedInstanceState) {
isBeforeOnCreate = false;
mHostTheme = null;//释放资源
boolean illegalIntent = isIllegalIntent(savedInstanceState);
if (illegalIntent) {
super.hostActivityDelegate = null;
hostActivityDelegate = null;
Log.e(TAG, "illegalIntent savedInstanceState==" + savedInstanceState + " getIntent().getExtras()==" + getIntent().getExtras());
}
if (hostActivityDelegate != null) {
hostActivityDelegate.onCreate(savedInstanceState);
} else {
//这里是进程被杀后重启后走到,当需要恢复fragment状态的时候,由于系统保留了TAG,会因为找不到fragment引起crash
super.onCreate(null);
Log.e(TAG, "onCreate: hostActivityDelegate==null finish activity");
finish();
System.exit(0);
}
}
5.4 ShadowActivityDelegate
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
val pluginInitBundle = savedInstanceState ?: mHostActivityDelegator.intent.extras!!
mCallingActivity = pluginInitBundle.getParcelable(CM_CALLING_ACTIVITY_KEY)
mBusinessName = pluginInitBundle.getString(CM_BUSINESS_NAME_KEY, "")
val partKey = pluginInitBundle.getString(CM_PART_KEY)!!
mPartKey = partKey
mDI.inject(this, partKey)
mDependenciesInjected = true
val bundleForPluginLoader = pluginInitBundle.getBundle(CM_LOADER_BUNDLE_KEY)!!
mBundleForPluginLoader = bundleForPluginLoader
bundleForPluginLoader.classLoader = this.javaClass.classLoader
val pluginActivityClassName = bundleForPluginLoader.getString(CM_CLASS_NAME_KEY)!!
val pluginActivityInfo: PluginManifest.ActivityInfo =
bundleForPluginLoader.getParcelable(CM_ACTIVITY_INFO_KEY)!!
mPluginActivityInfo = pluginActivityInfo
mCurrentConfiguration = Configuration(resources.configuration)
mPluginHandleConfigurationChange =
(pluginActivityInfo.configChanges
or ActivityInfo.CONFIG_SCREEN_SIZE//系统本身就会单独对待这个属性,不声明也不会重启Activity。
or ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE//系统本身就会单独对待这个属性,不声明也不会重启Activity。
or 0x20000000 //见ActivityInfo.CONFIG_WINDOW_CONFIGURATION 系统处理属性
)
if (savedInstanceState == null) {
mRawIntentExtraBundle = pluginInitBundle.getBundle(CM_EXTRAS_BUNDLE_KEY)
mHostActivityDelegator.intent.replaceExtras(mRawIntentExtraBundle)
}
mHostActivityDelegator.intent.setExtrasClassLoader(mPluginClassLoader)
try {
val pluginActivity = mAppComponentFactory.instantiateActivity(
mPluginClassLoader,
pluginActivityClassName,
mHostActivityDelegator.intent
)
initPluginActivity(pluginActivity, pluginActivityInfo)
super.pluginActivity = pluginActivity
if (mLogger.isDebugEnabled) {
mLogger.debug(
"{} mPluginHandleConfigurationChange=={}",
mPluginActivity.javaClass.canonicalName,
mPluginHandleConfigurationChange
)
}
//使PluginActivity替代ContainerActivity接收Window的Callback
mHostActivityDelegator.window.callback = pluginActivity
//设置插件AndroidManifest.xml 中注册的WindowSoftInputMode
mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.softInputMode)
//Activity.onCreate调用之前应该先收到onWindowAttributesChanged。
if (mCallOnWindowAttributesChanged) {
pluginActivity.onWindowAttributesChanged(
mBeforeOnCreateOnWindowAttributesChangedCalledParams
)
mBeforeOnCreateOnWindowAttributesChangedCalledParams = null
}
val pluginSavedInstanceState: Bundle? =
savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)
pluginSavedInstanceState?.classLoader = mPluginClassLoader
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
notifyPluginActivityPreCreated(pluginActivity, pluginSavedInstanceState)
}
pluginActivity.onCreate(pluginSavedInstanceState)
mPluginActivityCreated = true
} catch (e: Exception) {
throw RuntimeException(e)
}
}
5.5 插件Activity实例化与赋值流程
插件Activity (pluginActivity) 的赋值过程可以概括为:
- 获取类名:从启动参数中提取插件 Activity 的完整类名
- 加载类 :使用插件的
ClassLoader加载指定的类 - 创建实例 :通过反射调用
newInstance()创建对象 - 类型转换 :将实例转换为
ShadowActivity类型 - 初始化 :通过
initPluginActivity()设置上下文、资源等 - 赋值 :将初始化后的实例赋值给
super.pluginActivity
完整的赋值时序图:
第一步:通过 AppComponentFactory 创建实例
kotlin
// 1. 获取插件 Activity 的类名
val pluginActivityClassName = bundleForPluginLoader.getString(CM_CLASS_NAME_KEY)!!
// 2. 使用 AppComponentFactory 创建插件 Activity 实例
val pluginActivity = mAppComponentFactory.instantiateActivity(
mPluginClassLoader, // 插件 ClassLoader
pluginActivityClassName, // 插件 Activity 完整类名
mHostActivityDelegator.intent // Intent 参数
)
第二步:调用 initPluginActivity() 进行初始化
kotlin
// 3. 初始化插件 Activity
initPluginActivity(pluginActivity, pluginActivityInfo)
initPluginActivity() 方法会:
- 设置插件 Activity 的 Context(ShadowContext)
- 设置插件 Application
- 设置 Window 和 WindowManager
- 设置主题等
第三步:赋值给父类属性
kotlin
// 4. 赋值给父类的 pluginActivity 字段
super.pluginActivity = pluginActivity
这里 super 指的是 PluginContainerActivity 或类似基类。
AppComponentFactory 的来源
kotlin
// 1. 从插件 Manifest 中获取 AppComponentFactory 配置
val appComponentFactory = pluginManifest.appComponentFactory
// 2. 创建 AppComponentFactory 实例
val clazz = pluginClassLoader.loadClass(appComponentFactory)
ShadowAppComponentFactory::class.java.cast(clazz.newInstance())
ShadowAppComponentFactory.instantiateActivity 方法
java
public class ShadowAppComponentFactory {
public ShadowActivity instantiateActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// 使用插件 ClassLoader 加载类并创建实例
return (ShadowActivity) cl.loadClass(className).newInstance();
}
}
插件 Activity 的类型转换
注意实例化后的类型转换:
java
// ShadowAppComponentFactory 返回的是 ShadowActivity 类型
return (ShadowActivity) cl.loadClass(className).newInstance();
// 但插件中的 Activity 实际上继承自 ShadowActivity(编译期插桩修改)
// 编译前的插件 Activity:
public class PluginMainActivity extends Activity { ... }
// 编译后(经过 Shadow Transform 插桩):
public class PluginMainActivity extends ShadowActivity { ... }
关键类的继承关系
java
// 宿主中的代理 Activity
public class PluginDefaultProxyActivity
extends PluginContainerActivity
implements ComponentManager
// 插件中的 Activity(编译后)
public class PluginMainActivity
extends ShadowActivity // 编译期插桩修改的父类
// ShadowActivity 是框架提供的基类
public abstract class ShadowActivity extends Activity {
// 提供插件化所需的各种代理方法
}
5.6 生命周期同步(手动转发)
由于插件 Activity 不是系统管理的组件,其生命周期需由宿主代理 Activity 手动转发:
java
// ShadowActivity.java
@Override
protected void onResume() {
super.onResume();
if (mPluginActivity != null) {
mPluginActivity.onResume(); // 手动调用
}
}
@Override
protected void onPause() {
if (mPluginActivity != null) {
mPluginActivity.onPause();
}
super.onPause();
}
public class ShadowActivity extends PluginActivity {
@Override
public void setContentView(int layoutResID) {
if ("merge".equals(XmlPullParserUtil.getLayoutStartTagName(getResources(), layoutResID))) {
//如果传进来的xml文件的根tag是merge时,需要特殊处理
View decorView = hostActivityDelegator.getWindow().getDecorView();
ViewGroup viewGroup = decorView.findViewById(android.R.id.content);
LayoutInflater.from(this).inflate(layoutResID, viewGroup);
} else {
View inflate = LayoutInflater.from(this).inflate(layoutResID, null);
hostActivityDelegator.setContentView(inflate);
}
}
@Override
public final ShadowApplication getApplication() {
return mPluginApplication;
}
@Override
public final ShadowActivity getParent() {
return null;
}
@Override
public void overridePendingTransition(int enterAnim, int exitAnim) {
//如果使用的资源不是系统资源,我们无法支持这个特性。
if ((enterAnim & 0xFF000000) != 0x01000000) {
enterAnim = 0;
}
if ((exitAnim & 0xFF000000) != 0x01000000) {
exitAnim = 0;
}
hostActivityDelegator.overridePendingTransition(enterAnim, exitAnim);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
final Intent pluginIntent = new Intent(intent);
pluginIntent.setExtrasClassLoader(mPluginClassLoader);
ComponentName callingActivity = new ComponentName(getPackageName(), getClass().getName());
final boolean success = mPluginComponentLauncher.startActivityForResult(hostActivityDelegator, pluginIntent, requestCode, options, callingActivity);
if (!success) {
hostActivityDelegator.startActivityForResult(intent, requestCode, options);
}
}
@Override
public SharedPreferences getPreferences(int mode) {
return super.getSharedPreferences(getLocalClassName(), mode);
}
@Override
public String getLocalClassName() {
return this.getClass().getName();
}
@Override
public boolean shouldUpRecreateTask(Intent targetIntent) {
Intent intent = mPluginComponentLauncher.convertPluginActivityIntent(targetIntent);
return hostActivityDelegator.shouldUpRecreateTask(intent);
}
@Override
public boolean navigateUpTo(Intent upIntent) {
Intent intent = mPluginComponentLauncher.convertPluginActivityIntent(upIntent);
return hostActivityDelegator.navigateUpTo(intent);
}
此外,onBackPressed()、onActivityResult() 等回调也会被代理转发。
5.7 Context 替换与资源隔离
为了使插件 Activity 能正确访问资源、主题、ClassLoader 等,Shadow 会:
- 创建
ShadowContext,封装插件的Resources、AssetManager、ClassLoader - 通过 编译期字节码插桩 (Transform)将插件中所有
this(作为 Context)替换为getShadowDelegate().getPluginContext() - 在运行时通过
ReflectUtil.setField(activity, "mBase", shadowContext)注入上下文(部分版本使用 delegate 机制避免反射)
"零反射"实现 :Shadow 尽量在编译期完成适配,仅在必要时使用少量反射(如设置
mBase),相比其他框架大幅降低兼容性风险。
5.8 插件资源与类加载隔离
每个插件拥有独立的:
DexClassLoader:加载插件 DEXAssetManager:加载插件资源(通过addAssetPath())Resources:基于插件 AssetManager 构建
在 DynamicPluginLoader.loadPlugin() 中:
java
DexClassLoader classLoader = new DexClassLoader(
apkPath,
optDir.getAbsolutePath(),
libDir.getAbsolutePath(),
getHostClassLoader()
);
AssetManager assetManager = createAssetManager(apkPath);
Resources resources = new Resources(assetManager, ..., ...);
这样确保插件资源与宿主完全隔离,同时支持跨插件或访问宿主资源(通过配置)。
六、关键设计思想总结
| 技术挑战 | Shadow 解决方案 |
|---|---|
| Manifest 限制 | 宿主预注册通用代理 Activity(Placeholder) |
| Activity 生命周期 | 容器 Activity 手动转发所有生命周期回调 |
| Context 正确性 | 编译期插桩 + ShadowContext 代理 |
| 资源隔离 | 每个插件独立 AssetManager 和 Resources |
| 无反射 / 无 Hook | 基于接口 + 代码生成 + 字节码修改,规避系统限制 |
七、结语
Shadow 通过 "代理容器 + 编译期插桩 + 运行时隔离" 的组合策略,实现了高度兼容、稳定可靠的插件化方案。其启动插件 Activity 的流程虽涉及多层抽象,但逻辑清晰、职责分明,是 Android 插件化技术中的优秀实践。
对于希望构建大型模块化 App 或实现热更新能力的团队,Shadow 值得深入研究与集成。