第06章 数据处理与API集成¶
学习目标:掌握网络请求、数据解析、本地存储和缓存策略。
预计学习时间:5-7天 实践时间:2-3天
目录¶
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 - 冲突解决策略 - 同步状态显示
本章小结¶
核心要点¶
- Retrofit是Android网络请求的标准方案
- Room提供类型安全的SQLite访问
- DataStore替代SharedPreferences,支持类型安全
- 缓存策略提升用户体验,减少网络请求
- Coil是Kotlin友好的图片加载库
下一步¶
完成本章学习后,请进入第07章:状态管理与性能优化。
本章完成时间:预计5-7天