5.14
从网络请求到数据持久化的全流程实践
在完成 TodoList 应用的基础架构后,我们需要为其添加网络数据同步和本地持久化功能。本文将结合 Java 安卓开发中的网络请求库与 Room 数据库,实现一个完整的 "云同步 TodoList" 应用,探讨如何在安卓环境中处理异步数据交互与离线缓存策略。
一、网络请求实战:Retrofit 与协程的完美结合
Android 开发中,Retrofit是最主流的网络请求库,其设计思想与 Java 后端的 Feign 客户端高度相似:
// 定义API接口(类似Feign接口)
interface TodoApi {
@GET("todos")
Call<List
@POST("todos")
Call<TodoItem> addTodo(@Body TodoItem todo);
@PUT("todos/{id}")
Call<TodoItem> updateTodo(@Path("id") String id, @Body TodoItem todo);
@DELETE("todos/{id}")
Call<Void> deleteTodo(@Path("id") String id);
}
// 配置Retrofit客户端(类似Java的RestTemplate配置)
class ApiClient {
private val baseUrl = "https://jsonplaceholder.typicode.com/"
private lateinit var api: TodoApi
fun getApi(): TodoApi {
if (!::api.isInitialized) {
val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
api = retrofit.create(TodoApi::class.java)
}
return api
}
}
// 在ViewModel中使用Retrofit(结合协程处理异步)
class TodoViewModel(application: Application) : AndroidViewModel(application) {
private val repository = TodoRepository(TodoDatabase.getDatabase(application).todoDao())
val todos: LiveData<List
private val api = ApiClient().getApi()
fun syncWithServer() {
// 使用协程避免阻塞主线程(类似Java的CompletableFuture)
viewModelScope.launch {
try {
// 先获取远程数据
val response = api.getTodos().execute()
if (response.isSuccessful && response.body() != null) {
// 清空本地数据并插入远程数据
repository.clearAll()
repository.insertAll(response.body()!!)
}
} catch (e: Exception) {
// 网络异常时不做处理,使用本地缓存
Log.e("TodoViewModel", "Sync error: ${e.message}")
}
}
}
}
二、数据持久化:Room 数据库与缓存策略
Android 的Room库是基于 SQLite 的 ORM 框架,设计思想类似 Java 的 JPA/Hibernate:
// 定义数据实体(类似JPA的Entity)
@Entity(tableName = "todos")
data class TodoItem(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val title: String,
val description: String = "",
val isCompleted: Boolean = false,
val serverId: String? = null, // 远程服务器ID,用于同步
val createTime: Long = System.currentTimeMillis(),
val lastUpdateTime: Long = System.currentTimeMillis()
)
// 定义DAO接口(类似JPA的Repository)
@Dao
interface TodoDao {
@Query("SELECT * FROM todos ORDER BY createTime DESC")
fun getAll(): LiveData<List
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(todo: TodoItem): Long
@Insert
suspend fun insertAll(todos: List<TodoItem>)
@Update
suspend fun update(todo: TodoItem)
@Delete
suspend fun delete(todo: TodoItem)
@Query("DELETE FROM todos")
suspend fun clearAll()
}
// 定义数据库类(类似JPA的EntityManagerFactory)
@Database(entities = [TodoItem::class], version = 1)
abstract class TodoDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao
companion object {
@Volatile
private var instance: TodoDatabase? = null
fun getDatabase(context: Context): TodoDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): TodoDatabase {
return Room.databaseBuilder(
context.applicationContext,
TodoDatabase::class.java,
"todo_database"
)
.fallbackToDestructiveMigration() // 简化版本升级,实际开发应使用Migration
.build()
}
}
}
// 缓存策略实现(本地优先+定期同步)
class TodoRepository(private val todoDao: TodoDao) {
val allTodos: LiveData<List
suspend fun syncWithServer() {
// 1. 先获取本地数据展示(本地优先)
// 2. 异步获取远程数据并更新本地
// 省略具体实现,参考ViewModel中的syncWithServer方法
}
}
三、离线缓存策略:安卓开发中的数据一致性方案
在移动应用中,网络不稳定是常态,需设计合理的离线缓存策略:
本地优先模式
界面优先展示本地 Room 数据库中的数据,避免空白页面(类似 Java 后端的 Cache-Aside 模式)。
在ViewModel中使用LiveData监听本地数据变化,确保 UI 实时更新。
延迟同步机制
当网络不可用时,将本地修改暂存至 Room,待网络恢复后自动同步(类似 Java 的消息队列重试机制)。
使用WorkManager实现后台同步任务,即使应用退出也能保证数据最终一致性。
冲突解决策略
为每条数据添加lastUpdateTime时间戳,当本地与远程数据冲突时,以最新更新的版本为准(类似 Java 的乐观锁)。
浙公网安备 33010602011771号