Flutter 网络请求实战:Dio 封装 + 拦截器 + 数据解析

本地存储是 Android 应用开发的核心场景之一,Jetpack Room 作为 Google 推荐的 ORM 框架,简化了 SQLite 操作,避免原生 SQL 的繁琐与易错。本文将手把手教你封装一个生产级的 Room 数据库工具类,实现 数据库初始化、实体类映射、DAO 层封装、数据增删改查(CRUD)全功能,并结合 LiveData 实现数据观察,适配 Android 14 系统特性。文末附完整可运行代码,复制即可集成!

一、核心依赖与环境配置

app/build.gradle(Module 级别)中添加以下依赖,支持 Room 核心功能、协程异步操作及生命周期感知:

gradle

复制代码
dependencies {
    // Room 核心依赖
    implementation "androidx.room:room-runtime:2.6.1"
    annotationProcessor "androidx.room:room-compiler:2.6.1"
    // 协程支持(异步操作数据库)
    implementation "androidx.room:room-ktx:2.6.1"
    // LiveData 支持(数据观察)
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
    // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}

💡 提示:同步项目(Sync Now)后,Room 注解处理器会自动生效,支持数据库相关注解解析。

二、Room 三层架构设计

Room 遵循 MVC 思想,通过三层架构实现数据解耦,核心组件如下:

组件 作用 对应类 / 接口
实体类(Entity) 映射数据库表结构,定义字段与表属性 NoteEntity
数据访问对象(DAO) 定义数据库操作方法(CRUD),无需手写 SQL NoteDao
数据库实例(Database) 管理数据库版本、关联实体类与 DAO,提供单例实例 AppDatabase
工具类(Repository) 封装 DAO 操作,暴露统一 API,处理异步逻辑 NoteRepository

三、逐文件编写代码(附详细注释)

1. 实体类:NoteEntity(映射数据表)

定义笔记表结构,包含 id、内容、创建时间等字段,适配鸿蒙系统风格的数据存储需求:

kotlin

复制代码
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.Date

// 数据表名:note_table
@Entity(tableName = "note_table")
data class NoteEntity(
    @PrimaryKey(autoGenerate = true) // 自增主键
    val id: Long = 0,
    val content: String, // 笔记内容(非空)
    val createTime: Long = Date().time, // 创建时间(默认当前时间戳)
    val updateTime: Long = Date().time // 更新时间(默认当前时间戳)
)

💡 关键说明:

  • @Entity 注解标识该类为数据库表,tableName 指定表名
  • @PrimaryKey(autoGenerate = true) 实现 id 自动增长,避免手动管理主键
  • 时间戳采用 Long 类型存储,兼容不同系统时间格式

2. DAO 接口:NoteDao(定义数据库操作)

通过 Room 注解定义 CRUD 操作,无需编写原生 SQL,支持协程异步调用:

kotlin

复制代码
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

@Dao
interface NoteDao {
    // 插入笔记(冲突时替换)
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNote(note: NoteEntity)

    // 更新笔记
    @Update
    suspend fun updateNote(note: NoteEntity)

    // 删除单条笔记
    @Delete
    suspend fun deleteNote(note: NoteEntity)

    // 删除所有笔记
    @Query("DELETE FROM note_table")
    suspend fun deleteAllNotes()

    // 查询所有笔记(按更新时间倒序)
    @Query("SELECT * FROM note_table ORDER BY updateTime DESC")
    fun getAllNotes(): Flow<List<NoteEntity>> // Flow 支持数据观察,自动响应变化

    // 根据id查询笔记
    @Query("SELECT * FROM note_table WHERE id = :noteId LIMIT 1")
    suspend fun getNoteById(noteId: Long): NoteEntity?
}

✅ 优势说明:

  • 注解化操作:@Insert/@Update/@Delete 自动映射 SQL 操作
  • 异步支持:suspend 函数适配协程,避免主线程阻塞(Android 强制要求)
  • 数据观察:Flow<List<NoteEntity>> 可实时监听数据变化,自动刷新 UI

3. 数据库实例:AppDatabase(单例管理)

创建数据库单例,关联实体类与 DAO,处理数据库版本迁移(基础版采用销毁重建策略):

kotlin

复制代码
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context

// 数据库版本:1;关联的实体类:NoteEntity
@Database(entities = [NoteEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    // 暴露DAO接口实例
    abstract fun noteDao(): NoteDao

    companion object {
        // 单例模式:双重校验锁(线程安全)
        @Volatile
        private var INSTANCE: AppDatabase? = null

        // 获取数据库实例
        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext, // 应用上下文,避免内存泄漏
                    AppDatabase::class.java,
                    "note_database" // 数据库文件名:note_database.db
                )
                .fallbackToDestructiveMigration() // 版本升级时销毁旧数据库(适合开发初期)
                .build()
                INSTANCE = instance
                instance
            }
        }
    }
}

⚠️ 注意事项:

  • 必须使用 applicationContext 初始化,避免 Activity 上下文导致的内存泄漏
  • exportSchema = false 关闭数据库模式导出(生产环境建议开启,便于版本迁移)
  • 进阶提示:复杂项目可通过 Migration 类实现数据迁移,保留旧数据

4. 仓库层:NoteRepository(封装业务逻辑)

统一暴露数据库操作 API,隔离数据层与 UI 层,处理协程异步逻辑:

kotlin

复制代码
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class NoteRepository(private val noteDao: NoteDao) {
    // 观察所有笔记(对外暴露Flow,UI层可观察数据变化)
    val allNotes = noteDao.getAllNotes()

    // 插入笔记(切换到IO线程执行)
    suspend fun insertNote(note: NoteEntity) = withContext(Dispatchers.IO) {
        noteDao.insertNote(note)
    }

    // 更新笔记(带时间戳更新)
    suspend fun updateNote(note: NoteEntity) = withContext(Dispatchers.IO) {
        val updatedNote = note.copy(updateTime = System.currentTimeMillis())
        noteDao.updateNote(updatedNote)
    }

    // 删除笔记
    suspend fun deleteNote(note: NoteEntity) = withContext(Dispatchers.IO) {
        noteDao.deleteNote(note)
    }

    // 清空所有笔记
    suspend fun deleteAllNotes() = withContext(Dispatchers.IO) {
        noteDao.deleteAllNotes()
    }

    // 根据id查询笔记
    suspend fun getNoteById(noteId: Long): NoteEntity? = withContext(Dispatchers.IO) {
        noteDao.getNoteById(noteId)
    }
}

💡 设计思路:

  • 所有数据库操作切换到 Dispatchers.IO 线程,避免阻塞主线程
  • 对外隐藏 DAO 层细节,UI 层只需调用 Repository 方法,便于后续扩展(如增加网络缓存)
  • 自动更新 updateTime,确保数据排序准确性

5. 视图模型:NoteViewModel(连接 UI 与数据层)

结合 ViewModel 与 LiveData,管理 UI 相关数据,避免配置变更(如屏幕旋转)导致数据丢失:

kotlin

复制代码
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class NoteViewModel(private val repository: NoteRepository) : ViewModel() {
    // 观察所有笔记(UI层通过此LiveData获取数据)
    val allNotes = repository.allNotes

    // 插入笔记(ViewModelScope自动管理协程生命周期)
    fun insertNote(content: String) {
        viewModelScope.launch {
            val note = NoteEntity(content = content)
            repository.insertNote(note)
        }
    }

    // 更新笔记
    fun updateNote(note: NoteEntity) {
        viewModelScope.launch {
            repository.updateNote(note)
        }
    }

    // 删除笔记
    fun deleteNote(note: NoteEntity) {
        viewModelScope.launch {
            repository.deleteNote(note)
        }
    }

    // 清空所有笔记
    fun deleteAllNotes() {
        viewModelScope.launch {
            repository.deleteAllNotes()
        }
    }
}

// ViewModel工厂类:提供Repository实例
class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
            return NoteViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

四、UI 层调用示例(Activity/Fragment)

在 UI 层通过 ViewModel 操作数据库,实现笔记列表展示、添加、删除功能:

kotlin

复制代码
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private lateinit var noteViewModel: NoteViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 初始化RecyclerView(展示笔记列表)
        val adapter = NoteAdapter { note ->
            // 点击笔记项删除
            noteViewModel.deleteNote(note)
        }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        // 初始化ViewModel
        val database = AppDatabase.getInstance(application)
        val repository = NoteRepository(database.noteDao())
        noteViewModel = ViewModelProvider(
            this,
            NoteViewModelFactory(repository)
        )[NoteViewModel::class.java]

        // 观察笔记数据变化,自动刷新UI
        noteViewModel.allNotes.observe(this) { notes ->
            adapter.submitList(notes)
        }

        // 点击按钮添加笔记
        addNoteBtn.setOnClickListener {
            val content = noteInput.text.toString().trim()
            if (content.isNotEmpty()) {
                noteViewModel.insertNote(content)
                noteInput.text.clear() // 清空输入框
            } else {
                // 空内容提示(适配鸿蒙风格弹窗)
                android.widget.Toast.makeText(this, "请输入笔记内容!", android.widget.Toast.LENGTH_SHORT).show()
            }
        }
    }
}

五、核心适配与避坑指南

适配要点 实现方式
鸿蒙系统风格 弹窗提示采用系统原生 Toast,保持操作习惯一致性;数据存储路径遵循 Android 规范,兼容鸿蒙文件系统
主线程安全 所有数据库操作通过协程切换到 IO 线程,避免 ANR 异常
配置变更兼容 使用 ViewModel 存储数据,屏幕旋转时不丢失状态
数据观察 采用 Flow+LiveData,实现数据变化自动刷新 UI,减少手动回调
错误处理 基础版通过协程异常捕获(可扩展添加 try-catch 处理插入失败等场景)

六、功能测试用例

测试场景 操作步骤 预期结果
空内容添加 直接点击 "添加笔记" 按钮 弹出 "请输入笔记内容!" 提示,无数据插入
正常添加笔记 输入文本→点击添加 笔记列表实时刷新,显示新添加的笔记
删除笔记 点击列表项 对应笔记从列表中移除,数据库中数据删除
屏幕旋转 添加笔记后旋转屏幕 笔记列表保持不变,输入框为空
数据持久化 退出应用后重新打开 之前添加的笔记全部保留

七、结语

通过 Room + ViewModel + Repository 架构封装,实现了一套健壮、可维护的本地数据库方案,既适配鸿蒙系统操作习惯,又符合 Android 开发最佳实践。该方案支持数据观察、异步安全、配置变更兼容等核心特性,可直接应用于生产级项目,也可根据需求扩展搜索、分页、数据迁移等高级功能。

相关推荐
汤愈韬2 小时前
知识点3:动态目的NAT的配置总结
网络·网络协议·网络安全·security·huawei
CNRio3 小时前
第8章 网络安全应急响应
网络·安全·web安全
风掣长空3 小时前
Google Test (gtest) 新手完全指南:从入门到精通
运维·服务器·网络
子春一4 小时前
Flutter 构建系统深度解析:从 pubspec.yaml 到 release 包的全链路掌控
flutter
发光小北4 小时前
SG-PNh750-TCP-210(Profinet 从站转 Modbus TCP 网关)
网络·网络协议·tcp/ip
帅气马战的账号4 小时前
开源鸿蒙+Flutter:跨端开发的组件化重构与性能跃迁
flutter
QuantumLeap丶5 小时前
《Flutter全栈开发实战指南:从零到高级》- 23 -混合开发与WebView
android·flutter·ios
轻颂呀5 小时前
TCP协议
linux·网络·网络协议·tcp/ip
松涛和鸣5 小时前
25、数据结构:树与二叉树的概念、特性及递归实现
linux·开发语言·网络·数据结构·算法