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

打印 上一主题 下一主题

主题 1922|帖子 1922|积分 5766



  

1. 项目准备

1.1 创建新项目


  • 打开 Android Studio
  • 选择 “Start a new Android Studio project”
  • 选择 “Empty Activity” 模板
  • 设置项目名称(比方 “SQLiteDemo”)
  • 选择语言(Kotlin 或 Java,本教程以 Kotlin 为例)
  • 设置最低 API 级别(建议 API 21 或更高)
  • 点击 “Finish” 完成项目创建
1.2 添加必要依赖

确保 build.gradle (Module: app) 中包含以下依赖:
  1. dependencies {
  2.     implementation 'androidx.core:core-ktx:1.7.0'
  3.     implementation 'androidx.appcompat:appcompat:1.4.1'
  4.     implementation 'com.google.android.material:material:1.5.0'
  5.     implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
  6.    
  7.     // Room 数据库(SQLite 的抽象层)
  8.     implementation "androidx.room:room-runtime:2.4.2"
  9.     implementation "androidx.room:room-ktx:2.4.2"
  10.     kapt "androidx.room:room-compiler:2.4.2"
  11.    
  12.     // 协程支持
  13.     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
  14.    
  15.     // ViewModel 和 LiveData
  16.     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
  17.     implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
  18.    
  19.     testImplementation 'junit:junit:4.13.2'
  20.     androidTestImplementation 'androidx.test.ext:junit:1.1.3'
  21.     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  22. }
复制代码
点击 “Sync Now” 同步项目。
2. 数据库设计

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


  • notes 表:

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

3. 实现数据库

3.1 创建实体类 (Entity)

在 com.yourpackage.model 包下创建 Note.kt 文件:
  1. import androidx.room.Entity
  2. import androidx.room.PrimaryKey
  3. import java.util.*
  4. @Entity(tableName = "notes")
  5. data class Note(
  6.     @PrimaryKey(autoGenerate = true)
  7.     val id: Long = 0,
  8.    
  9.     var title: String,
  10.     var content: String,
  11.    
  12.     val created_at: Date = Date(),
  13.     var updated_at: Date = Date()
  14. )
复制代码
3.2 创建数据访问对象 (DAO)

在 com.yourpackage.dao 包下创建 NoteDao.kt 文件:
  1. import androidx.lifecycle.LiveData
  2. import androidx.room.*
  3. import com.yourpackage.model.Note
  4. @Dao
  5. interface NoteDao {
  6.    
  7.     @Insert(onConflict = OnConflictStrategy.REPLACE)
  8.     suspend fun insertNote(note: Note): Long
  9.    
  10.     @Update
  11.     suspend fun updateNote(note: Note)
  12.    
  13.     @Delete
  14.     suspend fun deleteNote(note: Note)
  15.    
  16.     @Query("SELECT * FROM notes ORDER BY updated_at DESC")
  17.     fun getAllNotes(): LiveData<List<Note>>
  18.    
  19.     @Query("SELECT * FROM notes WHERE id = :noteId")
  20.     suspend fun getNoteById(noteId: Long): Note?
  21.    
  22.     @Query("SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query ORDER BY updated_at DESC")
  23.     fun searchNotes(query: String): LiveData<List<Note>>
  24. }
复制代码
3.3 创建数据库类

在 com.yourpackage.database 包下创建 AppDatabase.kt 文件:
  1. import android.content.Context
  2. import androidx.room.Database
  3. import androidx.room.Room
  4. import androidx.room.RoomDatabase
  5. import com.yourpackage.dao.NoteDao
  6. import com.yourpackage.model.Note
  7. @Database(entities = [Note::class], version = 1, exportSchema = false)
  8. abstract class AppDatabase : RoomDatabase() {
  9.    
  10.     abstract fun noteDao(): NoteDao
  11.    
  12.     companion object {
  13.         @Volatile
  14.         private var INSTANCE: AppDatabase? = null
  15.         
  16.         fun getDatabase(context: Context): AppDatabase {
  17.             return INSTANCE ?: synchronized(this) {
  18.                 val instance = Room.databaseBuilder(
  19.                     context.applicationContext,
  20.                     AppDatabase::class.java,
  21.                     "notes_database"
  22.                 )
  23.                     .fallbackToDestructiveMigration() // 数据库升级策略,简单应用可以这样设置
  24.                     .build()
  25.                 INSTANCE = instance
  26.                 instance
  27.             }
  28.         }
  29.     }
  30. }
复制代码
4. 创建 Repository

在 com.yourpackage.repository 包下创建 NoteRepository.kt 文件:
  1. import androidx.lifecycle.LiveData
  2. import com.yourpackage.dao.NoteDao
  3. import com.yourpackage.model.Note
  4. import kotlinx.coroutines.Dispatchers
  5. import kotlinx.coroutines.withContext
  6. class NoteRepository(private val noteDao: NoteDao) {
  7.    
  8.     val allNotes: LiveData<List<Note>> = noteDao.getAllNotes()
  9.    
  10.     suspend fun insert(note: Note): Long {
  11.         return withContext(Dispatchers.IO) {
  12.             noteDao.insertNote(note)
  13.         }
  14.     }
  15.    
  16.     suspend fun update(note: Note) {
  17.         withContext(Dispatchers.IO) {
  18.             note.updated_at = Date()
  19.             noteDao.updateNote(note)
  20.         }
  21.     }
  22.    
  23.     suspend fun delete(note: Note) {
  24.         withContext(Dispatchers.IO) {
  25.             noteDao.deleteNote(note)
  26.         }
  27.     }
  28.    
  29.     suspend fun getNoteById(id: Long): Note? {
  30.         return withContext(Dispatchers.IO) {
  31.             noteDao.getNoteById(id)
  32.         }
  33.     }
  34.    
  35.     fun searchNotes(query: String): LiveData<List<Note>> {
  36.         return noteDao.searchNotes("%$query%")
  37.     }
  38. }
复制代码
5. 创建 ViewModel

在 com.yourpackage.viewmodel 包下创建 NoteViewModel.kt 文件:
  1. import androidx.lifecycle.ViewModel
  2. import androidx.lifecycle.ViewModelProvider
  3. import androidx.lifecycle.asLiveData
  4. import androidx.lifecycle.viewModelScope
  5. import com.yourpackage.model.Note
  6. import com.yourpackage.repository.NoteRepository
  7. import kotlinx.coroutines.launch
  8. class NoteViewModel(private val repository: NoteRepository) : ViewModel() {
  9.    
  10.     val allNotes = repository.allNotes
  11.    
  12.     fun insert(note: Note) = viewModelScope.launch {
  13.         repository.insert(note)
  14.     }
  15.    
  16.     fun update(note: Note) = viewModelScope.launch {
  17.         repository.update(note)
  18.     }
  19.    
  20.     fun delete(note: Note) = viewModelScope.launch {
  21.         repository.delete(note)
  22.     }
  23.    
  24.     fun getNoteById(id: Long) = viewModelScope.launch {
  25.         repository.getNoteById(id)
  26.     }
  27.    
  28.     fun searchNotes(query: String) = repository.searchNotes(query).asLiveData()
  29. }
  30. class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory {
  31.     override fun <T : ViewModel> create(modelClass: Class<T>): T {
  32.         if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
  33.             @Suppress("UNCHECKED_CAST")
  34.             return NoteViewModel(repository) as T
  35.         }
  36.         throw IllegalArgumentException("Unknown ViewModel class")
  37.     }
  38. }
复制代码
6. 实现 UI 层

6.1 创建条记列表 Activity

创建 NotesListActivity.kt 和对应的结构文件 activity_notes_list.xml
activity_notes_list.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.coordinatorlayout.widget.CoordinatorLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     xmlns:app="http://schemas.android.com/apk/res-auto"
  5.     xmlns:tools="http://schemas.android.com/tools"
  6.     android:layout_width="match_parent"
  7.     android:layout_height="match_parent"
  8.     tools:context=".ui.NotesListActivity">
  9.     <com.google.android.material.appbar.AppBarLayout
  10.         android:layout_width="match_parent"
  11.         android:layout_height="wrap_content"
  12.         android:theme="@style/Theme.SQLiteDemo.AppBarOverlay">
  13.         <androidx.appcompat.widget.Toolbar
  14.             android:id="@+id/toolbar"
  15.             android:layout_width="match_parent"
  16.             android:layout_height="?attr/actionBarSize"
  17.             android:background="?attr/colorPrimary"
  18.             app:popupTheme="@style/Theme.SQLiteDemo.PopupOverlay"
  19.             app:title="@string/app_name" />
  20.         <com.google.android.material.textfield.TextInputLayout
  21.             android:id="@+id/search_layout"
  22.             android:layout_width="match_parent"
  23.             android:layout_height="wrap_content"
  24.             style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
  25.             <com.google.android.material.textfield.TextInputEditText
  26.                 android:id="@+id/search_input"
  27.                 android:layout_width="match_parent"
  28.                 android:layout_height="wrap_content"
  29.                 android:hint="@string/search_hint"
  30.                 android:imeOptions="actionSearch"
  31.                 android:inputType="text" />
  32.         </com.google.android.material.textfield.TextInputLayout>
  33.     </com.google.android.material.appbar.AppBarLayout>
  34.     <androidx.recyclerview.widget.RecyclerView
  35.         android:id="@+id/notes_recycler_view"
  36.         android:layout_width="match_parent"
  37.         android:layout_height="match_parent"
  38.         android:clipToPadding="false"
  39.         android:paddingBottom="72dp"
  40.         app:layout_behavior="@string/appbar_scrolling_view_behavior" />
  41.     <com.google.android.material.floatingactionbutton.FloatingActionButton
  42.         android:id="@+id/fab_add_note"
  43.         android:layout_width="wrap_content"
  44.         android:layout_height="wrap_content"
  45.         android:layout_gravity="bottom|end"
  46.         android:layout_margin="16dp"
  47.         android:contentDescription="@string/add_note"
  48.         android:src="@drawable/ic_add"
  49.         app:backgroundTint="@color/purple_500"
  50.         app:tint="@android:color/white" />
  51. </androidx.coordinatorlayout.widget.CoordinatorLayout>
复制代码
NotesListActivity.kt

  1. import android.content.Intent
  2. import android.os.Bundle
  3. import android.view.Menu
  4. import android.view.MenuItem
  5. import android.view.inputmethod.EditorInfo
  6. import androidx.activity.viewModels
  7. import androidx.appcompat.app.AppCompatActivity
  8. import androidx.recyclerview.widget.LinearLayoutManager
  9. import com.google.android.material.snackbar.Snackbar
  10. import com.yourpackage.R
  11. import com.yourpackage.adapter.NotesAdapter
  12. import com.yourpackage.databinding.ActivityNotesListBinding
  13. import com.yourpackage.model.Note
  14. import com.yourpackage.viewmodel.NoteViewModel
  15. import com.yourpackage.viewmodel.NoteViewModelFactory
  16. class NotesListActivity : AppCompatActivity() {
  17.    
  18.     private lateinit var binding: ActivityNotesListBinding
  19.     private lateinit var notesAdapter: NotesAdapter
  20.    
  21.     private val viewModel: NoteViewModel by viewModels {
  22.         NoteViewModelFactory((application as NotesApplication).repository)
  23.     }
  24.    
  25.     override fun onCreate(savedInstanceState: Bundle?) {
  26.         super.onCreate(savedInstanceState)
  27.         binding = ActivityNotesListBinding.inflate(layoutInflater)
  28.         setContentView(binding.root)
  29.         
  30.         setSupportActionBar(binding.toolbar)
  31.         
  32.         setupRecyclerView()
  33.         setupSearch()
  34.         setupFAB()
  35.         observeNotes()
  36.     }
  37.    
  38.     private fun setupRecyclerView() {
  39.         notesAdapter = NotesAdapter { note ->
  40.             // 点击笔记项时的操作
  41.             val intent = Intent(this, NoteDetailActivity::class.java).apply {
  42.                 putExtra(NoteDetailActivity.EXTRA_NOTE_ID, note.id)
  43.             }
  44.             startActivity(intent)
  45.         }
  46.         
  47.         binding.notesRecyclerView.apply {
  48.             layoutManager = LinearLayoutManager(this@NotesListActivity)
  49.             adapter = notesAdapter
  50.             setHasFixedSize(true)
  51.         }
  52.     }
  53.    
  54.     private fun setupSearch() {
  55.         binding.searchInput.setOnEditorActionListener { _, actionId, _ ->
  56.             if (actionId == EditorInfo.IME_ACTION_SEARCH) {
  57.                 val query = binding.searchInput.text.toString().trim()
  58.                 if (query.isNotEmpty()) {
  59.                     viewModel.searchNotes(query).observe(this) { notes ->
  60.                         notesAdapter.submitList(notes)
  61.                     }
  62.                 } else {
  63.                     observeNotes() // 如果查询为空,返回所有笔记
  64.                 }
  65.                 true
  66.             } else {
  67.                 false
  68.             }
  69.         }
  70.     }
  71.    
  72.     private fun setupFAB() {
  73.         binding.fabAddNote.setOnClickListener {
  74.             val intent = Intent(this, NoteDetailActivity::class.java)
  75.             startActivity(intent)
  76.         }
  77.     }
  78.    
  79.     private fun observeNotes() {
  80.         viewModel.allNotes.observe(this) { notes ->
  81.             notesAdapter.submitList(notes)
  82.         }
  83.     }
  84.    
  85.     override fun onCreateOptionsMenu(menu: Menu): Boolean {
  86.         menuInflater.inflate(R.menu.menu_main, menu)
  87.         return true
  88.     }
  89.    
  90.     override fun onOptionsItemSelected(item: MenuItem): Boolean {
  91.         return when (item.itemId) {
  92.             R.id.action_delete_all -> {
  93.                 deleteAllNotes()
  94.                 true
  95.             }
  96.             else -> super.onOptionsItemSelected(item)
  97.         }
  98.     }
  99.    
  100.     private fun deleteAllNotes() {
  101.         viewModel.allNotes.value?.let { notes ->
  102.             if (notes.isNotEmpty()) {
  103.                 for (note in notes) {
  104.                     viewModel.delete(note)
  105.                 }
  106.                 Snackbar.make(binding.root, "All notes deleted", Snackbar.LENGTH_SHORT).show()
  107.             }
  108.         }
  109.     }
  110. }
复制代码
6.2 创建条记详情 Activity

创建 NoteDetailActivity.kt 和对应的结构文件 activity_note_detail.xml
activity_note_detail.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.coordinatorlayout.widget.CoordinatorLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     xmlns:app="http://schemas.android.com/apk/res-auto"
  5.     xmlns:tools="http://schemas.android.com/tools"
  6.     android:layout_width="match_parent"
  7.     android:layout_height="match_parent"
  8.     tools:context=".ui.NoteDetailActivity">
  9.     <com.google.android.material.appbar.AppBarLayout
  10.         android:layout_width="match_parent"
  11.         android:layout_height="wrap_content"
  12.         android:theme="@style/Theme.SQLiteDemo.AppBarOverlay">
  13.         <androidx.appcompat.widget.Toolbar
  14.             android:id="@+id/toolbar"
  15.             android:layout_width="match_parent"
  16.             android:layout_height="?attr/actionBarSize"
  17.             android:background="?attr/colorPrimary"
  18.             app:popupTheme="@style/Theme.SQLiteDemo.PopupOverlay" />
  19.     </com.google.android.material.appbar.AppBarLayout>
  20.     <androidx.core.widget.NestedScrollView
  21.         android:layout_width="match_parent"
  22.         android:layout_height="match_parent"
  23.         app:layout_behavior="@string/appbar_scrolling_view_behavior">
  24.         <LinearLayout
  25.             android:layout_width="match_parent"
  26.             android:layout_height="wrap_content"
  27.             android:orientation="vertical"
  28.             android:padding="16dp">
  29.             <com.google.android.material.textfield.TextInputLayout
  30.                 android:id="@+id/title_layout"
  31.                 android:layout_width="match_parent"
  32.                 android:layout_height="wrap_content"
  33.                 style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
  34.                 <com.google.android.material.textfield.TextInputEditText
  35.                     android:id="@+id/title_input"
  36.                     android:layout_width="match_parent"
  37.                     android:layout_height="wrap_content"
  38.                     android:hint="@string/title_hint"
  39.                     android:inputType="textCapSentences|textAutoCorrect"
  40.                     android:maxLines="1" />
  41.             </com.google.android.material.textfield.TextInputLayout>
  42.             <com.google.android.material.textfield.TextInputLayout
  43.                 android:id="@+id/content_layout"
  44.                 android:layout_width="match_parent"
  45.                 android:layout_height="wrap_content"
  46.                 android:layout_marginTop="16dp"
  47.                 style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
  48.                 <com.google.android.material.textfield.TextInputEditText
  49.                     android:id="@+id/content_input"
  50.                     android:layout_width="match_parent"
  51.                     android:layout_height="wrap_content"
  52.                     android:hint="@string/content_hint"
  53.                     android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
  54.                     android:minLines="5"
  55.                     android:gravity="top" />
  56.             </com.google.android.material.textfield.TextInputLayout>
  57.         </LinearLayout>
  58.     </androidx.core.widget.NestedScrollView>
  59.     <com.google.android.material.floatingactionbutton.FloatingActionButton
  60.         android:id="@+id/fab_save"
  61.         android:layout_width="wrap_content"
  62.         android:layout_height="wrap_content"
  63.         android:layout_gravity="bottom|end"
  64.         android:layout_margin="16dp"
  65.         android:contentDescription="@string/save_note"
  66.         android:src="@drawable/ic_save"
  67.         app:backgroundTint="@color/purple_500"
  68.         app:tint="@android:color/white" />
  69. </androidx.coordinatorlayout.widget.CoordinatorLayout>
复制代码
NoteDetailActivity.kt

  1. import android.os.Bundle
  2. import android.text.Editable
  3. import android.text.TextWatcher
  4. import android.view.MenuItem
  5. import androidx.activity.viewModels
  6. import androidx.appcompat.app.AppCompatActivity
  7. import com.google.android.material.snackbar.Snackbar
  8. import com.yourpackage.R
  9. import com.yourpackage.databinding.ActivityNoteDetailBinding
  10. import com.yourpackage.model.Note
  11. import com.yourpackage.viewmodel.NoteViewModel
  12. import com.yourpackage.viewmodel.NoteViewModelFactory
  13. import java.util.*
  14. class NoteDetailActivity : AppCompatActivity() {
  15.    
  16.     companion object {
  17.         const val EXTRA_NOTE_ID = "extra_note_id"
  18.     }
  19.    
  20.     private lateinit var binding: ActivityNoteDetailBinding
  21.     private val viewModel: NoteViewModel by viewModels {
  22.         NoteViewModelFactory((application as NotesApplication).repository)
  23.     }
  24.    
  25.     private var noteId: Long = -1L
  26.     private var isNewNote = true
  27.    
  28.     override fun onCreate(savedInstanceState: Bundle?) {
  29.         super.onCreate(savedInstanceState)
  30.         binding = ActivityNoteDetailBinding.inflate(layoutInflater)
  31.         setContentView(binding.root)
  32.         
  33.         setSupportActionBar(binding.toolbar)
  34.         supportActionBar?.setDisplayHomeAsUpEnabled(true)
  35.         
  36.         noteId = intent.getLongExtra(EXTRA_NOTE_ID, -1L)
  37.         isNewNote = noteId == -1L
  38.         
  39.         if (!isNewNote) {
  40.             loadNote()
  41.         }
  42.         
  43.         setupSaveButton()
  44.         setupTextWatchers()
  45.     }
  46.    
  47.     private fun loadNote() {
  48.         viewModel.getNoteById(noteId)
  49.         viewModel.allNotes.observe(this) { notes ->
  50.             notes.find { it.id == noteId }?.let { note ->
  51.                 binding.titleInput.setText(note.title)
  52.                 binding.contentInput.setText(note.content)
  53.             }
  54.         }
  55.     }
  56.    
  57.     private fun setupSaveButton() {
  58.         binding.fabSave.setOnClickListener {
  59.             saveNote()
  60.         }
  61.     }
  62.    
  63.     private fun setupTextWatchers() {
  64.         binding.titleInput.addTextChangedListener(object : TextWatcher {
  65.             override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
  66.             override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
  67.             override fun afterTextChanged(s: Editable?) {
  68.                 validateInputs()
  69.             }
  70.         })
  71.         
  72.         binding.contentInput.addTextChangedListener(object : TextWatcher {
  73.             override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
  74.             override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
  75.             override fun afterTextChanged(s: Editable?) {
  76.                 validateInputs()
  77.             }
  78.         })
  79.     }
  80.    
  81.     private fun validateInputs(): Boolean {
  82.         val titleValid = binding.titleInput.text?.isNotBlank() ?: false
  83.         val contentValid = binding.contentInput.text?.isNotBlank() ?: false
  84.         
  85.         binding.titleLayout.error = if (!titleValid) getString(R.string.title_required) else null
  86.         binding.contentLayout.error = if (!contentValid) getString(R.string.content_required) else null
  87.         
  88.         return titleValid && contentValid
  89.     }
  90.    
  91.     private fun saveNote() {
  92.         if (!validateInputs()) return
  93.         
  94.         val title = binding.titleInput.text.toString()
  95.         val content = binding.contentInput.text.toString()
  96.         
  97.         if (isNewNote) {
  98.             val note = Note(title = title, content = content)
  99.             viewModel.insert(note)
  100.             Snackbar.make(binding.root, "Note saved", Snackbar.LENGTH_SHORT).show()
  101.             finish()
  102.         } else {
  103.             viewModel.allNotes.value?.find { it.id == noteId }?.let { existingNote ->
  104.                 val updatedNote = existingNote.copy(
  105.                     title = title,
  106.                     content = content,
  107.                     updated_at = Date()
  108.                 )
  109.                 viewModel.update(updatedNote)
  110.                 Snackbar.make(binding.root, "Note updated", Snackbar.LENGTH_SHORT).show()
  111.                 finish()
  112.             }
  113.         }
  114.     }
  115.    
  116.     override fun onOptionsItemSelected(item: MenuItem): Boolean {
  117.         return when (item.itemId) {
  118.             android.R.id.home -> {
  119.                 onBackPressed()
  120.                 true
  121.             }
  122.             else -> super.onOptionsItemSelected(item)
  123.         }
  124.     }
  125. }
复制代码
6.3 创建 RecyclerView Adapter

在 com.yourpackage.adapter 包下创建 NotesAdapter.kt 文件:
  1. import android.view.LayoutInflater
  2. import android.view.View
  3. import android.view.ViewGroup
  4. import androidx.recyclerview.widget.DiffUtil
  5. import androidx.recyclerview.widget.ListAdapter
  6. import androidx.recyclerview.widget.RecyclerView
  7. import com.yourpackage.R
  8. import com.yourpackage.databinding.ItemNoteBinding
  9. import com.yourpackage.model.Note
  10. import java.text.SimpleDateFormat
  11. import java.util.*
  12. class NotesAdapter(private val onItemClick: (Note) -> Unit) :
  13.     ListAdapter<Note, NotesAdapter.NoteViewHolder>(NoteDiffCallback()) {
  14.    
  15.     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
  16.         val binding = ItemNoteBinding.inflate(
  17.             LayoutInflater.from(parent.context),
  18.             parent,
  19.             false
  20.         )
  21.         return NoteViewHolder(binding, onItemClick)
  22.     }
  23.    
  24.     override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
  25.         holder.bind(getItem(position))
  26.     }
  27.    
  28.     class NoteViewHolder(
  29.         private val binding: ItemNoteBinding,
  30.         private val onItemClick: (Note) -> Unit
  31.     ) : RecyclerView.ViewHolder(binding.root) {
  32.         
  33.         fun bind(note: Note) {
  34.             binding.apply {
  35.                 noteTitle.text = note.title
  36.                 noteContent.text = note.content
  37.                
  38.                 val dateFormat = SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault())
  39.                 noteDate.text = dateFormat.format(note.updated_at)
  40.                
  41.                 root.setOnClickListener {
  42.                     onItemClick(note)
  43.                 }
  44.             }
  45.         }
  46.     }
  47.    
  48.     private class NoteDiffCallback : DiffUtil.ItemCallback<Note>() {
  49.         override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
  50.             return oldItem.id == newItem.id
  51.         }
  52.         
  53.         override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
  54.             return oldItem == newItem
  55.         }
  56.     }
  57. }
复制代码
创建对应的列表项结构文件 item_note.xml:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.google.android.material.card.MaterialCardView
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     xmlns:app="http://schemas.android.com/apk/res-auto"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="wrap_content"
  7.     android:layout_margin="8dp"
  8.     app:cardCornerRadius="8dp"
  9.     app:cardElevation="4dp">
  10.     <LinearLayout
  11.         android:layout_width="match_parent"
  12.         android:layout_height="wrap_content"
  13.         android:orientation="vertical"
  14.         android:padding="16dp">
  15.         <TextView
  16.             android:id="@+id/note_title"
  17.             android:layout_width="match_parent"
  18.             android:layout_height="wrap_content"
  19.             android:textAppearance="@style/TextAppearance.AppCompat.Headline"
  20.             android:textColor="@android:color/black" />
  21.         <TextView
  22.             android:id="@+id/note_content"
  23.             android:layout_width="match_parent"
  24.             android:layout_height="wrap_content"
  25.             android:layout_marginTop="8dp"
  26.             android:ellipsize="end"
  27.             android:maxLines="2"
  28.             android:textAppearance="@style/TextAppearance.AppCompat.Body1"
  29.             android:textColor="@android:color/darker_gray" />
  30.         <TextView
  31.             android:id="@+id/note_date"
  32.             android:layout_width="match_parent"
  33.             android:layout_height="wrap_content"
  34.             android:layout_marginTop="8dp"
  35.             android:textAppearance="@style/TextAppearance.AppCompat.Caption"
  36.             android:textColor="@android:color/darker_gray" />
  37.     </LinearLayout>
  38. </com.google.android.material.card.MaterialCardView>
复制代码
6.4 创建 Application 类

在 com.yourpackage 包下创建 NotesApplication.kt 文件:
  1. import android.app.Application
  2. import com.yourpackage.database.AppDatabase
  3. import com.yourpackage.repository.NoteRepository
  4. class NotesApplication : Application() {
  5.    
  6.     val database by lazy { AppDatabase.getDatabase(this) }
  7.     val repository by lazy { NoteRepository(database.noteDao()) }
  8. }
复制代码
更新 AndroidManifest.xml 文件,添加 android:name 属性:
  1. <application
  2.     android:name=".NotesApplication"
  3.     android:allowBackup="true"
  4.     android:icon="@mipmap/ic_launcher"
  5.     android:label="@string/app_name"
  6.     android:roundIcon="@mipmap/ic_launcher_round"
  7.     android:supportsRtl="true"
  8.     android:theme="@style/Theme.SQLiteDemo">
  9.     <!-- 其他配置 -->
  10. </application>
复制代码
7. 添加菜单资源

在 res/menu 目录下创建 menu_main.xml 文件:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <menu xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto">
  4.    
  5.     <item
  6.         android:id="@+id/action_delete_all"
  7.         android:icon="@drawable/ic_delete"
  8.         android:title="@string/delete_all"
  9.         app:showAsAction="never" />
  10. </menu>
复制代码
8. 添加字符串资源

在 res/values/strings.xml 文件中添加以下字符串:
  1. <resources>
  2.     <string name="app_name">SQLite Notes</string>
  3.     <string name="title_hint">Title</string>
  4.     <string name="content_hint">Content</string>
  5.     <string name="search_hint">Search notes...</string>
  6.     <string name="add_note">Add new note</string>
  7.     <string name="save_note">Save note</string>
  8.     <string name="delete_all">Delete all notes</string>
  9.     <string name="title_required">Title is required</string>
  10.     <string name="content_required">Content is required</string>
  11. </resources>
复制代码
9. 添加图标资源

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


  • ic_add.xml (添加按钮图标)
  • ic_save.xml (生存按钮图标)
  • ic_delete.xml (删除按钮图标)
10. 运行和测试应用

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

  • 添加新条记
  • 查看条记列表
  • 编辑现有条记
  • 删除条记
  • 搜刮条记
  • 删除所有条记
11. 数据库调试本事

11.1 查看数据库内容


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

添加 Stetho 依赖到 build.gradle:
  1. implementation 'com.facebook.stetho:stetho:1.6.0'
复制代码
在 NotesApplication.kt 中初始化 Stetho:
  1. import com.facebook.stetho.Stetho
  2. class NotesApplication : Application() {
  3.     override fun onCreate() {
  4.         super.onCreate()
  5.         Stetho.initializeWithDefaults(this)
  6.     }
  7.    
  8.     // 其他代码...
  9. }
复制代码
运行应用后,在 Chrome 浏览器中访问 chrome://inspect 可以查看和调试数据库。
12. 数据库迁移

当您需要更改数据库结构时(比方添加新表或修改现有表),需要进行数据库迁移。
12.1 修改实体类

比方,我们要为 Note 添加一个 is_pinned 字段:
  1. @Entity(tableName = "notes")
  2. data class Note(
  3.     // 现有字段...
  4.     var is_pinned: Boolean = false
  5. )
复制代码
12.2 更新数据库版本

修改 AppDatabase.kt:
  1. @Database(entities = [Note::class], version = 2, exportSchema = false)
  2. abstract class AppDatabase : RoomDatabase() {
  3.     // ...
  4. }
复制代码
12.3 添加迁移策略

  1. val migration1to2 = object : Migration(1, 2) {
  2.     override fun migrate(database: SupportSQLiteDatabase) {
  3.         database.execSQL("ALTER TABLE notes ADD COLUMN is_pinned INTEGER NOT NULL DEFAULT 0")
  4.     }
  5. }
  6. // 在 databaseBuilder 中添加迁移
  7. val instance = Room.databaseBuilder(
  8.     context.applicationContext,
  9.     AppDatabase::class.java,
  10.     "notes_database"
  11. )
  12.     .addMigrations(migration1to2)
  13.     .build()
复制代码
13. 性能优化建议


  • 使用事件:对于批量操作,使用事件可以显著进步性能:
  1. @Dao
  2. interface NoteDao {
  3.     @Transaction
  4.     suspend fun insertAll(notes: List<Note>) {
  5.         notes.forEach { insertNote(it) }
  6.     }
  7. }
复制代码

  • 索引优化:为常用查询字段添加索引:
  1. @Entity(tableName = "notes", indices = [Index(value = ["title"], unique = false)])
  2. data class Note(
  3.     // ...
  4. )
复制代码

  • 分页加载:对于大量数据,使用 Paging 库:
  1. @Query("SELECT * FROM notes ORDER BY updated_at DESC")
  2. fun getPagedNotes(): PagingSource<Int, Note>
复制代码

  • 避免在主线程操作数据库:始终确保数据库操作在后台线程执行。
14. 完整项目结构

终极项目结构应类似于:
  1. com.yourpackage
  2. ├── adapter
  3. │   └── NotesAdapter.kt
  4. ├── dao
  5. │   └── NoteDao.kt
  6. ├── database
  7. │   └── AppDatabase.kt
  8. ├── model
  9. │   └── Note.kt
  10. ├── repository
  11. │   └── NoteRepository.kt
  12. ├── ui
  13. │   ├── NotesListActivity.kt
  14. │   └── NoteDetailActivity.kt
  15. ├── viewmodel
  16. │   ├── NoteViewModel.kt
  17. │   └── NoteViewModelFactory.kt
  18. └── NotesApplication.kt
复制代码
15. 总结

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

  • 设置项目和依赖
  • 设计数据库结构
  • 实现 Room 数据库组件(Entity, DAO, Database)
  • 创建 Repository 层
  • 实现 ViewModel
  • 构建用户界面
  • 添加数据库迁移支持
  • 性能优化建议
通过遵照这些步调,您可以构建一个功能美满、结构清晰的 Android 应用,充实利用 SQLite 数据库的强大功能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

罪恶克星

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表