#1388·Shadow

Shadow 接入使用基础

Author: s-charvinCreated Jun 28, 2025Updated Oct 16, 2025

Shadow 解析

Shadow 是腾讯开源的 Android 插件化框架.

框架结构及设计理念

插件代码加载:

  • 使用自定义的 DexClassLoader 加载插件 APK 中的普通类. 只有在加载 Activity 容器时, 通过反射插入 DexClassLoader 到宿主的 PathClassLoader 前面, 实现 BootClassLoader <- DexClassLoader <- PathClassLoader 逻辑, 使得系统在向 PathClassLoader 查找 ContainerActivity 时能够正确找到实现.
  • 插件框架的代码本身也是插件的一部分, 可以随插件一起更新, 避免了宿主代码更新困难的问题.
  • 通过ClassLoader的隔离机制确保插件之间的代码不会相互干扰

系统组件注册:

  • 宿主预注册壳子代理组件: 在宿主的 AndroidManifest.xml 中预先注册一系列壳子代理组件(Activity、Service等).
  • 框架内部维护插件组件与宿主壳子代理组件的映射关系, 当插件想要启动组件时, Shadow 会将插件组件 Intent 转换为对应的宿主壳子代理组件 Intent, 从而实现对系统服务的调用.
  • 委托机制: 使用 HostActivityDelegate 和 ShadowActivityDelegate 等委托类, 实现壳子代理组件与插件组件之间的方法调用转发.

资源加载:

  • 双方案适配:根据Android系统版本采用不同的资源加载策略:
    • 在API 26及以下版本:使用 MixResources 方案, 将宿主资源和插件资源混合在一起, 先尝试从插件 Resources 获取,失败则从宿主 Resources 获取
    • 在API 27及以上版本:通过 ApplicationInfo.sharedLibraryFiles 将插件 APK 添加到 Resources 对象中. 插件和宿主之间资源互相访问还是需要接口层来实现.
  • 资源ID冲突解决: 通过资源 ID 分区, 确保插件和宿主的资源ID不会冲突. 宿主使用 0x7f 分区, 插件使用0x80或更大的分区.
  • WebView 资源处理:
    • 因为 WebView 在初始化时会向系统构造的 Resources 对象注入 webview.apk, 以便 WebView 可以使用自己的资源. 但它不会向插件构造的 Resources 对象自动注入这些资源.
    • Shadow 框架会在在创建插件 Resources 时, 尝试创建 Webview 触发对应的初始化流程, 然后通过复制宿主的 sharedLibraryFiles, 将包含 webview.apk 的资源路径传递给插件 Resources.

插件和宿主,以及插件和插件之间的通信:

  • 直接或接口调用: 插件可以直接使用宿主中的类和方法, 但是要求宿主将需要暴露给插件的接口打包成独立的共享库, 供插件使用.
  • 广播通信: 通过 Android 的广播机制实现宿主和插件之间的通信
  • ContentProvider通信: Shadow 预注册了 PluginContainerContentProvider, 用于跨进程通信.

插件管理机制(补充):

  • 插件包结构约定:
    • zip 包格式, 包含插件管理器 APK, 插件运行时环境处理插件 APK, 业务插件加载插件 APK, 业务插件 APK 和相关配置文件. 所有插件都是可更新的, 更新时只需要更新插件 zip 包.
    • 配置文件会通过 gradle 插件自动生成, 包含当前插件包的
      • version: 插件包的版本号, 用于版本控制和更新.
      • compact_version: 插件包的兼容版本号, 用于旧 App 加载新插件包的兼容性支持.
      • UUID: 插件包的唯一标识符, 用于标识插件包的唯一性, 升级时必须更改.
      • UUID_NickName: 插件包的版本昵称, 用于辅助识别插件版本 (如"v1.0.3-beta").
      • pluginLoader: 处理业务插件加载的插件的插件信息 (包括路径和 hash), 主要负责加载插件类.
      • runtime: 处理业务插件运行时环境处理的插件的插件信息, 用于支持插件的生命周期管理等运行时操作.
      • plugins: 插件包内的所有业务插件信息的列表.
  • 业务插件约定:
    • 所有业务插件在某个插件的 build.gradle 文件中声明注册, 最终经由 gradle 插件自动识别对应的插件构建任务, 将插件及其插件信息自动打包到插件包中.
    • 每个插件有固定的结构约定:
      • businessName: 插件所代表的业务模块名称, 通常用于业务层标识.
      • partKey: 插件的唯一标识符, 用于标识插件的唯一性.
      • hostWhiteList: 表示此插件可访问的宿主内部的包名白名单, 非白名单的包名内部的类和方法将无法被插件访问.
      • dependsOn: 表示插件所依赖的其他插件模块, 如业务插件通常依赖基础模块.
      • buildTask: 插件的构建任务名称, 编译时自动打包当前插件的 apk 包时使用. 不注入插件配置信息.
      • apkPath: buildTask 任务执行后, 插件 APK 的生成路径, 用于编译后打包插件到插件包中. 不注入插件配置信息.
      • hash: 插件 apk 文件的哈希值, 用于校验插件的完整性和一致性. 自动生成, 不需要手动设置.

[!tip] 壳子代理: 用于代替插件内真正的页面、服务、广播接收器等组件的空壳占位类. 系统启动插件的对应组件时, 先访问壳子代理组件, 然后通过 runtime 访问插件内的真正组件.

使用介绍

基于 Shadow 框架的应用项目由几部分组成:

  • 宿主应用(host, 唯一, 必须): 打包了通用接口, 注册了壳子代理组件, 打包插件管理器(manager)的动态升级逻辑.
  • 插件管理器应用(manager, 唯一, 必须): 负责下载、安装插件, 同时带有一个动态的 View 表达 Loading 态.
  • 插件加载器应用(plugin->loader, 多个, 必须): 负责加载插件, 定义插件组件和壳子代理组件的配对关系.
  • 插件运行时应用(plugin->runtime, 待定, 必须): 负责插件的运行时逻辑, 定义壳子代理组件(Activity/Service/BroadcastReceiver的占位组件)的实际类.
  • 插件业务应用(plugin->business_plugin, 多个, 必须): 可以安装的业务插件.

一般的项目结构

├── projects
│   ├── host // 宿主
│   ├── host-lib // 宿主库
│   ├── contract // 宿主和插件之间的契约
│   ├── constant // 宿主, manager 和 loader 间共用的常量
│   ├── manager // 插件管理器
│   ├── plugin // 插件
│   │   ├── loader // 插件加载器
│   │   ├── runtime // 插件运行时
│   │   ├── business_lib // 业务依赖库, 包含完整业务代码
│   │   ├── business_app // 业务 app, 基于 business_lib, 可以直接打包和运行的业务 app
│   │   ├── business_plugin // 业务插件, 基于 business_lib, 仅包含业务代码和插件框架代码, 不能直接打包和运行的业务插件

[!tip] 目前来看, 业务插件和业务 app 的打包逻辑可以合并为一个, 插件相关的依赖使用 pluginImplementation, pluginCompileOnly 就可以了.

编译和发布 sdk

[TODO]

宿主(host)编写流程

  1. 依赖配置

    在 build.gradle 中添加以下依赖和配置:

    gradle
    android {
        // 配置资源目录, 用于构建过程中把自动生成的插件管理器 APK 和插件 ZIP 包 添加到宿主 App 的 assets 资源目录
        sourceSets {
            debug {
                // Debug版本的资源目录
                assets.srcDir('build/generated/assets/sample-manager/debug/')
                assets.srcDir('build/generated/assets/plugin-zip/debug/')
            }
            release {
                // Release版本的资源目录
                assets.srcDir('build/generated/assets/sample-manager/release/')
                assets.srcDir('build/generated/assets/plugin-zip/release/')
            }
        }
    }
    dependencies {
        implementation 'com.tencent.shadow.core:common'
        implementation 'com.tencent.shadow.dynamic:dynamic-host'
    }
  2. AndroidManifest.xml 配置

    • 自定义和注册 PluginLoadActivityXxx, 用于承接插件加载逻辑.
    • 自定义和注册 PluginProcessServiceXxx 类型的插件进程服务, 用于管理插件加载过程, 每个插件进程都需要注册一个.
    • 自定义和注册 PluginXxxProxyActivity, 为插件内业务 Activity 进行系统事件转发和处理, 可以有多个, 每个是不同的启动模式.
    • 自定义和注册 MainProcessManagerReceiver 接收器, 用于接收插件进程加载相关的的广播消息.
    • 注册 PluginContainerContentProvider 用于跨进程通信.
    xml
    <activity
        android:name=".PluginLoadActivityXxx"
        android:launchMode="standard"
        android:screenOrientation="portrait" />
    
    <service
        android:name=".PluginProcessServiceXxx"
        android:process=":plugin" />
    
    <activity
        android:name="com.tencent.shadow.sample.plugin.runtime.PluginXxxProxyActivity"
        android:launchMode="standard"
        android:screenOrientation="portrait"
        android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
        android:hardwareAccelerated="true"
        android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
        android:multiprocess="true" />
    
    <provider
        android:authorities="${applicationId}.contentprovider.authority.dynamic"
        android:name="com.tencent.shadow.core.runtime.container.PluginContainerContentProvider"
        android:grantUriPermissions="true"
        android:process=":plugin" />
    
    <receiver android:name=".plugin_view.MainProcessManagerReceiver">
        <intent-filter>
            <action android:name="sample_host.manager.startPluginService" />
        </intent-filter>
    </receiver>

[!tip] PluginXxxProxyActivity 是页面的壳子组件, 实现放在了 runtime 插件中. 编写时因为没依赖 runtime 库标红是正常的, 宿主可以正常在运行时实现对应类加载.

  1. Application 实现

    • 注意宿主进程和插件进程共用 Application 类, 即在宿主和插件的进程中都存在 Application 类, 因此初始化代码需要区分宿主和插件进程.
    • onCreate:
      • 如果是插件进程, 则恢复上次加载的插件进程的 runtime 信息, 主要是重新调整 classloader.
        • SDK: DynamicRuntime.recoveryRuntime(this);
      • 如果是宿主进程, 则初始化插件框架, 管理插件加载器, 以及其他 SDK 相关的初始化.
        • APP: PluginHelper.init(this);
        • 拷贝插件相关资源(包括插件管理器的资源包, 插件的资源包)到宿主的私有目录中.
        • 缓存资源包相关信息.
        • 初始化宿主和插件的共享资源, 如 host-lib 中的 Provider 等.
      • 提供插件管理器的唯一入口, 插件管理器包含了插件的加载和卸载逻辑, 以及插件的更新和安装逻辑.
        • APP: Shadow.getPluginManager(apk)
        • 自定义 PluginManagerUpdater 管理插件文件的更新逻辑.
        • 通过 SDK 的 DynamicPluginManager 自动管理插件管理器的更新, 安装和卸载.
  2. 插件管理器更新器 PluginManagerUpdater 的自定义实现, 官方 demo 不支持更新, 直接使用的固定路径的 apk.

    kotlin
    class PluginManagerUpdaterImpl : PluginManagerUpdater {
        override fun wasUpdating():  Boolean {
            // 检查插件管理器是否正在更新
       }
    
        override suspend fun update(): File? {
            // 异步更新插件管理器的 apk 文件
       }
    
       override fun getLatest(): File? {
            // 获取当前缓存的最新的插件管理器的 apk 文件
        }
    
       override suspend fun isAvailable(file: File): Boolean {
             // 检查 apk 文件是否可用
       }
    }

    完整的插件管理器升级器一般需要实现以下功能:

    • 服务器端更新支持 - 从远程服务器检查和下载更新
    • 断点续传 - 网络中断后可从断点继续下载
    • 文件完整性校验 - MD5 哈希验证确保文件完整性
    • 版本管理 - 智能版本比较和管理
    • 状态持久化 - 更新状态在应用重启后保持
    • 进度回调 - 详细的更新进度和状态回调
    • 线程安全 - 并发控制确保更新过程安全
    • 错误处理 - 完善的异常处理和重试机制
    • 本地更新支持 - 从本地文件检查更新
    • 支持补丁升级 - 插件升级时可以使用补丁包进行增量更新
  3. 启动插件: 基于 PluginLoadActivityXxx 发起加载插件的请求(Intent), 通过 Extras 传递插件相关参数.

    • KEY_ACTIVITY_CLASSNAME: 插件内的 Activity 类名, 必须是完整的类名, 如 com.tencent.shadow.sample.plugin.business.MainActivity.
    • KEY_PLUGIN_PART_KEY: 插件分区标识, 用于区分一个插件包内的不同插件 apk.
    • KEY_FROM_ID: 启动插件的目标类型 ID, 如 FROM_ID_NOOP, FROM_ID_START_ACTIVITY, FROM_ID_CLOSE, FROM_ID_LOAD_VIEW_TO_HOST
    • KEY_EXTRAS: 执行目标操作时需要给对应目标的额外参数, Bundle 对象, 例如打开 Activity 时需要传递的额外启动参数.

[!tip]

为什么不在这里传递 KEY_PLUGIN_ZIP_PATH, 而是在 PluginLoadActivityXxx 直接写死的插件 zip 包路径, 不推荐使用多个插件 zip 包吗?

  1. 插件加载的入口 PluginLoadActivityXxx 实现

    • 通常含有一个用于显示插件加载 view 的容器
    • onCreate 中获取 Intent 中的插件相关参数, 如插件 zip 包路径, Activity 类名等.
    • 通过协程或者线程执行插件加载
      • 通过 Application 访问唯一插件管理器
      • 基于启动 PluginLoadActivityXxx 时传递的 intent 以及写死的插件 zip 包路径 KEY_PLUGIN_ZIP_PATH, 构造插件加载参数, 并通过插件管理器执行与 KEY_FROM_ID 对应的操作.
        • FROM_ID_NOOP: 空操作
        • FROM_ID_START_ACTIVITY: 启动插件内部的 Activity
        • FROM_ID_CLOSE: 关闭插件进程
        • FROM_ID_LOAD_VIEW_TO_HOST: 加载插件 view 到宿主
        • 等...
    • 插件加载完成后, 触发 finish, 在 onDestroy 中释放当前插件管理器的资源和加载视图资源.

插件管理器(manager)编写流程

在 Shadow 插件框架中, 插件管理器的实例由宿主应用的 Application 类提供访问入口, 具体类型为 DynamicPluginManager. 该对象是插件系统的核心组成部分, 负责在运行时动态加载和管理插件, 并支持插件管理器自身的热更新与替换逻辑.

首先, 其动态更新能力依赖于宿主中开发者自定义的 PluginManagerUpdater 实现类, 在需要时可以实现替换或升级插件管理器. 而其运行时插件加载与管理能力, 则由开发者实现的 ManagerFactoryImpl 和自定义的 XxxPluginManagerThatUseDynamicLoader 实现类来完成.

DynamicPluginManager 会自动通过反射加载 ManagerFactoryImpl 类, 并调用其 buildManager 方法来创建具体的插件管理器实例 XxxPluginManagerThatUseDynamicLoader.

  1. 依赖配置

    在 build.gradle 中添加以下依赖:

    gradle
    dependencies {
        implementation 'com.tencent.shadow.dynamic:dynamic-manager'
        implementation 'com.tencent.shadow.core:manager'
        implementation 'com.tencent.shadow.dynamic:dynamic-loader'
        
        compileOnly 'com.tencent.shadow.core:common'
        compileOnly 'com.tencent.shadow.dynamic:dynamic-host'
    }
  2. 实现 sdk 内部所需的插件管理器工厂实现类 ManagerFactoryImpl 和可选的插件框架类加载白名单.

[!tip]

ManagerFactoryImpl 的包名, 类名, 方法, 继承关系必须固定

```kotlin
package com.tencent.shadow.dynamic.impl

import android.content.Context
import com.tencent.shadow.dynamic.host.ManagerFactory
import com.tencent.shadow.dynamic.host.PluginManagerImpl

class ManagerFactoryImpl : ManagerFactory {
    override fun buildManager(context: Context): PluginManagerImpl {
        return 这里填写具体的自定义实现类
    }
}

interface WhiteList {
    companion object {
        val sWhiteList = arrayOf(
            "com.tencent.host.shadow",
            "com.tencent.shadow.test.lib.constant"
        )
    }
}
```
  • 插件管理器工厂类会在使用 SDK 的 DynamicPluginManager 时, 自动从配置的插件管理器 APK 文件中通过反射加载.
  • 插件框架类加载白名单会在 SDK 加载 apk 文件时自动加载和绑定到对应的插件类加载器中.
  1. 自定义插件管理器实现类(一般分两部分实现(可以合并为一部分), FastPluginManager 只负责插件安装和加载, XxxPluginManager 负责根据插件管理器负责的业务行为或场景提供插件管理器所需的业务逻辑)

    插件管理器的业务行为主要由 getName() 方法和 enter() 方法控制. getName() 方法用于对不同插件管理器的持久化存储路径进行区分; enter() 方法控制当前插件管理器所支持的插件操作, 如启动插件 Activity, 关闭插件等操作.

    kotlin
    class XxxPluginManager(
        private val appContext: Context
    ) : FastPluginManager(appContext) {
        override fun getName(): String = "test-dynamic-manager"
    
        override fun enter(context: Context, fromId: Long, bundle: Bundle, callback: EnterCallback?) {
            when (val action = PluginAction.fromId(fromId)) {
                is PluginAction.NoOp -> {}
                is PluginAction.StartActivity -> {}
                is PluginAction.Close -> {}
            }
        }
    }

    启动插件 Activity 的逻辑:

    • 执行 callback 的展示加载 view 逻辑
    • 安装 KEY_PLUGIN_ZIP_PATH 参数指定的插件 zip 包
    • 加载 KEY_PLUGIN_PART_KEY 参数指定的插件
    • 手动调用插件 ApplicationonCreate 方法
    • 转换和构造能启动目标 Activity 的 Intent, 然后通过内部绑定的插件加载服务接口 mPluginLoader 在插件中打开目标 Activity.
    • 执行 callback 的隐藏加载 view 逻辑

    关闭插件进程的逻辑:

    • 直接调用 close() 方法, 触发内部 SQLite 数据库的关闭.

    安装插件的逻辑(处理插件包解析, 插件信息持久化):

    • KEY_PLUGIN_ZIP_PATH 提供的 zip 包中解析插件配置信息, 解析通过调用 SDK 内部方法 installPluginFromZip
    • 将所有插件 apk 内部的 so 库文件提取到宿主管理的目录下, 或是直接注册 so 库信息到框架内部, 运行时加载. 是否提取取决于插件内 AndroidManifest.xml 中的 extractNativeLibs 属性, 提取方法直接调用 extractSo 即可.
    • 调用 onInstallCompleted 方法, 通知框架所有插件安装完成, 并将插件信息持久化存储到 SQLite 数据库中, 所有信息共同存储在 InstalledPlugin 实例中.
    • 返回已安装的插件包信息, 类型为 InstalledPlugin.

    加载插件的逻辑: - 通过 KEY_PLUGIN_PART_KEY 提供的插件分区标识 partKey 绑定此插件对应的插件进程服务类 XxxPluginProcessService. - 优先加载插件运行时环境处理的插件(runtime)和处理业务插件加载的(loader), 然后加载业务插件. - 前两者加载需要通过 PluginProcessServiceloadPluginLoaderloadRuntime 函数 - 业务插件加载需要通过 DynamicPluginLoaderloadPlugin 函数.

    补充: 启动插件内部服务与宿主通信的方式(如在宿主中展示插件 view, 补充数据等操作):

    • 直接构造启动对应 Service 的 Intent, 然后通过内部绑定的插件加载服务接口 mPluginLoader 在插件中打开目标 Service.
    • 因为插件和宿主是跨进程隔离的, 因此两者之间需要交互的数据需要通过同时依赖的 host-lib 进行相关数据的静态全局管理.

插件编写流程

在 Shadow 插件框架中, 插件必须包含两个特殊插件: 负责业务插件运行环境处理的插件(runtime)和负责业务插件加载的插件(loader). runtime 负责插件的运行时逻辑, 包括插件的生命周期管理, 插件组件的注册和调用等. loader 负责加载插件, 包括插件的安装, 卸载, 更新等操作.

在 Shadow 插件框架中, 插件管理器的实例由宿主应用的 Application 类提供访问入口, 具体类型为 DynamicPluginManager. 该对象是插件系统的核心组成部分, 负责在运行时动态加载和管理插件, 并支持插件管理器自身的热更新与替换逻辑.

首先, 其动态更新能力依赖于宿主中开发者自定义的 PluginManagerUpdater 实现类, 在需要时可以实现替换或升级插件管理器. 而其运行时插件加载与管理能力, 则由开发者实现的 ManagerFactoryImpl 和自定义的 XxxPluginManagerThatUseDynamicLoader 实现类来完成.

DynamicPluginManager 会自动通过反射加载 ManagerFactoryImpl 类, 并调用其 buildManager 方法来创建具体的插件管理器实例 XxxPluginManagerThatUseDynamicLoader.

业务插件加载器(loader)编写流程

在 Shadow 插件框架中, 动态插件加载器 DynamicPluginLoader 是插件加载器的核心实现, 负责加载插件 APK 中的类和资源, 并提供插件组件的注册和调用功能. 插件加载器通常分为两部分实现: XxxPluginLoaderXxxComponentManager.

XxxPluginLoader 扩展自 ShadowPluginLoader, 只负责实际的插件加载过程,包括资源、类加载等核心功能. XxxComponentManager 负责管理插件中的四大组件和映射关系, 如 Activity、Service、BroadcastReceiver 和 ContentProvider.

DynamicPluginLoader 会自动通过反射加载 CoreLoaderFactoryImpl 类, 并调用其 build 方法来创建具体的插件加载器实例 XxxPluginLoader. 而 XxxComponentManager 则需要在 XxxPluginLoader 中创建和注册.

  1. 依赖配置

    在 build.gradle 中添加以下依赖和配置:

    gradle
    android {
        defaultConfig {
            // loader 插件是插件框架所必需的插件, 同时也不需要构建出可独立运行 APK
            // 而宿主加载插件时要求插件的 applicationId 和宿主的 applicationId 相同, 因此这里需要设置为 applicationId
            applicationId project.HOST_APP_APPLICATION_ID
        }
    }
    dependencies {
        implementation 'com.tencent.shadow.core:loader'
        implementation 'com.tencent.shadow.dynamic:dynamic-loader'
        implementation 'com.tencent.shadow.dynamic:dynamic-loader-impl'
    
        compileOnly 'com.tencent.shadow.core:runtime'
        compileOnly 'com.tencent.shadow.core:activity-container'
        compileOnly 'com.tencent.shadow.core:common'
        compileOnly 'com.tencent.shadow.dynamic:dynamic-host'
    }
  2. 实现 sdk 内部所需的核心加载器工厂实现类 CoreLoaderFactoryImpl 和可选的插件框架类加载白名单.

[!tip]

CoreLoaderFactoryImpl 的包名, 类名, 方法, 继承关系必须固定. 框架内部会自动通过反射去创建和使用.

```kotlin
package com.tencent.shadow.dynamic.loader.impl

import android.content.Context
import com.tencent.shadow.core.loader.ShadowPluginLoader

class CoreLoaderFactoryImpl : CoreLoaderFactory {
    override fun build(hostAppContext: Context): ShadowPluginLoader {
        return 这里填写具体的自定义实现类
    }
}

interface WhiteList {
    companion object {
        val sWhiteList = arrayOf(
            "com.tencent.shadow.sample.host.lib",
        )
    }
}
```
  1. 自定义插件加载器实现类(一般分两部分实现, XxxPluginLoader 只负责实际的业务插件的加载过程,包括资源、类加载等核心功能, XxxComponentManager 负责管理插件中的四大组件和壳子容器组件的映射关系)

业务插件加载器 XxxPluginLoader 扩展自 ShadowPluginLoader, 其业务行为主要由 getComponentManagergetDelegateProviderKey 方法控制, 因此一般情况下只需要实现两个方法, 返回一个自定义的 XxxComponentManager 实例和专用与此加载器的唯一 KEY 即可. 如果需要自定义插件加载器的其他行为, 如插件加载回调, 插件加载失败处理等, 可以重写 loadPlugin 方法. getDelegateProviderKey 返回的值一定要注意与代理类, 如 PluginDefaultProxyActivity 等的对应关系. 不然会导致插件加载时找不到对应类.

业务插件组件管理类 XxxComponentManager 负责管理插件中的四大组件和宿主中注册的壳子容器组件的映射关系, 其业务行为主要由 onBindContainerActivity 方法 和 onBindContainerContentProvider 方法控制.

  • onBindContainerActivity 方法用于将插件中的 Activity 组件与宿主中注册的壳子容器组件进行绑定, 并返回一个 ComponentName 实例, 绑定关系是通过类名实现.
  • onBindContainerContentProvider 方法用于将插件中的 ContentProvider 组件与宿主中注册的壳子容器组件进行绑定, 并返回一个 ContainerProviderInfo 实例.

[!tip] 为什么不根据插件内的 Manifest 文件中的 activity 元素的 android:name 属性来自动绑定组件, 而是通过类名实现? 为什么壳子容器组件不是自动生成, 而是手动注册?

处理业务插件运行时环境的插件(runtime)编写流程

在 Shadow 插件框架中, runtime 插件是插件运行时环境处理的插件, 负责插件的运行时逻辑, 包括插件的生命周期管理, 插件组件的注册和调用等. 处理的逻辑很复杂, 但是直接编写 runtime 插件应用很简单, 只需要实现以下几个步骤:

  1. 依赖配置

    在 build.gradle 中添加以下依赖和配置:

    gradle
    android {
        defaultConfig {
            // runtime 插件是插件框架所必需的插件, 同时也不需要构建出可独立运行 APK
            // 而宿主加载插件时要求插件的 applicationId 和宿主的 applicationId 相同, 因此这里需要设置为 applicationId
            applicationId project.HOST_APP_APPLICATION_ID
        }
    }
    dependencies {
        implementation 'com.tencent.shadow.core:activity-container'
    }
  2. 自定义各种运行时需要在宿主中使用的壳子组件, 如 XxxProxyActivity. 所有壳子 Activity 都需要继承自 PluginContainerActivity. 其中可选择实现 getDelegateProviderKey 方法, 返回一个唯一的 KEY, 用于标识此壳子组件对应的插件加载器. 如果不实现, 则默认使用 PluginContainerActivity 的 KEY.

业务插件编写流程

在 Shadow 插件框架中, 业务插件是指实际的业务逻辑代码, 包括独立的业务组件和相关资源. 每个业务插件可以分为两部分实现: 业务插件库和业务插件应用. 如果需要业务插件能够直接打包和运行, 可以再额外添加一个依赖业务插件库的对应业务应用或是处理业务插件应用的打包逻辑.

业务插件库

业务插件库是一个 library 模块, 主要用于存放当前业务插件所需的所有业务逻辑代码, 甚至是业务插件的入口 Activity 和 Application 类.

这类库的编写没什么特殊的, 与普通的 Android library 模块编写类似.

业务插件应用

业务插件应用是一个 application 模块, 主要用于打包当前业务插件所需的所有业务逻辑代码和资源, 并生成一个可安装的 APK 文件. 业务插件应用通常依赖于对应的业务插件库, 并在其基础上添加一些额外的配置和逻辑, 便于 Shadow 插件框架进行自动化处理.

[!tip] 如果有多个业务插件, 某些配置只需要在一个业务插件应用中配置即可, 其他业务插件应用可以不配置, 对应的特殊配置在下面会提到. 如果插件依赖 AppCompatActivity 进行业务开发, 需要在插件内进行主题资源适配, 而不是宿主中 runtime 依赖类型必须 compileOnly, 不然会导致 transform 时遇到壳子组件发生循环依赖

  1. 依赖配置

    在 build.gradle 中添加以下依赖和配置:

    gradle
    buildscript {
        dependencies {
            classpath 'com.tencent.shadow.core:runtime' // Shadow框架的核心运行时库
            classpath 'com.tencent.shadow.core:activity-container' // Shadow框架的Activity容器
            classpath 'com.tencent.shadow.core:gradle-plugin' // Shadow框架的Gradle插件
            classpath "org.javassist:javassist:$javassist_version" // Java字节码操作库,用于代码注入
        }
    }
    
    apply plugin: 'com.android.application'
    apply plugin: 'com.tencent.shadow.plugin'
    
    android {
        // 默认配置
        defaultConfig {
            // 业务插件一般可能需要直接打包成 APK, 因此需要设置业务对应 applicationId
            // 但是宿主使用插件时要求插件的 applicationId 和宿主的 applicationId 相同
            // 因此需要后面的 productFlavors 配置在插件打包时覆盖此值为宿主的 applicationId
            applicationId 'com.tencent.shadow.sample.plugin.app' 
        }
    
        productFlavors {
            plugin {
                // 这里会自动将插件 applicationId 设置为和宿主相同
                applicationId project.SAMPLE_HOST_APP_APPLICATION_ID
            }
        }
    
        aaptOptions {
            // 因为前面提到过低版本 Android 系统不支持插件的资源分区, 因此插件的 Resources 需要和宿主的 Resources 合并.
            // 这里需要将插件的资源ID分区改为和宿主0x7F不同的值, 避免合并过程产生资源冲突.
            additionalParameters "--package-id", "0x7E", "--allow-reserved-package-id"
        }
    }
    
    // 依赖配置
    dependencies {
        pluginCompileOnly 'com.tencent.shadow.core:runtime'
    
        pluginCompileOnly project(":sample-host-lib") // 插件类型打包时使用 compileOnly 类型依赖, 因为这个库的实现在宿主有了, 配置插件的访问白名单即可使用
        normalImplementation project(":sample-host-lib") // 正常 apk 打包时使用 implementation 类型依赖, 是直接生成独立 apk 了, 与宿主无关
    
        implementation project(":sample-base-lib") // 这里表示插件类型打包或正常 apk 打包均使用 implementation 类型依赖
    }
    
    // Shadow 插件框架的配置, 此配置只需要在业务插件应用中配置一次即可.
    shadow {
        // 插件打包配置
        packagePlugin {
            // 加载器和运行时APK的项目路径, 用于和下面的 loaderApkConfig 和 runtimeApkConfig 配置一起实现两个插件的自动打包和加载.
            loaderApkProjectPath = 'projects/sample/source/sample-plugin/sample-loader'
            runtimeApkProjectPath = 'projects/sample/source/sample-plugin/sample-runtime'
    
            // 配置不同构建类型的插件
            pluginTypes {
                // Debug版本配置
                debug {