Android 插件化开发完全指南(Kotlin DSL/Gradle KTS 配置)

Android 插件化开发完全指南(Kotlin DSL/Gradle KTS 配置)

🎯 Gradle KTS 配置插件化项目

  1. 项目级配置 (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")
  1. 根目录 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>

🚀 构建和运行

  1. 构建命令
bash 复制代码
# 构建所有插件组件
./gradlew :app:assembleDebug
./gradlew :plugin-app:assembleDebug
./gradlew :plugin-loader:assembleDebug  
./gradlew :plugin-runtime:assembleDebug

# 一键构建所有
./gradlew assembleDebug

# 生成插件包
./gradlew packageDebugPlugin
  1. 项目结构

    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

🎯 最佳实践

  1. 资源隔离:插件应使用自己的资源前缀,避免与宿主冲突
  2. 依赖管理:插件只依赖接口,不依赖具体实现
  3. 版本兼容:确保插件和宿主使用相同的依赖版本
  4. 错误处理:完善的错误处理和降级方案
  5. 安全考虑:插件应运行在沙箱环境中

📝 注意事项

  1. 插件签名:插件APK需要与宿主使用相同的签名
  2. 权限管理:插件申请的权限需要在宿主中声明
  3. 资源冲突:避免插件和宿主资源ID冲突
  4. AndroidX 版本:确保所有模块使用相同的 AndroidX 版本
  5. ProGuard 配置:为插件配置适当的混淆规则

这个完整的 KTS 配置方案提供了一个现代化的插件化开发基础架构,适合大多数 Android 插件化场景。

相关推荐
阿巴斯甜7 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker8 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95279 小时前
Andorid Google 登录接入文档
android
黄林晴10 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android