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 插件化场景。

相关推荐
八眼鱼2 小时前
uniappx 安卓拍照,添加水印后比例正常
android
野生风长2 小时前
从零开始的C语言:文件操作与数据管理(下)(fseek,ftell,rewind,文件的编译和链接)
android·java·c语言·开发语言·visual studio
2501_916007472 小时前
Xcode 在 iOS 上架中的定位,多工具组合
android·macos·ios·小程序·uni-app·iphone·xcode
游戏开发爱好者82 小时前
uni-app 项目在 iOS 上架过程中常见的问题与应对方式
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
iOS 抓包工具在不同场景的实际作用
android·macos·ios·小程序·uni-app·cocoa·iphone
草莓熊Lotso2 小时前
C++ 异常完全指南:从语法到实战,优雅处理程序错误
android·java·开发语言·c++·人工智能·经验分享·后端
モンキー・D・小菜鸡儿2 小时前
Android BottomSheetBehavior 使用详解
android·kotlin
帅得不敢出门2 小时前
Android Framework不弹窗设置默认sim卡
android·java·framework
技术摆渡人2 小时前
RK3588 边缘 AI 深度开发指南:从 Android NNAPI 源码到 LLM 大模型性能调优
android·人工智能