Android 互操作
Android 是 Kotlin Multiplatform 项目中最成熟的平台之一。理解如何在共享代码中集成 Android 特性,是构建跨平台应用的基础。
Kotlin 调用 Android API
kotlin
// androidMain
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.annotation.RequiresApi
// 访问 SharedPreferences
class AndroidStorage(private val context: Context) {
private val prefs: SharedPreferences = context.getSharedPreferences(
"app_prefs",
Context.MODE_PRIVATE
)
fun saveString(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
fun getString(key: String, default: String = ""): String {
return prefs.getString(key, default) ?: default
}
}
// 获取系统信息
fun getDeviceInfo(): String {
return "Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
}
// 检查权限
fun hasPermission(context: Context, permission: String): Boolean {
return context.checkSelfPermission(permission) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
}
// 获取资源
fun getStringResource(context: Context, resId: Int): String {
return context.getString(resId)
}Context 传递策略
方式 1:全局 Context 管理
kotlin
// androidMain
import android.app.Application
object AppContext {
lateinit var application: Application
private set
fun init(app: Application) {
application = app
}
val context: Context
get() = application.applicationContext
}
// Application 类中初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AppContext.init(this)
}
}xml
<!-- AndroidManifest.xml -->
<application
android:name=".MyApplication"
...>
</application>方式 2:依赖注入传递
kotlin
// commonMain
interface StorageFactory {
fun createStorage(): Storage
}
expect fun getStorageFactory(): StorageFactory
// androidMain
class AndroidStorageFactory(private val context: Context) : StorageFactory {
override fun createStorage(): Storage {
return AndroidStorage(context)
}
}
actual fun getStorageFactory(): StorageFactory {
return AndroidStorageFactory(AppContext.context)
}方式 3:构造函数传递
kotlin
// commonMain
expect class PlatformContext
class UserRepository(context: PlatformContext) {
// ...
}
// androidMain
actual typealias PlatformContext = Context
// Android 使用
val repository = UserRepository(context)Android Jetpack 集成
ViewModel 集成
kotlin
// androidMain
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = repository.getUsers()
}
}
}LiveData 与 Flow 互操作
kotlin
// androidMain
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.flow.Flow
// Flow → LiveData
fun <T> Flow<T>.toLiveData(): LiveData<T> {
return this.asLiveData()
}
// 使用示例
class MyViewModel : ViewModel() {
val users: LiveData<List<User>> = repository.observeUsers().toLiveData()
}Compose 集成
kotlin
// androidMain
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
@Composable
fun UserScreen(viewModel: UserViewModel) {
val users by viewModel.users.collectAsState()
LazyColumn {
items(users) { user ->
Text(text = user.name)
}
}
}资源访问
字符串资源
kotlin
// androidMain
import android.content.res.Resources
class LocalizedStrings(private val resources: Resources) {
fun get(resId: Int): String = resources.getString(resId)
fun getFormatted(resId: Int, vararg args: Any): String {
return resources.getString(resId, *args)
}
}
// 从共享代码访问
expect class StringProvider {
fun getString(key: String): String
}
// androidMain 实现
actual class StringProvider(private val context: Context) {
actual fun getString(key: String): String {
val resId = context.resources.getIdentifier(
key,
"string",
context.packageName
)
return if (resId != 0) {
context.getString(resId)
} else {
key // Fallback
}
}
}Drawable 资源
kotlin
// androidMain
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
fun getDrawable(context: Context, resId: Int): Drawable? {
return ContextCompat.getDrawable(context, resId)
}
// 图片加载
import coil.ImageLoader
import coil.request.ImageRequest
fun loadImage(context: Context, url: String, into: android.widget.ImageView) {
val request = ImageRequest.Builder(context)
.data(url)
.target(into)
.build()
ImageLoader(context).enqueue(request)
}权限处理
权限检查与请求
kotlin
// androidMain
import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
class PermissionManager(private val activity: ComponentActivity) {
private val requestPermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
// 处理权限结果
}
fun checkPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
activity,
permission
) == PackageManager.PERMISSION_GRANTED
}
fun requestPermission(permission: String) {
requestPermissionLauncher.launch(permission)
}
}
// 在共享代码中抽象
interface PermissionHandler {
suspend fun requestPermission(permission: String): Boolean
fun hasPermission(permission: String): Boolean
}生命周期感知
Lifecycle 集成
kotlin
// androidMain
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
class DataObserver(private val lifecycleOwner: LifecycleOwner) {
fun observe(flow: Flow<Data>, onData: (Data) -> Unit) {
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
flow.collect { data ->
onData(data)
}
}
}
}
}WorkManager 集成
后台任务调度
kotlin
// androidMain
import androidx.work.*
import java.util.concurrent.TimeUnit
class DataSyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val repository = UserRepository(/* ... */)
repository.syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
// 调度工作
fun scheduleDataSync(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val syncRequest = PeriodicWorkRequestBuilder<DataSyncWorker>(
15, TimeUnit.MINUTES
)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"data_sync",
ExistingPeriodicWorkPolicy.KEEP,
syncRequest
)
}Gradle 配置
Android 目标配置
kotlin
// shared/build.gradle.kts
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
androidMain.dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.work.runtime)
}
}
}
android {
namespace = "com.example.shared"
compileSdk = 35
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}ProGuard/R8 配置
proguard
# shared/proguard-rules.pro
# Kotlin 反射
-keep class kotlin.Metadata { *; }
# Kotlinx Serialization
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt
# Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# 共享模块类
-keep class com.example.shared.** { *; }最佳实践
✅ 实践 1:最小化 Android 依赖
kotlin
// ✅ 在共享代码中定义接口
interface Analytics {
fun logEvent(name: String, params: Map<String, Any>)
}
// ❌ 直接依赖 Android 库
import com.google.firebase.analytics.FirebaseAnalytics✅ 实践 2:使用 Context 包装器
kotlin
// androidMain
class AppContextProvider(private val context: Context) {
val filesDir: String
get() = context.filesDir.absolutePath
val cacheDir: String
get() = context.cacheDir.absolutePath
fun getSharedPreferences(name: String): SharedPreferences {
return context.getSharedPreferences(name, Context.MODE_PRIVATE)
}
}✅ 实践 3:版本检查
kotlin
// androidMain
import android.os.Build
import androidx.annotation.RequiresApi
fun isAtLeastAndroid12(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
}
@RequiresApi(Build.VERSION_CODES.S)
fun useAndroid12Feature() {
// Android 12+ 特性
}
// 在运行时检查
if (isAtLeastAndroid12()) {
useAndroid12Feature()
}✅ 实践 4:测试友好的设计
kotlin
// 提供测试用的 Context 替代
interface AppContext {
fun getString(resId: Int): String
fun getSharedPreferences(name: String): SharedPreferences
}
class RealAppContext(private val context: Context) : AppContext {
override fun getString(resId: Int) = context.getString(resId)
override fun getSharedPreferences(name: String) =
context.getSharedPreferences(name, Context.MODE_PRIVATE)
}
class FakeAppContext : AppContext {
override fun getString(resId: Int) = "test_string"
override fun getSharedPreferences(name: String) =
/* Mock SharedPreferences */
}常见集成库
依赖配置
toml
# gradle/libs.versions.toml
[versions]
androidx-core = "1.15.0"
androidx-lifecycle = "2.8.7"
androidx-work = "2.10.0"
androidx-room = "2.6.1"
[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
androidx-work-runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }Android 作为 Kotlin Multiplatform 的主力平台,提供了丰富的生态和工具链。合理利用 Android Jetpack 和现代 Android 开发模式,可以构建高质量的跨平台应用。