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

在这里插入图片描述

文章目录

    • 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. 总结

在这里插入图片描述

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 和 LiveDataimplementation "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.Entity
import androidx.room.PrimaryKey
import 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.LiveData
import androidx.room.*
import com.yourpackage.model.Note@Dao
interface NoteDao {@Insert(onConflict = OnConflictStrategy.REPLACE)suspend fun insertNote(note: Note): Long@Updatesuspend fun updateNote(note: Note)@Deletesuspend 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.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.yourpackage.dao.NoteDao
import com.yourpackage.model.Note@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {abstract fun noteDao(): NoteDaocompanion object {@Volatileprivate var INSTANCE: AppDatabase? = nullfun getDatabase(context: Context): AppDatabase {return INSTANCE ?: synchronized(this) {val instance = Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"notes_database").fallbackToDestructiveMigration() // 数据库升级策略,简单应用可以这样设置.build()INSTANCE = instanceinstance}}}
}

4. 创建 Repository

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

import androidx.lifecycle.LiveData
import com.yourpackage.dao.NoteDao
import com.yourpackage.model.Note
import kotlinx.coroutines.Dispatchers
import 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.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.yourpackage.model.Note
import com.yourpackage.repository.NoteRepository
import kotlinx.coroutines.launchclass NoteViewModel(private val repository: NoteRepository) : ViewModel() {val allNotes = repository.allNotesfun 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
<?xml version="1.0" encoding="utf-8"?>
<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.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:theme="@style/Theme.SQLiteDemo.AppBarOverlay"><androidx.appcompat.widget.Toolbarandroid: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.TextInputLayoutandroid: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.TextInputEditTextandroid: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.RecyclerViewandroid: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.FloatingActionButtonandroid: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.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.inputmethod.EditorInfo
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.yourpackage.R
import com.yourpackage.adapter.NotesAdapter
import com.yourpackage.databinding.ActivityNotesListBinding
import com.yourpackage.model.Note
import com.yourpackage.viewmodel.NoteViewModel
import com.yourpackage.viewmodel.NoteViewModelFactoryclass NotesListActivity : AppCompatActivity() {private lateinit var binding: ActivityNotesListBindingprivate lateinit var notesAdapter: NotesAdapterprivate 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 = notesAdaptersetHasFixedSize(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
<?xml version="1.0" encoding="utf-8"?>
<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.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:theme="@style/Theme.SQLiteDemo.AppBarOverlay"><androidx.appcompat.widget.Toolbarandroid: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.NestedScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"app:layout_behavior="@string/appbar_scrolling_view_behavior"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="16dp"><com.google.android.material.textfield.TextInputLayoutandroid: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.TextInputEditTextandroid: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.TextInputLayoutandroid: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.TextInputEditTextandroid: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.FloatingActionButtonandroid: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.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import com.yourpackage.R
import com.yourpackage.databinding.ActivityNoteDetailBinding
import com.yourpackage.model.Note
import com.yourpackage.viewmodel.NoteViewModel
import com.yourpackage.viewmodel.NoteViewModelFactory
import java.util.*class NoteDetailActivity : AppCompatActivity() {companion object {const val EXTRA_NOTE_ID = "extra_note_id"}private lateinit var binding: ActivityNoteDetailBindingprivate val viewModel: NoteViewModel by viewModels {NoteViewModelFactory((application as NotesApplication).repository)}private var noteId: Long = -1Lprivate var isNewNote = trueoverride 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 == -1Lif (!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() ?: falseval contentValid = binding.contentInput.text?.isNotBlank() ?: falsebinding.titleLayout.error = if (!titleValid) getString(R.string.title_required) else nullbinding.contentLayout.error = if (!contentValid) getString(R.string.content_required) else nullreturn titleValid && contentValid}private fun saveNote() {if (!validateInputs()) returnval 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.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.yourpackage.R
import com.yourpackage.databinding.ItemNoteBinding
import com.yourpackage.model.Note
import java.text.SimpleDateFormat
import 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.titlenoteContent.text = note.contentval 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

<?xml version="1.0" encoding="utf-8"?>
<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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="16dp"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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.Application
import com.yourpackage.database.AppDatabase
import com.yourpackage.repository.NoteRepositoryclass NotesApplication : Application() {val database by lazy { AppDatabase.getDatabase(this) }val repository by lazy { NoteRepository(database.noteDao()) }
}

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

<applicationandroid: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 文件:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><itemandroid: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. 使用事务:对于批量操作,使用事务可以显著提高性能:
@Dao
interface NoteDao {@Transactionsuspend 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 数据库的强大功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/77577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue基础(7)_计算属性

计算属性(computed) 一、使用方式&#xff1a; 1.定义计算属性&#xff1a; 在Vue组件中&#xff0c;通过在 computed 对象中定义计算属性名称及对应的计算函数来创建计算属性。计算函数会返回计算属性的值。 2.在模板中使用计算属性&#xff1a; 在Vue的模板中&#xff0c;您…

辛格迪客户案例 | 华道生物细胞治疗生产及追溯项目(CGTS)

01 华道&#xff08;上海&#xff09;生物医药有限公司&#xff1a;细胞治疗领域的创新先锋 华道&#xff08;上海&#xff09;生物医药有限公司&#xff08;以下简称“华道生物”&#xff09;是一家专注于细胞治疗技术研发与应用的创新型企业&#xff0c;尤其在CAR-T细胞免疫…

[26] cuda 应用之 nppi 实现图像格式转换

[26] cuda 应用之 nppi 实现图像格式转换 讲述 nppi 接口定义通过nppi实现 bayer 格式转rgb格式官网参考信息:http://gwmodel.whu.edu.cn/docs/CUDA/npp/group__image__color__debayer.html#details1. 接口定义 官网关于转换的原理是这么写的: Grayscale Color Filter Array …

2025“钉耙编程”中国大学生算法设计春季联赛(8)10031007

题目的意思很好理解找从最左边到最右边最短路&#xff08;BFS&#xff09; #include <bits/stdc.h> using namespace std; int a[510][510]; // 存储网格中每个位置是否有障碍&#xff08;1表示有障碍&#xff0c;0表示无障碍&#xff09; int v[510][510]; // 记录每…

【Linux】第十一章 管理网络

目录 1.TCP/IP网络模型 物理层&#xff08;Physical&#xff09; 数据链路层&#xff08;Date Link&#xff09; 网络层&#xff08;Internet&#xff09; 传输层&#xff08;Transport&#xff09; 应用层&#xff08;Application&#xff09; 2. 对于 IPv4 地址&#…

python_股票月数据趋势判断

目录 前置 代码 视频&月数据 前置 1 A股月数据趋势大致判断&#xff0c;做一个粗略的筛选 2 逻辑&#xff1a; 1&#xff09;取最近一次历史最高点 2&#xff09;以1&#xff09;中最高点为分界点&#xff0c;只看右侧数据&#xff0c;取最近一次最低点 3&#xf…

Python PyAutoGUI库【GUI 自动化库】深度解析与实战指南

一、核心工作原理 底层驱动机制&#xff1a; 通过操作系统原生API模拟输入使用ctypes库调用Windows API/Mac Cocoa/Xlib屏幕操作依赖Pillow库进行图像处理 事件模拟流程&#xff1a; #mermaid-svg-1CGDRNzFNEffhvSa {font-family:"trebuchet ms",verdana,arial,sans…

Spring框架allow-bean-definition-overriding详细解释

Spring框架中&#xff0c;allow-bean-definition-overriding 是一个控制是否允许覆盖同名Bean定义的配置属性。以下是详细说明&#xff1a; ​1. 作用​ ​允许/禁止Bean定义覆盖​&#xff1a;当Spring容器中检测到多个同名的Bean定义时&#xff0c;此配置决定是否允许后续的…

机器人抓取位姿检测——GRCN训练及测试教程(Pytorch)

机器人抓取位姿检测——GRCN训练及测试教程(Pytorch) 这篇文章主要介绍了2020年IROS提出的一种名为GRCN的检测模型,给出了代码各部分的说明,并给出windows系统下可以直接复现的完整代码,包含Cornell数据集。 模型结构图 github源码地址:https://github.com/skumra/robo…

在web应用后端接入内容审核——以腾讯云音频审核为例(Go语言示例)

腾讯云对象存储数据万象&#xff08;Cloud Infinite&#xff0c;CI&#xff09;为用户提供图片、视频、语音、文本等文件的内容安全智能审核服务&#xff0c;帮助用户有效识别涉黄、违法违规和广告审核&#xff0c;规避运营风险。本文以音频审核为例给出go语言示例代码与相应结…

GraphRAG知识库概要设计展望

最近研究了一下GraphRAG&#xff0c;写了一个文档转换工具还有图可视化工具&#xff0c;结合langchain构建RAG经验&#xff0c;还有以前的数据平台&#xff0c;做了一个知识库概要设计&#xff0c;具体应用欢迎留言探讨。 一、GraphRAG整体概述 GraphRAG图基检索增强生成&…

Android Studio 日志系统详解

文章目录 一、Android 日志系统基础1. Log 类2. 日志级别 二、Android Studio 中的 Logcat1. 打开 Logcat2. Logcat 界面组成3. 常用 Logcat 命令 三、高级日志技巧1. 自定义日志工具类2. 打印方法调用栈3. 打印长日志4. JSON 和 XML 格式化输出 四、Logcat 高级功能1. 自定义日…

深度对比:Objective-C与Swift的RunTime机制与底层原理

1. RunTime简介 RunTime&#xff08;运行时&#xff09;是指程序在运行过程中动态管理类型、对象、方法等的机制。Objective-C 和 Swift 都拥有自己的运行时系统&#xff0c;但设计理念和实现方式有很大不同。理解 RunTime 的底层原理&#xff0c;是掌握 iOS 高级开发的关键。…

使用手机录制rosbag包

文章目录 简介录制工具录制步骤录制设置设置IMU录制频率设置相机分辨率拍照模式录制模式数据制作获取数据数据转为rosbag查看rosbag简介 ROS数据包(rosbag)是ROS系统中用于记录和回放传感器数据的重要工具,通常用于算法调试、系统测试和数据采集。传统上,rosbag依赖于ROS环…

浅谈PCB传输线(一)

前言&#xff1a;浅谈传输线的类型&#xff0c;以及传输线的一些行为特性。 1.传输线的种类 2.互连线被视为传输线的场景 3.传输线的行为特性*** 1.传输线的种类 PCB 中的信号传输线通常有两种基本类型: 微带线和带状线。此外&#xff0c;还有第三种类型–共面线(没有参考平面…

【angular19】入门基础教程(一):项目的搭建与启动

angular现在发展的越来越能完善了&#xff0c;在vue和react的强势竞争下&#xff0c;它迎来了自己的巨大变革。项目工程化越来越好&#xff0c;也开始拥抱了vite这种高效的构建方式。所以&#xff0c;我们有必要来学习这么一个框架了。 项目实现效果 nodejs环境 Node.js - v^…

在前端应用领域驱动设计(DDD):必要性、挑战与实践指南

引言 领域驱动设计&#xff08;Domain-Driven Design&#xff0c;简称 DDD&#xff09;起源于后端复杂业务系统建模领域&#xff0c;是 Eric Evans 在 2003 年提出的一套理论体系。近年来&#xff0c;随着前端工程化与业务复杂度的持续提升&#xff0c;"前端也要 DDD&quo…

一文了解 模型上下文协议(MCP)

MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由Anthropic公司于2024年11月推出的一项开放标准协议&#xff0c;旨在解决大型语言模型&#xff08;LLM&#xff09;与外部数据源和工具之间的通信问题。其核心目标是通过提供一个标准化的接口&…

面向全球的行业开源情报体系建设方法论——以易海聚实战经验为例

在全球数字化转型加速的背景下&#xff0c;如何精准锁定目标领域的关键信息源&#xff0c;构建可持续迭代的情报网络&#xff0c;已成为企业战略决策的核心能力。深圳易海聚信息技术有限公司&#xff08;以下简称“易海聚”&#xff09;深耕开源情报领域十余年&#xff0c;其自…

UDP协议详解+代码演示

1、UDP协议基础 1. UDP是什么&#xff1f; UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是传输层的核心协议之一&#xff0c;与TCP并列。它的主要特点是&#xff1a;​​​​ 无连接&#xff1a;通信前不需要建立连接&#xff08;知道对端的…