引言:告别"巨石",拥抱真正的模块化
在前面的系列文章中,我们从理论、历史、架构和源码等多个维度,深入探讨了Android插件化的必要性,以及ComboLite
作为一个现代化、"0 Hook"框架的设计哲学与技术实现。理论的深度最终需要通过实践来检验。今天,我们将从零开始,手把手地带领大家完成一次完整的插件化开发闭环,用ComboLite
亲手构建一个"万物皆可插拔"的动态化应用。
我们将构建一个极其简单的应用场景:一个"空壳"宿主App,动态加载一个"问候"插件,并显示由该插件提供的UI界面。这个过程虽然简单,但它将完整地覆盖从环境搭建、宿主配置、插件开发、插件打包 到动态加载的全过程,为您后续构建更复杂的动态化系统打下坚实的基础。
第一步:万丈高楼平地起 ------ 项目初始化与依赖配置
首先,请使用Android Studio创建一个新的、包含"Empty Activity"模板的Android项目。在这里,我们将其命名为ComboLiteDemo
。
ComboLite
的集成遵循现代Android项目的最佳实践,我们强烈推荐使用**Version Catalog (libs.versions.toml
)**来管理依赖,这会让您的依赖版本管理更加清晰和可维护。
-
在
gradle/libs.versions.toml
中定义依赖项打开位于项目根目录下
gradle
文件夹内的libs.versions.toml
文件,添加ComboLite
核心库和aar2apk
打包插件的版本及定义:Ini, TOML
ini[versions] # ... 其他版本定义 combolite = "1.0.0" # 强烈建议在此处使用最新的稳定版 aar2apk = "1.0.0" # 同样,使用最新版本 [libraries] # ... 其他库定义 # ComboLite 核心运行时库 combolite-core = { group = "io.github.lnzz123", name = "combolite-core", version.ref = "combolite" } [plugins] # ... 其他插件定义 # ComboLite 专用的AAR转APK打包插件 combolite-aar2apk = { id = "io.github.lnzz123.combolite-aar2apk", version.ref = "aar2apk" }
-
在项目根
build.gradle.kts
中应用打包插件aar2apk
插件只需在项目根目录的build.gradle.kts
中应用一次。它将负责后续所有被声明的插件模块的打包任务。Kotlin
scss// in your project's root /build.gradle.kts plugins { // ... 其他插件 alias(libs.plugins.combolite.aar2apk) }
-
在宿主
:app
模块的build.gradle.kts
中添加核心库宿主模块需要运行时依赖
combolite-core
。Kotlin
scss// in your :app/build.gradle.kts dependencies { // ... 其他依赖 implementation(libs.combolite.core) }
至此,我们的项目已经具备了ComboLite
框架的基础能力。
第二步:构筑坚实基座 ------ 配置"空壳"宿主
"空壳"宿主是所有插件的容器和管理器,我们需要为其进行最简化的初始化配置。
-
实现自动初始化的
Application
ComboLite
提供了极致简单的初始化方式。只需让你的Application
类继承自BaseHostApplication
,框架将自动为你完成所有繁琐的初始化工作,包括PluginManager
的配置、资源管理器的创建以及PluginCrashHandler
的注册。在
app
模块中创建HostApp.kt
:Kotlin
kotlin// in :app/src/main/java/.../HostApp.kt import com.combo.core.base.BaseHostApplication // 只需继承即可,一键完成所有配置,无需任何额外代码 class HostApp : BaseHostApplication()
然后,在
app/src/main/AndroidManifest.xml
中注册这个Application
类:XML
ini<application android:name=".HostApp" ...> </application>
-
配置代理
Activity
为了让插件能够正确地访问合并后的资源,并为未来可能的插件Activity提供代理占坑,宿主的
Activity
需要继承自BaseHostActivity
。修改我们项目模板中自动创建的
MainActivity.kt
:Kotlin
kotlin// in :app/src/main/java/.../MainActivity.kt import com.combo.core.base.BaseHostActivity // 让MainActivity继承BaseHostActivity class MainActivity : BaseHostActivity() { // ... onCreate 等方法 }
BaseHostActivity
内部重写了getResources()
和getAssets()
方法,这是实现插件资源对Jetpack Compose透明访问的关键。
第三步:创造第一个"零件" ------ 开发一个简单的"问候"插件
现在,让我们来创建第一个真正的插件。
-
新建插件模块
在项目中,选择
File > New > New Module...
,然后选择Android Library
,我们将模块命名为hello-plugin
。 -
为插件添加依赖
打开新建的
:hello-plugin/build.gradle.kts
文件。插件模块对核心库的依赖应该是编译时依赖 (compileOnly
) ,因为在实际运行时,这些类将由宿主提供。Kotlin
scss// in :hello-plugin/build.gradle.kts dependencies { // ... // 插件只在编译时需要核心库,运行时由宿主提供 compileOnly(libs.combolite.core) }
-
实现插件入口类
IPluginEntryClass
这是插件的核心。它实现了
IPluginEntryClass
接口,是插件与框架交互的唯一桥梁。在
hello-plugin
模块中创建HelloPluginEntry.kt
:Kotlin
kotlin// in :hello-plugin/src/main/java/.../HelloPluginEntry.kt package com.example.helloplugin import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.combo.core.interfaces.IPluginEntryClass class HelloPluginEntry : IPluginEntryClass { /** * (可选) 声明此插件提供的 Koin 依赖注入模块 * 插件化框架会自动处理依赖注入的加载和卸载 */ override val pluginModule: List<Module> get() = emptyList() /** * onLoad 生命周期回调 * 当插件被框架加载后,此方法会被调用。 * 这是执行所有初始化逻辑的最佳位置。 * * @param context 插件运行的上下文环境 */ override fun onLoad(context: PluginContext) { } /** * onUnload 生命周期回调 * 当插件被框架卸载前,此方法会被调用。 * 这是执行所有资源清理工作的最佳位置。 */ override fun onUnload() { } /** * 插件的UI入口,必须是一个 @Composable 函数。 * 框架在启动插件后,会调用此方法来获取并展示插件的UI。 */ @Composable override fun Content() { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text(text = "Hello from a dynamically loaded Plugin!") } } }
-
在 Manifest 中配置插件元数据
框架如何知道这个插件的ID、版本和入口类呢?答案是通过插件
AndroidManifest.xml
中的<meta-data>
标签。打开
:hello-plugin/src/main/AndroidManifest.xml
,添加以下元数据:XML
ini<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <meta-data android:name="plugin.id" android:value="com.example.hello-plugin" /> <meta-data android:name="plugin.version" android:value="1.0.0" /> <meta-data android:name="plugin.entryClass" android:value="com.example.helloplugin.HelloPluginEntry" /> <meta-data android:name="plugin.description" android:value="A simple plugin that says hello." /> </application> </manifest>
恭喜!你的第一个插件已经开发完成了!
第四步:打包!aar2apk
的一键魔法
现在,我们需要将这个library
模块打包成一个可供宿主加载的APK文件。
-
在根项目
build.gradle.kts
中配置aar2apk
回到项目根目录的
build.gradle.kts
,添加aar2apk
的配置块,并声明我们的hello-plugin
模块。Kotlin
scss// in your project's root /build.gradle.kts plugins { // ... alias(libs.plugins.combolite.aar2apk) // 在这里应用插件 } aar2apk { // 声明需要打包成APK的插件模块列表 modules { module(":hello-plugin") } }
-
执行打包任务
现在,打开Android Studio的Gradle面板,或者在终端中,你可以找到或执行新的打包任务。任务名遵循
convert_<module_path>_<buildType>
的格式。对于我们的hello-plugin
,任务名就是:convert_hello-plugin_debug
convert_hello-plugin_release
执行
convert_hello-plugin_debug
任务。Bash
bash./gradlew convert_hello-plugin_debug
-
找到你的插件APK
构建成功后,你可以在
app/build/outputs/plugin-apks/debug/
目录下,找到我们新鲜出炉的插件包:hello-plugin-debug.apk
。
第五步:见证奇迹 ------ 动态加载并运行插件
最后一步,让我们在宿主中加载并运行这个插件。为了快速验证,我们将插件APK预置在宿主的assets
目录中。
-
预置插件APK
- 在宿主
:app
模块的src/main
目录下,创建一个assets/plugins
文件夹。 - 将上一步生成的
hello-plugin-debug.apk
复制到这个目录中。
- 在宿主
-
编写宿主加载逻辑
现在,我们来填充
MainActivity.kt
的逻辑。我们将使用一个简单的ViewModel
来处理从assets
安装和加载插件的异步逻辑。LoadingViewModel.kt
Kotlin
kotlin// in :app/src/main/java/.../LoadingViewModel.kt import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.combo.core.interfaces.IPluginEntryClass import com.combo.core.manager.PluginManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import java.io.File import java.io.FileOutputStream class LoadingViewModel(application: Application) : AndroidViewModel(application) { private val _pluginEntry = MutableStateFlow<IPluginEntryClass?>(null) val pluginEntry = _pluginEntry.asStateFlow() companion object { const val PLUGIN_ID = "com.example.hello-plugin" const val PLUGIN_APK_NAME = "hello-plugin-debug.apk" } fun loadPluginFromAssets() { viewModelScope.launch { val context = getApplication<Application>() val pluginFile = File(context.filesDir, PLUGIN_APK_NAME) // 1. 从assets复制APK到应用私有目录 context.assets.open("plugins/$PLUGIN_APK_NAME").use { inputStream -> FileOutputStream(pluginFile).use { outputStream -> inputStream.copyTo(outputStream) } } // 2. 安装插件 val installResult = PluginManager.installerManager.installPlugin(pluginFile) if (installResult) { // 3. 启动插件 val launchResult = PluginManager.launchPlugin(PLUGIN_ID) if (launchResult) { // 4. 获取插件实例 _pluginEntry.value = PluginManager.getPluginInstance(PLUGIN_ID) } } } } }
MainActivity.kt
Kotlin
kotlin// in :app/src/main/java/.../MainActivity.kt import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.combo.core.base.BaseHostActivity class MainActivity : BaseHostActivity() { private val viewModel: LoadingViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val pluginEntry by viewModel.pluginEntry.collectAsState() if (pluginEntry != null) { // 插件已加载,显示插件UI pluginEntry?.Content() } else { // 插件未加载,显示加载按钮 Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Button(onClick = { viewModel.loadPluginFromAssets() }) { Text("Load Hello Plugin") } } } } } }
-
运行应用
现在,运行你的
app
模块。应用启动后,会显示一个"Load Hello Plugin"的按钮。点击它,ViewModel
中的逻辑会执行,插件被安装、加载,然后------ "Hello from a dynamically loaded Plugin!" 将会神奇地出现在你的屏幕中央!
结语:这仅仅是开始
您已经成功地跨出了最重要的一步,亲手完成了ComboLite
插件化开发的完整闭环。这不仅仅是一个简单的"Hello World",它证明了您已经掌握了将应用功能拆分为独立、动态模块的核心能力,这是开启现代化、大型App构建之路的钥匙。
今天我们只触及了冰山一角。ComboLite
还提供了对四大组件的完整支持、强大的热更新与链式重启能力、以及灵活的去中心化插件管理。我们鼓励您深入探索官方文档和示例项目,将这些强大的能力应用到您的真实项目中。
我们为你感到骄傲,欢迎来到"万物皆可插拔"的新世界!
-
项目源码 : github.com/lnzz123/Com...
- 如果
ComboLite
的设计理念与工程实践获得了你的认可,请不吝给我们一个 Star!你的支持是我们持续迭代的最大动力。
- 如果
-
示例App下载 : 点击这里直接下载APK
- 安装示例App,亲手体验一个"万物皆可插拔"的应用是怎样的。
-
交流与贡献:
- 有任何问题、建议或发现了Bug?我们期待在 GitHub Issues 中与您展开深入的技术探讨!