| 特性 | Coil | Glide |
| Compose 支持 | 原生支持 | 需要额外集成 |
| Kotlin 友好 | 完全 Kotlin | Java 为主 |
| 包大小 | 较小 (~2MB) | 较大 (~5MB) |
| 协程支持 | 原生支持 | 需要额外封装 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
推荐: 新项目使用 Coil,老项目迁移可考虑 Glide。
II. 网络请求:Retrofit + Compose
2.1 基础集成
// 依赖
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}
2.2 定义 API Service
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: String): User
@GET("posts")
suspend fun getPosts(@Query("page") page: Int): List<Post>
@POST("users")
suspend fun createUser(@Body user: User): User
}
// Retrofit 实例
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
)
.build()
.create(ApiService::class.java)
}
}
2.3 在 ViewModel 中使用
@HiltViewModel
class UserViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
val user = apiService.getUser(userId)
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message ?: "Unknown error")
}
}
}
}
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String) : UserUiState()
}
2.4 在 Compose 中展示
@Composable
fun UserScreen(
userId: String,
viewModel: UserViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(userId) {
viewModel.loadUser(userId)
}
when (val state = uiState) {
is UserUiState.Loading -> {
LoadingView()
}
is UserUiState.Success -> {
UserContent(user = state.user)
}
is UserUiState.Error -> {
ErrorView(message = state.message)
}
}
}
2.5 使用 Flow 实现响应式更新
@HiltViewModel
class PostsViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
val posts: Flow<List<Post>> = flow {
while (true) {
emit(apiService.getPosts(page = 1))
delay(60_000) // 每分钟刷新
}
}.catch { e ->
emit(emptyList())
}
}
@Composable
fun PostsScreen(viewModel: PostsViewModel = hiltViewModel()) {
val posts by viewModel.posts.collectAsState(initial = emptyList())
LazyColumn {
items(posts) { post ->
PostItem(post)
}
}
}
III. 数据库:Room + Compose
3.1 Room 配置
// 依赖
dependencies {
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
}
3.2 定义 Entity 和 DAO
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: String,
val name: String,
val email: String,
val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY createdAt DESC")
fun getAllUsers(): Flow<List<UserEntity>>
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserById(userId: String): Flow<UserEntity?>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: UserEntity)
@Delete
suspend fun deleteUser(user: UserEntity)
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteUserById(userId: String)
}
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
3.3 在 ViewModel 中使用
@HiltViewModel
class UsersViewModel @Inject constructor(
private val userDao: UserDao
) : ViewModel() {
// 自动观察数据库变化
val users: StateFlow<List<UserEntity>> = userDao.getAllUsers()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun addUser(name: String, email: String) {
viewModelScope.launch {
val user = UserEntity(
id = UUID.randomUUID().toString(),
name = name,
email = email,
createdAt = System.currentTimeMillis()
)
userDao.insertUser(user)
}
}
fun deleteUser(userId: String) {
viewModelScope.launch {
userDao.deleteUserById(userId)
}
}
}
3.4 在 Compose 中展示
@Composable
fun UsersScreen(viewModel: UsersViewModel = hiltViewModel()) {
val users by viewModel.users.collectAsState()
Column(modifier = Modifier.fillMaxSize()) {
// 添加按钮
Button(onClick = { viewModel.addUser("John", "john@example.com") }) {
Text("Add User")
}
// 用户列表
LazyColumn {
items(users, key = { it.id }) { user ->
UserItem(
user = user,
onDelete = { viewModel.deleteUser(user.id) }
)
}
}
}
}
@Composable
fun UserItem(user: UserEntity, onDelete: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(user.name, style = MaterialTheme.typography.titleMedium)
Text(user.email, style = MaterialTheme.typography.bodySmall)
}
IconButton(onClick = onDelete) {
Icon(Icons.Default.Delete, contentDescription = "Delete")
}
}
}
IV. 分页加载:Paging 3 + Compose
4.1 依赖配置
dependencies {
implementation("androidx.paging:paging-runtime:3.2.1")
implementation("androidx.paging:paging-compose:3.2.1")
}
4.2 定义 PagingSource
class PostPagingSource(
private val apiService: ApiService
) : PagingSource<Int, Post>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Post> {
return try {
val page = params.key ?: 1
val response = apiService.getPosts(page = page, pageSize = params.loadSize)
LoadResult.Page(
data = response.data,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.data.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, Post>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
4.3 在 ViewModel 中创建 Pager
@HiltViewModel
class PostsViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
val posts: Flow<PagingData<Post>> = Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false,
prefetchDistance = 3
),
pagingSourceFactory = { PostPagingSource(apiService) }
).flow.cachedIn(viewModelScope)
}
4.4 在 Compose 中展示
@Composable
fun PostsScreen(viewModel: PostsViewModel = hiltViewModel()) {
val lazyPagingItems = viewModel.posts.collectAsLazyPagingItems()
LazyColumn {
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val post = lazyPagingItems[index]
if (post != null) {
PostItem(post)
}
}
// 加载状态
when (lazyPagingItems.loadState.append) {
is LoadState.Loading -> {
item {
LoadingItem()
}
}
is LoadState.Error -> {
item {
ErrorItem(
message = "Failed to load",
onRetry = { lazyPagingItems.retry() }
)
}
}
else -> {}
}
}
// 下拉刷新
SwipeRefresh(
state = rememberSwipeRefreshState(lazyPagingItems.loadState.refresh is LoadState.Loading),
onRefresh = { lazyPagingItems.refresh() }
) {
// LazyColumn...
}
}
4.5 高级用法:RemoteMediator
用于实现网络+数据库的混合分页。
@OptIn(ExperimentalPagingApi::class)
class PostRemoteMediator(
private val database: AppDatabase,
private val apiService: ApiService
) : RemoteMediator<Int, PostEntity>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, PostEntity>
): MediatorResult {
return try {
val page = when (loadType) {
LoadType.REFRESH -> 1
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
?: return MediatorResult.Success(endOfPaginationReached = true)
lastItem.page + 1
}
}
val response = apiService.getPosts(page)
database.withTransaction {
if (loadType == LoadType.REFRESH) {
database.postDao().clearAll()
}
database.postDao().insertAll(response.map { it.toEntity(page) })
}
MediatorResult.Success(endOfPaginationReached = response.isEmpty())
} catch (e: Exception) {
MediatorResult.Error(e)
}
}
}
V. 后台任务:WorkManager + Compose
5.1 定义 Worker
class DataSyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// 执行同步任务
val data = fetchDataFromApi()
saveToDatabase(data)
// 更新通知
showNotification("Sync completed")
Result.success()
} catch (e: Exception) {
Log.e("DataSyncWorker", "Sync failed", e)
Result.retry()
}
}
}
5.2 在 ViewModel 中调度 Worker
@HiltViewModel
class SyncViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val workManager = WorkManager.getInstance(context)
fun scheduleSyncWork() {
val syncRequest = PeriodicWorkRequestBuilder<DataSyncWorker>(
15, TimeUnit.MINUTES
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
workManager.enqueueUniquePeriodicWork(
"DataSync",
ExistingPeriodicWorkPolicy.KEEP,
syncRequest
)
}
fun cancelSyncWork() {
workManager.cancelUniqueWork("DataSync")
}
val workInfo: Flow<WorkInfo?> = workManager
.getWorkInfosForUniqueWorkFlow("DataSync")
.map { it.firstOrNull() }
}
5.3 在 Compose 中监听 Work 状态
@Composable
fun SyncScreen(viewModel: SyncViewModel = hiltViewModel()) {
val workInfo by viewModel.workInfo.collectAsState(initial = null)
Column(modifier = Modifier.padding(16.dp)) {
Text("Sync Status:")
when (workInfo?.state) {
WorkInfo.State.ENQUEUED -> Text("Waiting to sync...")
WorkInfo.State.RUNNING -> {
Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Syncing...")
}
}
WorkInfo.State.SUCCEEDED -> Text("Sync completed", color = Color.Green)
WorkInfo.State.FAILED -> Text("Sync failed", color = Color.Red)
WorkInfo.State.CANCELLED -> Text("Sync cancelled")
else -> Text("Unknown status")
}
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(onClick = { viewModel.scheduleSyncWork() }) {
Text("Start Sync")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { viewModel.cancelSyncWork() }) {
Text("Cancel Sync")
}
}
}
}
VI. 导航进阶:Navigation Compose 深入
6.1 类型安全导航(Kotlin Serialization)
// 依赖
implementation("androidx.navigation:navigation-compose:2.7.7")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
// 定义路由
@Serializable
object Home
@Serializable
data class Profile(val userId: String)
@Serializable
data class Settings(val showAdvanced: Boolean = false)
6.2 配置导航
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Home) {
composable<Home> {
HomeScreen(
onNavigateToProfile = { userId ->
navController.navigate(Profile(userId))
}
)
}
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(
userId = profile.userId,
onBack = { navController.popBackStack() }
)
}
composable<Settings> { backStackEntry ->
val settings: Settings = backStackEntry.toRoute()
SettingsScreen(
showAdvanced = settings.showAdvanced,
onBack = { navController.popBackStack() }
)
}
}
}
6.3 深度链接集成
// AndroidManifest.xml
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="profile" />
</intent-filter>
</activity>
// Navigation
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "myapp://profile")
)
) { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(userId = profile.userId)
}
VII. 权限处理:Accompanist Permissions
7.1 依赖配置
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
7.2 单个权限请求
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraScreen() {
val cameraPermissionState = rememberPermissionState(
android.Manifest.permission.CAMERA
)
when {
cameraPermissionState.status.isGranted -> {
CameraPreview()
}
cameraPermissionState.status.shouldShowRationale -> {
Column {
Text("Camera permission is required")
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Grant Permission")
}
}
}
else -> {
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Request Camera Permission")
}
}
}
}
7.3 多个权限请求
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun LocationScreen() {
val permissionsState = rememberMultiplePermissionsState(
listOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)
)
when {
permissionsState.allPermissionsGranted -> {
LocationContent()
}
permissionsState.shouldShowRationale -> {
RationaleDialog(
onConfirm = { permissionsState.launchMultiplePermissionRequest() }
)
}
else -> {
Button(onClick = { permissionsState.launchMultiplePermissionRequest() }) {
Text("Request Location Permissions")
}
}
}
}
VIII. 其他常用库集成
8.1 Lottie 动画
// 依赖
implementation("com.airbnb.android:lottie-compose:6.4.0")
@Composable
fun LottieAnimationExample() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.animation))
val progress by animateLottieCompositionAsState(composition)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier.size(200.dp)
)
}
8.2 ExoPlayer 视频播放
// 依赖
implementation("androidx.media3:media3-exoplayer:1.3.0")
implementation("androidx.media3:media3-ui:1.3.0")
@Composable
fun VideoPlayer(videoUrl: String) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl))
prepare()
}
}
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
}
AndroidView(
factory = { context ->
PlayerView(context).apply {
player = exoPlayer
}
},
modifier = Modifier.fillMaxWidth().aspectRatio(16f / 9f)
)
}
8.3 Google Maps
// 依赖
implementation("com.google.maps.android:maps-compose:4.3.3")
@Composable
fun MapScreen() {
val singapore = LatLng(1.35, 103.87)
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(singapore, 10f)
}
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
) {
Marker(
state = MarkerState(position = singapore),
title = "Singapore",
snippet = "Marker in Singapore"
)
}
}
总结
Compose 与第三方库集成的核心原则:
图片加载
- ✅ 推荐使用 Coil(原生 Compose 支持)
- ✅ 使用
AsyncImage 简化代码
- ✅ 合理配置缓存和变换
网络请求
- ✅ Retrofit + Coroutines 是标准组合
- ✅ 在 ViewModel 中处理网络逻辑
- ✅ 使用 StateFlow 暴露 UI 状态
数据库
- ✅ Room 的 Flow 完美适配 Compose
- ✅ 使用
stateIn 转换为 StateFlow
- ✅ 数据库操作在 ViewModel 中进行
分页加载
- ✅ Paging 3 + Compose 无缝集成
- ✅ 使用
collectAsLazyPagingItems
- ✅ RemoteMediator 实现混合数据源
后台任务
- ✅ WorkManager 管理定时任务
- ✅ 使用 Flow 监听 Work 状态
- ✅ 在 Compose 中实时展示进度
导航
- ✅ 使用类型安全导航(Kotlin Serialization)
- ✅ 支持深度链接
- ✅ 参数传递类型安全
---
推荐阅读