Compose 与第三方库集成实战指南

2026-05-21 · 45-50 min · 第三方库集成

在实际项目中,我们通常需要集成各种成熟的第三方库来加速开发。Compose 作为声明式 UI 框架,与传统 View 系统的集成方式有所不同。本文将全面介绍如何在 Compose 中集成常用的第三方库,包括图片加载、网络请求、数据库、分页、后台任务等。

📚 官方参考

目录

I. 图片加载:Coil vs Glide

1.1 Coil - Compose 官方推荐

Coil (Coroutine Image Loader) 是专为 Kotlin 和 Compose 设计的图片加载库。

添加依赖

        // build.gradle.kts
dependencies {
    implementation("io.coil-kt:coil-compose:2.6.0")
}
        
基础使用

        import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter

@Composable
fun BasicImageExample() {
    // 方式1:AsyncImage (推荐)
    AsyncImage(
        model = "https://example.com/image.jpg",
        contentDescription = "Example Image",
        modifier = Modifier.size(200.dp)
    )
    
    // 方式2:使用 Painter
    Image(
        painter = rememberAsyncImagePainter("https://example.com/image.jpg"),
        contentDescription = "Example Image",
        modifier = Modifier.size(200.dp)
    )
}
        
高级配置

        @Composable
fun AdvancedCoilExample() {
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data("https://example.com/image.jpg")
            .crossfade(true)
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error)
            .size(Size.ORIGINAL)
            .transformations(
                CircleCropTransformation(),
                RoundedCornersTransformation(16f)
            )
            .build(),
        contentDescription = null,
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(16.dp))
    )
}
        
加载状态监听

        @Composable
fun ImageWithLoadingState() {
    var isLoading by remember { mutableStateOf(true) }
    var isError by remember { mutableStateOf(false) }
    
    Box(modifier = Modifier.size(200.dp)) {
        AsyncImage(
            model = ImageRequest.Builder(LocalContext.current)
                .data("https://example.com/image.jpg")
                .listener(
                    onStart = { isLoading = true },
                    onSuccess = { _, _ -> isLoading = false },
                    onError = { _, _ -> 
                        isLoading = false
                        isError = true
                    }
                )
                .build(),
            contentDescription = null,
            modifier = Modifier.fillMaxSize()
        )
        
        if (isLoading) {
            CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
        }
        
        if (isError) {
            Icon(
                imageVector = Icons.Default.Error,
                contentDescription = null,
                modifier = Modifier.align(Alignment.Center)
            )
        }
    }
}
        

1.2 Glide - 传统方案

Glide 也支持 Compose,但配置相对复杂。


        // 依赖
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")

@Composable
fun GlideImageExample() {
    GlideImage(
        model = "https://example.com/image.jpg",
        contentDescription = null,
        loading = placeholder(R.drawable.placeholder),
        failure = placeholder(R.drawable.error),
        modifier = Modifier.size(200.dp)
    )
}
        

1.3 性能对比与选择

推荐: 新项目使用 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 与第三方库集成的核心原则:

图片加载

网络请求

数据库

分页加载

后台任务

导航

---

推荐阅读

特性CoilGlide
Compose 支持原生支持需要额外集成
Kotlin 友好完全 KotlinJava 为主
包大小较小 (~2MB)较大 (~5MB)
协程支持原生支持需要额外封装
推荐度⭐⭐⭐⭐⭐⭐⭐⭐
📚 官方参考
Jetpack Compose Interoperability APIs
Compose and other libraries - Android Developers
Modern Android Development (MAD) - Android Developers