> 技术文档 > Android Studio 中使用 SQLite 数据库开发完整指南(Kotlin版本)_kotlin sqlite

Android Studio 中使用 SQLite 数据库开发完整指南(Kotlin版本)_kotlin sqlite

Android Studio 中使用 SQLite 数据库开发完整指南(Kotlin版本)_kotlin sqlite

文章目录

    • 1. 项目准备
      • 1.1 创建新项目
      • 1.2 添加必要依赖
    • 2. 数据库设计
    • 3. 实现数据库
      • 3.1 创建实体类 (Entity)
      • 3.2 创建数据访问对象 (DAO)
      • 3.3 创建数据库类
    • 4. 创建 Repository
    • 5. 创建 ViewModel
    • 6. 实现 UI 层
      • 6.1 创建笔记列表 Activity
        • activity_notes_list.xml
        • NotesListActivity.kt
      • 6.2 创建笔记详情 Activity
        • activity_note_detail.xml
        • NoteDetailActivity.kt
      • 6.3 创建 RecyclerView Adapter
      • 6.4 创建 Application 类
    • 7. 添加菜单资源
    • 8. 添加字符串资源
    • 9. 添加图标资源
    • 10. 运行和测试应用
    • 11. 数据库调试技巧
      • 11.1 查看数据库内容
      • 11.2 使用 Stetho 进行调试
    • 12. 数据库迁移
      • 12.1 修改实体类
      • 12.2 更新数据库版本
      • 12.3 添加迁移策略
    • 13. 性能优化建议
    • 14. 完整项目结构
    • 15. 总结

Android Studio 中使用 SQLite 数据库开发完整指南(Kotlin版本)_kotlin sqlite

1. 项目准备

1.1 创建新项目

  1. 打开 Android Studio
  2. 选择 “Start a new Android Studio project”
  3. 选择 “Empty Activity” 模板
  4. 设置项目名称(例如 “SQLiteDemo”)
  5. 选择语言(Kotlin 或 Java,本教程以 Kotlin 为例)
  6. 设置最低 API 级别(建议 API 21 或更高)
  7. 点击 “Finish” 完成项目创建

1.2 添加必要依赖

确保 build.gradle (Module: app) 中包含以下依赖:

dependencies { implementation \'androidx.core:core-ktx:1.7.0\' implementation \'androidx.appcompat:appcompat:1.4.1\' implementation \'com.google.android.material:material:1.5.0\' implementation \'androidx.constraintlayout:constraintlayout:2.1.3\' // Room 数据库(SQLite 的抽象层) implementation \"androidx.room:room-runtime:2.4.2\" implementation \"androidx.room:room-ktx:2.4.2\" kapt \"androidx.room:room-compiler:2.4.2\" // 协程支持 implementation \'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0\' // ViewModel 和 LiveData implementation \"androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1\" implementation \"androidx.lifecycle:lifecycle-livedata-ktx:2.4.1\" testImplementation \'junit:junit:4.13.2\' androidTestImplementation \'androidx.test.ext:junit:1.1.3\' androidTestImplementation \'androidx.test.espresso:espresso-core:3.4.0\'}

点击 “Sync Now” 同步项目。

2. 数据库设计

假设我们要创建一个简单的笔记应用,包含以下数据表:

  • notes 表:
    • id: 主键,自增
    • title: 笔记标题
    • content: 笔记内容
    • created_at: 创建时间
    • updated_at: 更新时间

3. 实现数据库

3.1 创建实体类 (Entity)

com.yourpackage.model 包下创建 Note.kt 文件

import androidx.room.Entityimport androidx.room.PrimaryKeyimport java.util.*@Entity(tableName = \"notes\")data class Note( @PrimaryKey(autoGenerate = true) val id: Long = 0, var title: String, var content: String, val created_at: Date = Date(), var updated_at: Date = Date())

3.2 创建数据访问对象 (DAO)

com.yourpackage.dao 包下创建 NoteDao.kt 文件:

import androidx.lifecycle.LiveDataimport androidx.room.*import com.yourpackage.model.Note@Daointerface NoteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertNote(note: Note): Long @Update suspend fun updateNote(note: Note) @Delete suspend fun deleteNote(note: Note) @Query(\"SELECT * FROM notes ORDER BY updated_at DESC\") fun getAllNotes(): LiveData<List<Note>> @Query(\"SELECT * FROM notes WHERE id = :noteId\") suspend fun getNoteById(noteId: Long): Note? @Query(\"SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query ORDER BY updated_at DESC\") fun searchNotes(query: String): LiveData<List<Note>>}

3.3 创建数据库类

com.yourpackage.database 包下创建 AppDatabase.kt 文件:

import android.content.Contextimport androidx.room.Databaseimport androidx.room.Roomimport androidx.room.RoomDatabaseimport com.yourpackage.dao.NoteDaoimport com.yourpackage.model.Note@Database(entities = [Note::class], version = 1, exportSchema = false)abstract class AppDatabase : RoomDatabase() { abstract fun noteDao(): NoteDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder(  context.applicationContext,  AppDatabase::class.java,  \"notes_database\" )  .fallbackToDestructiveMigration() // 数据库升级策略,简单应用可以这样设置  .build() INSTANCE = instance instance } } }}

4. 创建 Repository

com.yourpackage.repository 包下创建 NoteRepository.kt 文件:

import androidx.lifecycle.LiveDataimport com.yourpackage.dao.NoteDaoimport com.yourpackage.model.Noteimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContextclass NoteRepository(private val noteDao: NoteDao) { val allNotes: LiveData<List<Note>> = noteDao.getAllNotes() suspend fun insert(note: Note): Long { return withContext(Dispatchers.IO) { noteDao.insertNote(note) } } suspend fun update(note: Note) { withContext(Dispatchers.IO) { note.updated_at = Date() noteDao.updateNote(note) } } suspend fun delete(note: Note) { withContext(Dispatchers.IO) { noteDao.deleteNote(note) } } suspend fun getNoteById(id: Long): Note? { return withContext(Dispatchers.IO) { noteDao.getNoteById(id) } } fun searchNotes(query: String): LiveData<List<Note>> { return noteDao.searchNotes(\"%$query%\") }}

5. 创建 ViewModel

com.yourpackage.viewmodel 包下创建 NoteViewModel.kt 文件:

import androidx.lifecycle.ViewModelimport androidx.lifecycle.ViewModelProviderimport androidx.lifecycle.asLiveDataimport androidx.lifecycle.viewModelScopeimport com.yourpackage.model.Noteimport com.yourpackage.repository.NoteRepositoryimport kotlinx.coroutines.launchclass NoteViewModel(private val repository: NoteRepository) : ViewModel() { val allNotes = repository.allNotes fun insert(note: Note) = viewModelScope.launch { repository.insert(note) } fun update(note: Note) = viewModelScope.launch { repository.update(note) } fun delete(note: Note) = viewModelScope.launch { repository.delete(note) } fun getNoteById(id: Long) = viewModelScope.launch { repository.getNoteById(id) } fun searchNotes(query: String) = repository.searchNotes(query).asLiveData()}class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(NoteViewModel::class.java)) { @Suppress(\"UNCHECKED_CAST\") return NoteViewModel(repository) as T } throw IllegalArgumentException(\"Unknown ViewModel class\") }}

6. 实现 UI 层

6.1 创建笔记列表 Activity

创建 NotesListActivity.kt 和对应的布局文件 activity_notes_list.xml

activity_notes_list.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\" xmlns:tools=\"http://schemas.android.com/tools\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" tools:context=\".ui.NotesListActivity\"> <com.google.android.material.appbar.AppBarLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:theme=\"@style/Theme.SQLiteDemo.AppBarOverlay\"> <androidx.appcompat.widget.Toolbar android:id=\"@+id/toolbar\" android:layout_width=\"match_parent\" android:layout_height=\"?attr/actionBarSize\" android:background=\"?attr/colorPrimary\" app:popupTheme=\"@style/Theme.SQLiteDemo.PopupOverlay\" app:title=\"@string/app_name\" /> <com.google.android.material.textfield.TextInputLayout android:id=\"@+id/search_layout\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"> <com.google.android.material.textfield.TextInputEditText android:id=\"@+id/search_input\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:hint=\"@string/search_hint\" android:imeOptions=\"actionSearch\" android:inputType=\"text\" /> </com.google.android.material.textfield.TextInputLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id=\"@+id/notes_recycler_view\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" android:clipToPadding=\"false\" android:paddingBottom=\"72dp\" app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id=\"@+id/fab_add_note\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:layout_gravity=\"bottom|end\" android:layout_margin=\"16dp\" android:contentDescription=\"@string/add_note\" android:src=\"@drawable/ic_add\" app:backgroundTint=\"@color/purple_500\" app:tint=\"@android:color/white\" /></androidx.coordinatorlayout.widget.CoordinatorLayout>
NotesListActivity.kt
import android.content.Intentimport android.os.Bundleimport android.view.Menuimport android.view.MenuItemimport android.view.inputmethod.EditorInfoimport androidx.activity.viewModelsimport androidx.appcompat.app.AppCompatActivityimport androidx.recyclerview.widget.LinearLayoutManagerimport com.google.android.material.snackbar.Snackbarimport com.yourpackage.Rimport com.yourpackage.adapter.NotesAdapterimport com.yourpackage.databinding.ActivityNotesListBindingimport com.yourpackage.model.Noteimport com.yourpackage.viewmodel.NoteViewModelimport com.yourpackage.viewmodel.NoteViewModelFactoryclass NotesListActivity : AppCompatActivity() { private lateinit var binding: ActivityNotesListBinding private lateinit var notesAdapter: NotesAdapter private val viewModel: NoteViewModel by viewModels { NoteViewModelFactory((application as NotesApplication).repository) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityNotesListBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolbar) setupRecyclerView() setupSearch() setupFAB() observeNotes() } private fun setupRecyclerView() { notesAdapter = NotesAdapter { note -> // 点击笔记项时的操作 val intent = Intent(this, NoteDetailActivity::class.java).apply { putExtra(NoteDetailActivity.EXTRA_NOTE_ID, note.id) } startActivity(intent) } binding.notesRecyclerView.apply { layoutManager = LinearLayoutManager(this@NotesListActivity) adapter = notesAdapter setHasFixedSize(true) } } private fun setupSearch() { binding.searchInput.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_SEARCH) { val query = binding.searchInput.text.toString().trim() if (query.isNotEmpty()) {  viewModel.searchNotes(query).observe(this) { notes -> notesAdapter.submitList(notes)  } } else {  observeNotes() // 如果查询为空,返回所有笔记 } true } else { false } } } private fun setupFAB() { binding.fabAddNote.setOnClickListener { val intent = Intent(this, NoteDetailActivity::class.java) startActivity(intent) } } private fun observeNotes() { viewModel.allNotes.observe(this) { notes -> notesAdapter.submitList(notes) } } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_delete_all -> { deleteAllNotes() true } else -> super.onOptionsItemSelected(item) } } private fun deleteAllNotes() { viewModel.allNotes.value?.let { notes -> if (notes.isNotEmpty()) { for (note in notes) {  viewModel.delete(note) } Snackbar.make(binding.root, \"All notes deleted\", Snackbar.LENGTH_SHORT).show() } } }}

6.2 创建笔记详情 Activity

创建 NoteDetailActivity.kt 和对应的布局文件 activity_note_detail.xml

activity_note_detail.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\" xmlns:tools=\"http://schemas.android.com/tools\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" tools:context=\".ui.NoteDetailActivity\"> <com.google.android.material.appbar.AppBarLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:theme=\"@style/Theme.SQLiteDemo.AppBarOverlay\"> <androidx.appcompat.widget.Toolbar android:id=\"@+id/toolbar\" android:layout_width=\"match_parent\" android:layout_height=\"?attr/actionBarSize\" android:background=\"?attr/colorPrimary\" app:popupTheme=\"@style/Theme.SQLiteDemo.PopupOverlay\" /> </com.google.android.material.appbar.AppBarLayout> <androidx.core.widget.NestedScrollView android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"> <LinearLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:orientation=\"vertical\" android:padding=\"16dp\"> <com.google.android.material.textfield.TextInputLayout android:id=\"@+id/title_layout\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"> <com.google.android.material.textfield.TextInputEditText  android:id=\"@+id/title_input\"  android:layout_width=\"match_parent\"  android:layout_height=\"wrap_content\"  android:hint=\"@string/title_hint\"  android:inputType=\"textCapSentences|textAutoCorrect\"  android:maxLines=\"1\" /> </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout android:id=\"@+id/content_layout\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:layout_marginTop=\"16dp\" style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox\"> <com.google.android.material.textfield.TextInputEditText  android:id=\"@+id/content_input\"  android:layout_width=\"match_parent\"  android:layout_height=\"wrap_content\"  android:hint=\"@string/content_hint\"  android:inputType=\"textMultiLine|textCapSentences|textAutoCorrect\"  android:minLines=\"5\"  android:gravity=\"top\" /> </com.google.android.material.textfield.TextInputLayout> </LinearLayout> </androidx.core.widget.NestedScrollView> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id=\"@+id/fab_save\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:layout_gravity=\"bottom|end\" android:layout_margin=\"16dp\" android:contentDescription=\"@string/save_note\" android:src=\"@drawable/ic_save\" app:backgroundTint=\"@color/purple_500\" app:tint=\"@android:color/white\" /></androidx.coordinatorlayout.widget.CoordinatorLayout>
NoteDetailActivity.kt
import android.os.Bundleimport android.text.Editableimport android.text.TextWatcherimport android.view.MenuItemimport androidx.activity.viewModelsimport androidx.appcompat.app.AppCompatActivityimport com.google.android.material.snackbar.Snackbarimport com.yourpackage.Rimport com.yourpackage.databinding.ActivityNoteDetailBindingimport com.yourpackage.model.Noteimport com.yourpackage.viewmodel.NoteViewModelimport com.yourpackage.viewmodel.NoteViewModelFactoryimport java.util.*class NoteDetailActivity : AppCompatActivity() { companion object { const val EXTRA_NOTE_ID = \"extra_note_id\" } private lateinit var binding: ActivityNoteDetailBinding private val viewModel: NoteViewModel by viewModels { NoteViewModelFactory((application as NotesApplication).repository) } private var noteId: Long = -1L private var isNewNote = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityNoteDetailBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) noteId = intent.getLongExtra(EXTRA_NOTE_ID, -1L) isNewNote = noteId == -1L if (!isNewNote) { loadNote() } setupSaveButton() setupTextWatchers() } private fun loadNote() { viewModel.getNoteById(noteId) viewModel.allNotes.observe(this) { notes -> notes.find { it.id == noteId }?.let { note -> binding.titleInput.setText(note.title) binding.contentInput.setText(note.content) } } } private fun setupSaveButton() { binding.fabSave.setOnClickListener { saveNote() } } private fun setupTextWatchers() { binding.titleInput.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { validateInputs() } }) binding.contentInput.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { validateInputs() } }) } private fun validateInputs(): Boolean { val titleValid = binding.titleInput.text?.isNotBlank() ?: false val contentValid = binding.contentInput.text?.isNotBlank() ?: false binding.titleLayout.error = if (!titleValid) getString(R.string.title_required) else null binding.contentLayout.error = if (!contentValid) getString(R.string.content_required) else null return titleValid && contentValid } private fun saveNote() { if (!validateInputs()) return val title = binding.titleInput.text.toString() val content = binding.contentInput.text.toString() if (isNewNote) { val note = Note(title = title, content = content) viewModel.insert(note) Snackbar.make(binding.root, \"Note saved\", Snackbar.LENGTH_SHORT).show() finish() } else { viewModel.allNotes.value?.find { it.id == noteId }?.let { existingNote -> val updatedNote = existingNote.copy(  title = title,  content = content,  updated_at = Date() ) viewModel.update(updatedNote) Snackbar.make(binding.root, \"Note updated\", Snackbar.LENGTH_SHORT).show() finish() } } } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { onBackPressed() true } else -> super.onOptionsItemSelected(item) } }}

6.3 创建 RecyclerView Adapter

com.yourpackage.adapter 包下创建 NotesAdapter.kt 文件:

import android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport androidx.recyclerview.widget.DiffUtilimport androidx.recyclerview.widget.ListAdapterimport androidx.recyclerview.widget.RecyclerViewimport com.yourpackage.Rimport com.yourpackage.databinding.ItemNoteBindingimport com.yourpackage.model.Noteimport java.text.SimpleDateFormatimport java.util.*class NotesAdapter(private val onItemClick: (Note) -> Unit) : ListAdapter<Note, NotesAdapter.NoteViewHolder>(NoteDiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { val binding = ItemNoteBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return NoteViewHolder(binding, onItemClick) } override fun onBindViewHolder(holder: NoteViewHolder, position: Int) { holder.bind(getItem(position)) } class NoteViewHolder( private val binding: ItemNoteBinding, private val onItemClick: (Note) -> Unit ) : RecyclerView.ViewHolder(binding.root) { fun bind(note: Note) { binding.apply { noteTitle.text = note.title noteContent.text = note.content val dateFormat = SimpleDateFormat(\"MMM dd, yyyy - hh:mm a\", Locale.getDefault()) noteDate.text = dateFormat.format(note.updated_at) root.setOnClickListener {  onItemClick(note) } } } } private class NoteDiffCallback : DiffUtil.ItemCallback<Note>() { override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean { return oldItem == newItem } }}

创建对应的列表项布局文件 item_note.xml

<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:layout_margin=\"8dp\" app:cardCornerRadius=\"8dp\" app:cardElevation=\"4dp\"> <LinearLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:orientation=\"vertical\" android:padding=\"16dp\"> <TextView android:id=\"@+id/note_title\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:textAppearance=\"@style/TextAppearance.AppCompat.Headline\" android:textColor=\"@android:color/black\" /> <TextView android:id=\"@+id/note_content\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:layout_marginTop=\"8dp\" android:ellipsize=\"end\" android:maxLines=\"2\" android:textAppearance=\"@style/TextAppearance.AppCompat.Body1\" android:textColor=\"@android:color/darker_gray\" /> <TextView android:id=\"@+id/note_date\" android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\" android:layout_marginTop=\"8dp\" android:textAppearance=\"@style/TextAppearance.AppCompat.Caption\" android:textColor=\"@android:color/darker_gray\" /> </LinearLayout></com.google.android.material.card.MaterialCardView>

6.4 创建 Application 类

com.yourpackage 包下创建 NotesApplication.kt 文件:

import android.app.Applicationimport com.yourpackage.database.AppDatabaseimport com.yourpackage.repository.NoteRepositoryclass NotesApplication : Application() { val database by lazy { AppDatabase.getDatabase(this) } val repository by lazy { NoteRepository(database.noteDao()) }}

更新 AndroidManifest.xml 文件,添加 android:name 属性:

<application android:name=\".NotesApplication\" android:allowBackup=\"true\" android:icon=\"@mipmap/ic_launcher\" android:label=\"@string/app_name\" android:roundIcon=\"@mipmap/ic_launcher_round\" android:supportsRtl=\"true\" android:theme=\"@style/Theme.SQLiteDemo\"> </application>

7. 添加菜单资源

res/menu 目录下创建 menu_main.xml 文件:

<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\"> <item android:id=\"@+id/action_delete_all\" android:icon=\"@drawable/ic_delete\" android:title=\"@string/delete_all\" app:showAsAction=\"never\" /></menu>

8. 添加字符串资源

res/values/strings.xml 文件中添加以下字符串:

<resources> <string name=\"app_name\">SQLite Notes</string> <string name=\"title_hint\">Title</string> <string name=\"content_hint\">Content</string> <string name=\"search_hint\">Search notes...</string> <string name=\"add_note\">Add new note</string> <string name=\"save_note\">Save note</string> <string name=\"delete_all\">Delete all notes</string> <string name=\"title_required\">Title is required</string> <string name=\"content_required\">Content is required</string></resources>

9. 添加图标资源

确保在 res/drawable 目录下有以下矢量图标:

  • ic_add.xml (添加按钮图标)
  • ic_save.xml (保存按钮图标)
  • ic_delete.xml (删除按钮图标)

10. 运行和测试应用

现在,您可以运行应用程序并测试以下功能:

  1. 添加新笔记
  2. 查看笔记列表
  3. 编辑现有笔记
  4. 删除笔记
  5. 搜索笔记
  6. 删除所有笔记

11. 数据库调试技巧

11.1 查看数据库内容

  1. 在 Android Studio 中打开 “Device File Explorer” (View -> Tool Windows -> Device File Explorer)
  2. 导航到 /data/data/com.yourpackage/databases/
  3. 找到 notes_database 文件
  4. 右键点击并选择 “Save As” 将其保存到本地
  5. 使用 SQLite 浏览器工具(如 DB Browser for SQLite)打开该文件查看内容

11.2 使用 Stetho 进行调试

添加 Stetho 依赖到 build.gradle:

implementation \'com.facebook.stetho:stetho:1.6.0\'

NotesApplication.kt 中初始化 Stetho:

import com.facebook.stetho.Stethoclass NotesApplication : Application() { override fun onCreate() { super.onCreate() Stetho.initializeWithDefaults(this) } // 其他代码...}

运行应用后,在 Chrome 浏览器中访问 chrome://inspect 可以查看和调试数据库。

12. 数据库迁移

当您需要更改数据库结构时(例如添加新表或修改现有表),需要进行数据库迁移。

12.1 修改实体类

例如,我们要为 Note 添加一个 is_pinned 字段:

@Entity(tableName = \"notes\")data class Note( // 现有字段... var is_pinned: Boolean = false)

12.2 更新数据库版本

修改 AppDatabase.kt:

@Database(entities = [Note::class], version = 2, exportSchema = false)abstract class AppDatabase : RoomDatabase() { // ...}

12.3 添加迁移策略

val migration1to2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL(\"ALTER TABLE notes ADD COLUMN is_pinned INTEGER NOT NULL DEFAULT 0\") }}// 在 databaseBuilder 中添加迁移val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, \"notes_database\") .addMigrations(migration1to2) .build()

13. 性能优化建议

  1. 使用事务:对于批量操作,使用事务可以显著提高性能:
@Daointerface NoteDao { @Transaction suspend fun insertAll(notes: List<Note>) { notes.forEach { insertNote(it) } }}
  1. 索引优化:为常用查询字段添加索引:
@Entity(tableName = \"notes\", indices = [Index(value = [\"title\"], unique = false)])data class Note( // ...)
  1. 分页加载:对于大量数据,使用 Paging 库:
@Query(\"SELECT * FROM notes ORDER BY updated_at DESC\")fun getPagedNotes(): PagingSource<Int, Note>
  1. 避免在主线程操作数据库:始终确保数据库操作在后台线程执行。

14. 完整项目结构

最终项目结构应类似于:

com.yourpackage├── adapter│ └── NotesAdapter.kt├── dao│ └── NoteDao.kt├── database│ └── AppDatabase.kt├── model│ └── Note.kt├── repository│ └── NoteRepository.kt├── ui│ ├── NotesListActivity.kt│ └── NoteDetailActivity.kt├── viewmodel│ ├── NoteViewModel.kt│ └── NoteViewModelFactory.kt└── NotesApplication.kt

15. 总结

本指南详细介绍了在 Android Studio 中使用 SQLite 数据库的完整开发流程,包括:

  1. 设置项目和依赖
  2. 设计数据库结构
  3. 实现 Room 数据库组件(Entity, DAO, Database)
  4. 创建 Repository 层
  5. 实现 ViewModel
  6. 构建用户界面
  7. 添加数据库迁移支持
  8. 性能优化建议

通过遵循这些步骤,您可以构建一个功能完善、结构清晰的 Android 应用,充分利用 SQLite 数据库的强大功能。