跳转至

第06章 数据处理与API集成

数据处理与API集成图

学习目标:掌握网络请求、数据解析、本地存储和缓存策略。

预计学习时间:5-7天 实践时间:2-3天


目录

  1. 网络请求基础
  2. Retrofit与API设计
  3. 本地数据存储
  4. 数据缓存策略
  5. 图片加载
  6. 实践练习

1. 网络请求基础

1.1 HTTP基础

Kotlin
// HTTP 请求方法枚举:定义常用的 RESTful 操作类型
enum class HttpMethod {
    GET, POST, PUT, DELETE, PATCH
}

// HTTP 常见状态码常量,用于网络响应判断
object HttpStatus {
    const val OK = 200               // 请求成功
    const val CREATED = 201          // 资源创建成功
    const val BAD_REQUEST = 400      // 请求参数错误
    const val UNAUTHORIZED = 401     // 未认证(需要登录)
    const val FORBIDDEN = 403        // 无权限访问
    const val NOT_FOUND = 404        // 资源不存在
    const val SERVER_ERROR = 500     // 服务器内部错误
}

1.2 网络权限与安全

XML
<!-- AndroidManifest.xml -->
<!-- 声明网络相关权限 -->
<uses-permission android:name="android.permission.INTERNET" />           <!-- 网络访问权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 网络状态检测权限 -->

<!-- 配置网络安全策略 -->
<application
    android:networkSecurityConfig="@xml/network_security_config">
</application>
XML
<!-- res/xml/network_security_config.xml -->
<!-- 网络安全配置:控制应用的网络通信安全策略 -->
<network-security-config>
    <!-- 禁止明文传输(HTTP),强制使用 HTTPS 加密通信 -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <!-- 仅信任系统预装的 CA 证书 -->
            <certificates src="system"/>
        </trust-anchors>
    </base-config>
</network-security-config>

2. Retrofit与API设计

2.1 Retrofit配置

Kotlin
// API 接口定义:使用 Retrofit 注解声明 HTTP 请求
interface UserApi {
    // GET 请求:通过用户 ID 获取单个用户
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: String): UserDto

    // GET 请求:分页获取用户列表
    @GET("users")
    suspend fun getUsers(
        @Query("page") page: Int,      // 页码
        @Query("limit") limit: Int     // 每页数量
    ): List<UserDto>

    // POST 请求:创建新用户
    @POST("users")
    suspend fun createUser(@Body user: CreateUserRequest): UserDto

    // PUT 请求:更新用户信息
    @PUT("users/{id}")
    suspend fun updateUser(
        @Path("id") id: String,
        @Body user: UpdateUserRequest
    ): UserDto

    // DELETE 请求:删除用户
    @DELETE("users/{id}")
    suspend fun deleteUser(@Path("id") id: String)
}

// Retrofit 网络模块配置(使用 Hilt 依赖注入)
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    // 提供 OkHttpClient 单例:配置拦截器和超时
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            // 日志拦截器:记录请求/响应的完整信息
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            // 认证拦截器:自动添加 Token 到请求头
            .addInterceptor(AuthInterceptor())
            .connectTimeout(30, TimeUnit.SECONDS)  // 连接超时 30s
            .readTimeout(30, TimeUnit.SECONDS)     // 读取超时 30s
            .build()
    }

    // 提供 Retrofit 单例:配置基址和转换器
    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)          // API 基础地址
            .client(okHttpClient)
            .addConverterFactory(
                // 使用 Kotlin Serialization 进行 JSON 解析
                Json.asConverterFactory("application/json".toMediaType())
            )
            .build()
    }

    // 提供 UserApi 接口实例(由 Retrofit 动态代理生成)
    @Provides
    @Singleton
    fun provideUserApi(retrofit: Retrofit): UserApi {
        return retrofit.create(UserApi::class.java)
    }
}

// 认证拦截器:为每个请求自动添加 Bearer Token 认证头
class AuthInterceptor @Inject constructor(
    private val tokenManager: TokenManager
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().newBuilder()
            .addHeader("Authorization", "Bearer ${tokenManager.getToken()}")
            .build()
        return chain.proceed(request)   // 继续执行请求链
    }
}

2.2 数据模型设计

Kotlin
// DTO (Data Transfer Object):与服务器 JSON 结构一一对应的数据类
@Serializable
data class UserDto(
    @SerialName("id") val id: String,
    @SerialName("name") val name: String,
    @SerialName("email") val email: String,
    @SerialName("avatar_url") val avatarUrl: String?  // 可空字段:头像 URL
)

// Domain Model:业务层使用的纯净数据模型(与网络层解耦)
data class User(
    val id: String,
    val name: String,
    val email: String,
    val avatarUrl: String?
)

// Mapper 扩展函数:DTO → Domain 转换
fun UserDto.toDomain(): User = User(
    id = id,
    name = name,
    email = email,
    avatarUrl = avatarUrl
)

// Mapper 扩展函数:Domain → DTO 转换
fun User.toDto(): UserDto = UserDto(
    id = id,
    name = name,
    email = email,
    avatarUrl = avatarUrl
)

3. 本地数据存储

3.1 Room数据库

Kotlin
// Entity:Room 数据库表实体,映射到 SQLite 表
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: String,            // 主键
    val name: String,
    val email: String,
    val avatarUrl: String?,
    val updatedAt: Long = System.currentTimeMillis()  // 更新时间戳,用于缓存失效判断
)

// DAO:数据访问对象,定义数据库操作接口
@Dao
interface UserDao {  // interface定义类型契约
    // 根据 ID 查询单个用户(挂起函数,支持协程)
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getUserById(id: String): UserEntity?

    // 获取所有用户的 Flow 流(数据变化时自动发射新值)
    @Query("SELECT * FROM users")
    fun getUsersFlow(): Flow<List<UserEntity>>

    // 插入用户,冲突时替换旧数据
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: UserEntity)

    // 删除指定用户
    @Delete
    suspend fun deleteUser(user: UserEntity)

    // 清空所有用户数据
    @Query("DELETE FROM users")
    suspend fun deleteAllUsers()
}

// Database:Room 数据库定义,声明包含的实体和版本号
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao   // 提供 DAO 实例
}

// Hilt 数据库模块:提供数据库和 DAO 的单例
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    // 提供数据库单例,应用生命周期内只创建一次
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"             // 数据库文件名
        ).build()
    }

    // 提供 UserDao 实例
    @Provides
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}

3.2 DataStore

Kotlin
// Preferences DataStore:替代 SharedPreferences 的现代键值对存储方案
val Context.dataStore by preferencesDataStore(name = "settings")

// 设置数据存储类:封装 DataStore 的读写操作
class SettingsDataStore @Inject constructor(
    @ApplicationContext private val context: Context
) {
    private val dataStore = context.dataStore

    companion object {
        // 定义类型安全的偏好设置键
        val DARK_MODE = booleanPreferencesKey("dark_mode")    // 深色模式开关
        val USER_NAME = stringPreferencesKey("user_name")     // 用户名
    }

    // 读取深色模式设置(返回 Flow,数据变化时自动通知)
    val darkMode: Flow<Boolean> = dataStore.data
        .map { preferences -> preferences[DARK_MODE] ?: false }

    // 保存深色模式设置(挂起函数,在协程中调用)
    suspend fun setDarkMode(enabled: Boolean) {
        dataStore.edit { preferences ->
            preferences[DARK_MODE] = enabled
        }
    }
}

// Proto DataStore:基于 Protocol Buffers 的类型安全存储方案
object UserSerializer : Serializer<User> {
    override val defaultValue: User = User.getDefaultInstance()  // 默认值

    // 从输入流反序列化 User 对象
    override suspend fun readFrom(input: InputStream): User {
        return User.parseFrom(input)
    }

    // 将 User 对象序列化写入输出流
    override suspend fun writeTo(t: User, output: OutputStream) {
        t.writeTo(output)
    }
}

// 创建 Proto DataStore 实例,数据存储在 user.pb 文件中
val Context.userDataStore by dataStore(
    fileName = "user.pb",
    serializer = UserSerializer
)

4. 数据缓存策略

4.1 Repository缓存实现

Kotlin
// 带缓存的 Repository 实现:网络优先 + 本地回退策略
class CachedUserRepository @Inject constructor(
    private val userApi: UserApi,          // 网络数据源
    private val userDao: UserDao           // 本地数据源
) : UserRepository {

    // 获取单个用户:先尝试网络,失败后回退到本地缓存
    override suspend fun getUser(userId: String): Result<User> {
        return try {  // try/catch捕获异常
            // 第1步:从网络获取最新数据
            val remoteUser = userApi.getUser(userId)

            // 第2步:将网络数据存入本地数据库(更新缓存)
            userDao.insertUser(remoteUser.toEntity())

            Result.success(remoteUser.toDomain())
        } catch (e: Exception) {
            // 第3步:网络失败时,尝试从本地缓存读取
            userDao.getUserById(userId)?.let {
                Result.success(it.toDomain())
            } ?: Result.failure(e)    // 本地也没有则返回失败
        }
    }

    // 获取用户列表 Flow:直接观察本地数据库变化
    override fun getUsersFlow(): Flow<List<User>> {
        return userDao.getUsersFlow()
            .map { entities -> entities.map { it.toDomain() } }
    }

    // 强制刷新:从网络拉取最新数据并更新本地缓存
    suspend fun refreshUsers() {
        try {
            val remoteUsers = userApi.getUsers(page = 1, limit = 100)
            userDao.deleteAllUsers()       // 清空旧数据
            remoteUsers.forEach {
                userDao.insertUser(it.toEntity())  // 批量插入新数据
            }
        } catch (e: Exception) {
            // 刷新失败,保留本地缓存数据,保证离线可用
        }
    }
}

5. 图片加载

5.1 Coil配置与使用

Kotlin
// 依赖配置
implementation("io.coil-kt:coil-compose:2.7.0")

// Coil 基本使用:异步加载网络图片
@Composable
fun ImageExample() {
    AsyncImage(
        model = "https://example.com/image.jpg",    // 图片 URL
        contentDescription = "Description",           // 无障碍描述
        modifier = Modifier.size(100.dp),             // 图片尺寸
        contentScale = ContentScale.Crop,             // 裁剪模式
        placeholder = painterResource(R.drawable.placeholder),  // 加载中占位图
        error = painterResource(R.drawable.error)     // 加载失败图
    )
}

// Coil 高级配置:自定义内存缓存和磁盘缓存
@Composable
fun AdvancedImageExample() {
    val context = LocalContext.current
    val imageLoader = ImageLoader.Builder(context)
        .crossfade(true)                              // 启用淡入淡出动画
        .memoryCache {
            // 内存缓存:占用应用可用内存的 25%
            MemoryCache.Builder(context)
                .maxSizePercent(0.25)
                .build()
        }
        .diskCache {
            // 磁盘缓存:最大 50MB,存储在应用缓存目录
            DiskCache.Builder()
                .directory(context.cacheDir.resolve("image_cache"))
                .maxSizeBytes(50 * 1024 * 1024) // 50MB
                .build()
        }
        .build()

    AsyncImage(
        model = ImageRequest.Builder(context)
            .data("https://example.com/image.jpg")
            .crossfade(true)                          // 淡入效果
            .placeholder(R.drawable.placeholder)       // 加载中占位图
            .error(R.drawable.error)                   // 加载错误图
            .transformations(CircleCropTransformation())  // 圆形裁剪变换
            .build(),
        contentDescription = null,
        imageLoader = imageLoader                      // 使用自定义 ImageLoader
    )
}

6. 实践练习

练习1:网络数据展示

任务:实现一个展示GitHub用户信息的应用

要求: - 使用Retrofit获取用户数据 - 使用Room缓存数据 - 实现下拉刷新 - 处理网络错误

练习2:离线优先应用

任务:实现一个支持离线使用的笔记应用

要求: - 本地数据使用Room存储 - 网络同步使用WorkManager - 冲突解决策略 - 同步状态显示


本章小结

核心要点

  1. Retrofit是Android网络请求的标准方案
  2. Room提供类型安全的SQLite访问
  3. DataStore替代SharedPreferences,支持类型安全
  4. 缓存策略提升用户体验,减少网络请求
  5. Coil是Kotlin友好的图片加载库

下一步

完成本章学习后,请进入第07章:状态管理与性能优化


本章完成时间:预计5-7天