Android 插件化开发完全指南(Kotlin DSL/Gradle KTS 配置)
🎯 Gradle KTS 配置插件化项目
- 项目级配置 (settings.gradle.kts)
kotlin
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
// Shadow 插件仓库
maven { url = uri("https://maven.aliyun.com/repository/public") }
maven { url = uri("https://jitpack.io") }
}
plugins {
id("com.tencent.shadow.plugin") version "2.8.4"
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.aliyun.com/repository/public") }
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "PluginApp"
include(":app")
include(":plugin-loader")
include(":plugin-runtime")
include(":plugin-app")
- 根目录 build.gradle.kts
kotlin
plugins {
id("com.tencent.shadow.plugin") version "2.8.4" apply false
}
// 项目全局配置
subprojects {
apply(plugin = "org.jetbrains.kotlin.android")
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
}
}
}
🏗️ 宿主应用配置 (app/build.gradle.kts)
完整配置示例:
kotlin
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-parcelize")
id("kotlin-kapt")
id("com.tencent.shadow.plugin")
}
android {
namespace = "com.example.pluginhost"
compileSdk = 34
defaultConfig {
applicationId = "com.example.pluginhost"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
buildConfig = true
}
// 启用 Java 8+ API 去糖化
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
// Shadow 插件配置
shadow {
transform {
// 字节码转换配置
useHostContext = listOf("com.example.pluginhost")
}
packagePlugin {
pluginTypes {
create("debug") {
// 调试模式配置
loaderApkConfig = ProjectUtil.makeLoaderApkConfig("debug")
runtimeApkConfig = ProjectUtil.makeRuntimeApkConfig("debug")
pluginApks {
create("plugin-demo") {
// 业务名称
businessName = "demo"
// 插件唯一标识
partKey = "demo-plugin"
// 构建任务
buildTask = ":plugin-app:assembleDebug"
// 插件APK路径
apkPath = "plugin-app/build/outputs/apk/debug/plugin-app-debug.apk"
// 依赖的宿主列表
hostWhiteList = listOf("com.example.pluginhost")
}
}
}
create("release") {
// 发布模式配置
loaderApkConfig = ProjectUtil.makeLoaderApkConfig("release")
runtimeApkConfig = ProjectUtil.makeRuntimeApkConfig("release")
pluginApks {
create("plugin-demo") {
businessName = "demo"
partKey = "demo-plugin"
buildTask = ":plugin-app:assembleRelease"
apkPath = "plugin-app/build/outputs/apk/release/plugin-app-release.apk"
hostWhiteList = listOf("com.example.pluginhost")
}
}
}
}
// 加载器 APK 名称
loaderApkProjectPath = ":plugin-loader"
// 运行时 APK 名称
runtimeApkProjectPath = ":plugin-runtime"
// 插件 APK 保存目录
archivePrefix = "plugin"
archiveSuffix = ""
// 版本号
version = 4
compactVersion = listOf(1, 2, 3)
uuidNickName = "2.8.4"
}
}
dependencies {
// AndroidX Core
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 生命周期
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// Shadow 核心依赖
implementation("com.tencent.shadow.core:loader:2.8.4")
implementation("com.tencent.shadow.dynamic:dynamic-loader:2.8.4")
implementation("com.tencent.shadow.dynamic:dynamic-loader-impl:2.8.4")
// 插件化必备依赖(compileOnly,不会打包到宿主)
compileOnly("com.tencent.shadow.core:runtime:2.8.4")
compileOnly("com.tencent.shadow.core:activity-container:2.8.4")
compileOnly("com.tencent.shadow.core:common:2.8.4")
// Java 8+ API 去糖化支持
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
// 测试依赖
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// 网络库(可选)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
// 图片加载(可选)
implementation("io.coil-kt:coil:2.5.0")
}
// 工具类,用于生成配置
object ProjectUtil {
fun makeLoaderApkConfig(buildType: String): com.tencent.shadow.gradle.api.LoaderApkConfig {
return com.tencent.shadow.gradle.api.LoaderApkConfig(
projectPath = ":plugin-loader",
buildType = buildType
)
}
fun makeRuntimeApkConfig(buildType: String): com.tencent.shadow.gradle.api.RuntimeApkConfig {
return com.tencent.shadow.gradle.api.RuntimeApkConfig(
projectPath = ":plugin-runtime",
buildType = buildType
)
}
}
📦 插件应用配置 (plugin-app/build.gradle.kts)
kotlin
plugins {
id("com.android.application")
id("kotlin-android")
id("com.tencent.shadow.plugin")
}
android {
namespace = "com.example.plugindemo"
compileSdk = 34
defaultConfig {
applicationId = "com.example.plugindemo"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
// 插件应用不需要 applicationId 后缀
applicationVariants.all {
outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
this.outputFileName = "plugin-app-${buildType.name}.apk"
}
}
}
}
// Shadow 插件配置
shadow {
transform {
// 使用宿主上下文
useHostContext = listOf("com.example.pluginhost")
}
// 插件包名白名单
hostWhiteList = listOf("com.example.pluginhost")
}
dependencies {
// 插件依赖宿主中的库,需要使用 compileOnly
compileOnly(project(":app"))
// AndroidX 基础库
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// 插件需要 Shadow 运行时
implementation("com.tencent.shadow.core:runtime:2.8.4")
// 注意:插件中不要依赖 host 中的具体实现类,只依赖接口
}
🔧 Loader 模块配置 (plugin-loader/build.gradle.kts)
kotlin
plugins {
id("com.android.application")
id("kotlin-android")
}
android {
namespace = "com.example.pluginloader"
compileSdk = 34
defaultConfig {
applicationId = "com.example.pluginloader"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
}
}
// 设置为独立应用
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
// 重命名输出文件
applicationVariants.all {
outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
this.outputFileName = "plugin-loader-${buildType.name}.apk"
}
}
}
}
dependencies {
// Loader 需要 Shadow 核心库
implementation("com.tencent.shadow.core:loader:2.8.4")
// 基础 Android 支持
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
// Kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
}
⚙️ Runtime 模块配置 (plugin-runtime/build.gradle.kts)
kotlin
plugins {
id("com.android.application")
id("kotlin-android")
}
android {
namespace = "com.example.pluginruntime"
compileSdk = 34
defaultConfig {
applicationId = "com.example.pluginruntime"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
// 重命名输出文件
applicationVariants.all {
outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
this.outputFileName = "plugin-runtime-${buildType.name}.apk"
}
}
}
}
dependencies {
// Runtime 核心依赖
implementation("com.tencent.shadow.core:runtime:2.8.4")
implementation("com.tencent.shadow.core:activity-container:2.8.4")
// Android 支持
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.fragment:fragment-ktx:1.6.2")
}
🚀 宿主 Application 配置 (Kotlin)
kotlin
package com.example.pluginhost
import android.app.Application
import android.content.Context
import com.tencent.shadow.core.common.Installer
import com.tencent.shadow.dynamic.host.DynamicRuntime
import com.tencent.shadow.dynamic.host.PluginManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class HostApplication : Application() {
companion object {
lateinit var instance: HostApplication
private set
}
private var pluginManager: PluginManager? = null
private val applicationScope = CoroutineScope(Dispatchers.IO)
override fun onCreate() {
super.onCreate()
instance = this
// 初始化插件管理器
initPluginManager()
}
private fun initPluginManager() {
applicationScope.launch {
try {
// 初始化 Shadow 运行时
DynamicRuntime.start(this@HostApplication) { context, fromId, params ->
// 创建 PluginManager
PluginManager.getInstance(context)
}
// 获取 PluginManager
pluginManager = PluginManager.getInstance(this@HostApplication)
// 设置插件安装器
pluginManager?.setInstaller(object : Installer {
override fun install(pluginZipPath: String): Boolean {
// 安装插件逻辑
return installPlugin(pluginZipPath)
}
override fun remove(pluginKey: String): Boolean {
// 移除插件逻辑
return removePlugin(pluginKey)
}
})
// 监听插件加载
pluginManager?.addPluginLoadListener(object : PluginManager.PluginLoadListener {
override fun onLoadSuccess(pluginPackage: PluginManager.PluginPackage) {
log("插件加载成功: ${pluginPackage.packageName}")
}
override fun onLoadFailed(throwable: Throwable) {
log("插件加载失败", throwable)
}
})
} catch (e: Exception) {
log("插件管理器初始化失败", e)
}
}
}
fun getPluginManager(): PluginManager? = pluginManager
private fun installPlugin(pluginZipPath: String): Boolean {
return try {
// 实际安装逻辑
pluginManager?.installPlugin(pluginZipPath)
true
} catch (e: Exception) {
log("插件安装失败", e)
false
}
}
private fun removePlugin(pluginKey: String): Boolean {
return try {
pluginManager?.removePlugin(pluginKey)
true
} catch (e: Exception) {
log("插件移除失败", e)
false
}
}
private fun log(message: String, throwable: Throwable? = null) {
android.util.Log.d("PluginHost", message, throwable)
}
// 获取插件存储目录
fun getPluginDir(): String {
return "${filesDir.absolutePath}/plugins"
}
// 获取插件数据库目录
fun getPluginDatabaseDir(pluginPackageName: String): String {
return "${getPluginDir()}/$pluginPackageName/databases"
}
// 获取插件缓存目录
fun getPluginCacheDir(pluginPackageName: String): String {
return "${cacheDir.absolutePath}/plugins/$pluginPackageName"
}
}
🔌 插件管理器封装类
kotlin
package com.example.pluginhost
import android.content.Context
import android.content.Intent
import com.tencent.shadow.dynamic.host.PluginManager
import kotlinx.coroutines.*
import java.io.File
class PluginController private constructor(context: Context) {
companion object {
@Volatile
private var instance: PluginController? = null
fun getInstance(context: Context): PluginController {
return instance ?: synchronized(this) {
instance ?: PluginController(context.applicationContext).also { instance = it }
}
}
}
private val appContext = context.applicationContext
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// 加载插件
suspend fun loadPlugin(pluginApkPath: String, pluginPackageName: String): Result<Boolean> {
return withContext(Dispatchers.IO) {
try {
val pluginManager = (appContext as HostApplication).getPluginManager()
pluginManager ?: return@withContext Result.failure(
IllegalStateException("PluginManager not initialized")
)
// 检查插件文件
val pluginFile = File(pluginApkPath)
if (!pluginFile.exists()) {
return@withContext Result.failure(
IllegalArgumentException("Plugin file not exists: $pluginApkPath")
)
}
// 安装插件
val installSuccess = pluginManager.installPlugin(pluginApkPath)
if (!installSuccess) {
return@withContext Result.failure(RuntimeException("Plugin install failed"))
}
// 加载插件
val loadSuccess = pluginManager.loadPlugin(pluginPackageName)
Result.success(loadSuccess)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// 启动插件 Activity
fun startPluginActivity(
context: Context,
pluginPackageName: String,
activityClassName: String,
extras: Map<String, Any> = emptyMap()
): Result<Boolean> {
return try {
val intent = Intent().apply {
setClassName(pluginPackageName, activityClassName)
extras.forEach { (key, value) ->
when (value) {
is String -> putExtra(key, value)
is Int -> putExtra(key, value)
is Boolean -> putExtra(key, value)
is Long -> putExtra(key, value)
is Float -> putExtra(key, value)
is Double -> putExtra(key, value)
// 添加更多类型支持
}
}
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
// 使用 Shadow Intent 包装
val shadowIntent = com.tencent.shadow.dynamic.host.ShadowIntent(intent)
context.startActivity(shadowIntent)
Result.success(true)
} catch (e: Exception) {
Result.failure(e)
}
}
// 卸载插件
suspend fun unloadPlugin(pluginPackageName: String): Result<Boolean> {
return withContext(Dispatchers.IO) {
try {
val pluginManager = (appContext as HostApplication).getPluginManager()
pluginManager ?: return@withContext Result.failure(
IllegalStateException("PluginManager not initialized")
)
val success = pluginManager.removePlugin(pluginPackageName)
Result.success(success)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// 检查插件是否已加载
fun isPluginLoaded(pluginPackageName: String): Boolean {
return try {
val pluginManager = (appContext as HostApplication).getPluginManager()
pluginManager?.isPluginLoaded(pluginPackageName) ?: false
} catch (e: Exception) {
false
}
}
// 获取已安装插件列表
suspend fun getInstalledPlugins(): List<PluginInfo> {
return withContext(Dispatchers.IO) {
try {
val pluginManager = (appContext as HostApplication).getPluginManager()
val plugins = pluginManager?.getInstalledPlugins() ?: emptyList()
plugins.map { packageName ->
PluginInfo(
packageName = packageName,
isLoaded = pluginManager.isPluginLoaded(packageName),
// 可以添加更多插件信息
)
}
} catch (e: Exception) {
emptyList()
}
}
}
// 清理资源
fun cleanup() {
coroutineScope.cancel()
}
data class PluginInfo(
val packageName: String,
val isLoaded: Boolean,
val versionCode: Int = 0,
val versionName: String = "",
val size: Long = 0,
val installTime: Long = 0
)
}
📱 宿主 Activity 示例
kotlin
package com.example.pluginhost
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
private val pluginController by lazy {
PluginController.getInstance(this)
}
private val coroutineScope = rememberCoroutineScope()
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
var isLoading by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
var pluginList by remember { mutableStateOf<List<PluginController.PluginInfo>>(emptyList()) }
LaunchedEffect(Unit) {
loadPluginList()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("插件化演示") }
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 插件列表
LazyColumn(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(pluginList) { plugin ->
PluginItem(
plugin = plugin,
onStartClick = { startPlugin(plugin.packageName) },
onUninstallClick = { uninstallPlugin(plugin.packageName) }
)
}
}
// 加载插件按钮
Button(
onClick = {
loadPluginFromAssets()
},
modifier = Modifier.fillMaxWidth(),
enabled = !isLoading
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(8.dp))
Text("加载中...")
} else {
Text("从 Assets 加载插件")
}
}
// 错误信息
errorMessage?.let { message ->
Text(
text = message,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(8.dp)
)
}
}
}
}
}
}
private fun loadPluginList() {
coroutineScope.launch {
pluginController.getInstalledPlugins().collect { plugins ->
pluginList = plugins
}
}
}
private fun loadPluginFromAssets() {
coroutineScope.launch {
try {
// 从 assets 复制插件到内部存储
val pluginPath = copyPluginFromAssets()
// 加载插件
val result = pluginController.loadPlugin(
pluginPath,
"com.example.plugindemo"
)
result.onSuccess { success ->
if (success) {
// 重新加载插件列表
loadPluginList()
}
}.onFailure { throwable ->
errorMessage = "加载失败: ${throwable.message}"
}
} catch (e: Exception) {
errorMessage = "复制插件失败: ${e.message}"
}
}
}
private fun startPlugin(packageName: String) {
val result = pluginController.startPluginActivity(
context = this,
pluginPackageName = packageName,
activityClassName = "$packageName.MainActivity",
extras = mapOf(
"source" to "host",
"timestamp" to System.currentTimeMillis()
)
)
result.onFailure { throwable ->
errorMessage = "启动插件失败: ${throwable.message}"
}
}
private fun uninstallPlugin(packageName: String) {
coroutineScope.launch {
val result = pluginController.unloadPlugin(packageName)
result.onSuccess { success ->
if (success) {
loadPluginList()
}
}.onFailure { throwable ->
errorMessage = "卸载失败: ${throwable.message}"
}
}
}
@Composable
fun PluginItem(
plugin: PluginController.PluginInfo,
onStartClick: () -> Unit,
onUninstallClick: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = plugin.packageName,
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "状态: ${if (plugin.isLoaded) "已加载" else "未加载"}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
if (plugin.isLoaded) {
Button(
onClick = onStartClick,
modifier = Modifier.padding(end = 8.dp)
) {
Text("启动")
}
}
OutlinedButton(onClick = onUninstallClick) {
Text("卸载")
}
}
}
}
}
private fun copyPluginFromAssets(): String {
// 实现从 assets 复制插件文件的逻辑
// 返回插件文件的完整路径
return ""
}
}
📦 插件清单配置 (plugin-app/src/main/AndroidManifest.xml)
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 插件不需要 applicationId,使用宿主提供的上下文 -->
<application
android:allowBackup="true"
android:supportsRtl="true"
tools:ignore="HardcodedDebugMode"
tools:replace="android:icon,android:label,android:theme">
<!-- 插件主 Activity -->
<activity
android:name=".MainActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 插件 Service -->
<service
android:name=".PluginService"
android:exported="false" />
<!-- 插件 BroadcastReceiver -->
<receiver
android:name=".PluginReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.example.plugin.ACTION_UPDATE" />
</intent-filter>
</receiver>
<!-- 插件 ContentProvider -->
<provider
android:name=".PluginProvider"
android:exported="false"
android:authorities="${applicationId}.provider" />
</application>
<!-- 插件权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
🚀 构建和运行
- 构建命令
bash
# 构建所有插件组件
./gradlew :app:assembleDebug
./gradlew :plugin-app:assembleDebug
./gradlew :plugin-loader:assembleDebug
./gradlew :plugin-runtime:assembleDebug
# 一键构建所有
./gradlew assembleDebug
# 生成插件包
./gradlew packageDebugPlugin
-
项目结构
plugin-app/
├── app/ # 宿主应用
│ ├── src/main/
│ │ ├── java/com/example/pluginhost/
│ │ │ ├── HostApplication.kt
│ │ │ ├── MainActivity.kt
│ │ │ └── PluginController.kt
│ │ └── assets/ # 存放插件文件
│ │ └── plugin-demo.apk
│ └── build.gradle.kts
│
├── plugin-app/ # 插件应用
│ ├── src/main/
│ │ └── java/com/example/plugindemo/
│ │ ├── MainActivity.kt
│ │ ├── PluginService.kt
│ │ └── PluginReceiver.kt
│ └── build.gradle.kts
│
├── plugin-loader/ # 加载器模块
│ └── build.gradle.kts
│
├── plugin-runtime/ # 运行时模块
│ └── build.gradle.kts
│
├── build.gradle.kts
└── settings.gradle.kts
🎯 最佳实践
- 资源隔离:插件应使用自己的资源前缀,避免与宿主冲突
- 依赖管理:插件只依赖接口,不依赖具体实现
- 版本兼容:确保插件和宿主使用相同的依赖版本
- 错误处理:完善的错误处理和降级方案
- 安全考虑:插件应运行在沙箱环境中
📝 注意事项
- 插件签名:插件APK需要与宿主使用相同的签名
- 权限管理:插件申请的权限需要在宿主中声明
- 资源冲突:避免插件和宿主资源ID冲突
- AndroidX 版本:确保所有模块使用相同的 AndroidX 版本
- ProGuard 配置:为插件配置适当的混淆规则
这个完整的 KTS 配置方案提供了一个现代化的插件化开发基础架构,适合大多数 Android 插件化场景。