文章目录

本文将详细介绍在 Android Studio 中使用 Java 语言开发 SQLite 数据库应用的完整方案,包括每一个步骤、每一个文件和每一段代码。
1. 项目准备
1.1 创建新项目
- 打开 Android Studio
- 选择 “Start a new Android Studio project”
- 选择 “Empty Activity” 模板
- 设置项目名称(例如 “SQLiteDemoJava”)
- 选择语言(选择 Java)
- 设置最低 API 级别(建议 API 21 或更高)
- 点击 “Finish” 完成项目创建
1.2 添加必要依赖
确保 build.gradle (Module: app)
中包含以下依赖:
dependencies {
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"
annotationProcessor "androidx.room:room-compiler:2.4.2"
// ViewModel 和 LiveData
implementation "androidx.lifecycle:lifecycle-viewmodel:2.4.1"
implementation "androidx.lifecycle:lifecycle-livedata: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.java
文件:
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import java.util.Date;
@Entity(tableName = "notes")
public class Note {
@PrimaryKey(autoGenerate = true)
private long id;
private String title;
private String content;
@TypeConverters(DateConverter.class)
private Date created_at;
@TypeConverters(DateConverter.class)
private Date updated_at;
public Note(String title, String content) {
this.title = title;
this.content = content;
this.created_at = new Date();
this.updated_at = new Date();
}
// Getters and Setters
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
public Date getUpdated_at() {
return updated_at;
}
public void setUpdated_at(Date updated_at) {
this.updated_at = updated_at;
}
}
3.2 创建日期转换器
由于 Room 不能直接存储 Date 对象,我们需要创建一个转换器:
在 com.yourpackage.database
包下创建 DateConverter.java
:
import androidx.room.TypeConverter;
import java.util.Date;
public class DateConverter {
@TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
@TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
3.3 创建数据访问对象 (DAO)
在 com.yourpackage.dao
包下创建 NoteDao.java
文件:
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.yourpackage.model.Note;
import java.util.List;
@Dao
public interface NoteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertNote(Note note);
@Update
void updateNote(Note note);
@Delete
void deleteNote(Note note);
@Query("SELECT * FROM notes ORDER BY updated_at DESC")
LiveData<List<Note>> getAllNotes();
@Query("SELECT * FROM notes WHERE id = :noteId")
Note getNoteById(long noteId);
@Query("SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query ORDER BY updated_at DESC")
LiveData<List<Note>> searchNotes(String query);
@Query("DELETE FROM notes")
void deleteAllNotes();
}
3.4 创建数据库类
在 com.yourpackage.database
包下创建 AppDatabase.java
文件:
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import com.yourpackage.dao.NoteDao;
import com.yourpackage.model.Note;
@Database(entities = {Note.class}, version = 1, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
public abstract NoteDao noteDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "notes_database")
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
}
4. 创建 Repository
在 com.yourpackage.repository
包下创建 NoteRepository.java
文件:
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.yourpackage.dao.NoteDao;
import com.yourpackage.database.AppDatabase;
import com.yourpackage.model.Note;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NoteRepository {
private NoteDao noteDao;
private LiveData<List<Note>> allNotes;
private ExecutorService executorService;
public NoteRepository(Application application) {
AppDatabase database = AppDatabase.getDatabase(application);
noteDao = database.noteDao();
allNotes = noteDao.getAllNotes();
executorService = Executors.newSingleThreadExecutor();
}
public LiveData<List<Note>> getAllNotes() {
return allNotes;
}
public void insert(Note note) {
executorService.execute(() -> noteDao.insertNote(note));
}
public void update(Note note) {
executorService.execute(() -> {
note.setUpdated_at(new Date());
noteDao.updateNote(note);
});
}
public void delete(Note note) {
executorService.execute(() -> noteDao.deleteNote(note));
}
public Note getNoteById(long id) {
return noteDao.getNoteById(id);
}
public LiveData<List<Note>> searchNotes(String query) {
return noteDao.searchNotes("%" + query + "%");
}
public void deleteAllNotes() {
executorService.execute(() -> noteDao.deleteAllNotes());
}
}
5. 创建 ViewModel
在 com.yourpackage.viewmodel
包下创建 NoteViewModel.java
文件:
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.yourpackage.model.Note;
import com.yourpackage.repository.NoteRepository;
import java.util.List;
public class NoteViewModel extends AndroidViewModel {
private NoteRepository repository;
private LiveData<List<Note>> allNotes;
public NoteViewModel(Application application) {
super(application);
repository = new NoteRepository(application);
allNotes = repository.getAllNotes();
}
public void insert(Note note) {
repository.insert(note);
}
public void update(Note note) {
repository.update(note);
}
public void delete(Note note) {
repository.delete(note);
}
public void deleteAllNotes() {
repository.deleteAllNotes();
}
public LiveData<List<Note>> getAllNotes() {
return allNotes;
}
public Note getNoteById(long id) {
return repository.getNoteById(id);
}
public LiveData<List<Note>> searchNotes(String query) {
return repository.searchNotes(query);
}
}
6. 实现 UI 层
6.1 创建笔记列表 Activity
创建 NotesListActivity.java
和对应的布局文件 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.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.SQLiteDemoJava.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.SQLiteDemoJava.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.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.yourpackage.R;
import com.yourpackage.adapter.NotesAdapter;
import com.yourpackage.model.Note;
import com.yourpackage.viewmodel.NoteViewModel;
import java.util.Objects;
public class NotesListActivity extends AppCompatActivity {
private NoteViewModel noteViewModel;
private NotesAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notes_list);
// 设置Toolbar
setSupportActionBar(findViewById(R.id.toolbar));
Objects.requireNonNull(getSupportActionBar()).setTitle("Notes");
// 初始化ViewModel
noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
// 设置RecyclerView
RecyclerView recyclerView = findViewById(R.id.notes_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
adapter = new NotesAdapter();
recyclerView.setAdapter(adapter);
// 观察笔记列表变化
noteViewModel.getAllNotes().observe(this, notes -> {
adapter.setNotes(notes);
});
// 设置添加笔记按钮
FloatingActionButton fabAddNote = findViewById(R.id.fab_add_note);
fabAddNote.setOnClickListener(view -> {
Intent intent = new Intent(NotesListActivity.this, NoteDetailActivity.class);
startActivity(intent);
});
// 设置搜索功能
EditText searchInput = findViewById(R.id.search_input);
searchInput.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String query = searchInput.getText().toString().trim();
if (!query.isEmpty()) {
noteViewModel.searchNotes(query).observe(this, notes -> {
adapter.setNotes(notes);
});
} else {
noteViewModel.getAllNotes().observe(this, notes -> {
adapter.setNotes(notes);
});
}
return true;
}
return false;
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_delete_all) {
noteViewModel.deleteAllNotes();
Snackbar.make(findViewById(android.R.id.content), "All notes deleted", Snackbar.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
6.2 创建笔记详情 Activity
创建 NoteDetailActivity.java
和对应的布局文件 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.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.SQLiteDemoJava.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.SQLiteDemoJava.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.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.MenuItem;
import android.widget.EditText;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.yourpackage.R;
import com.yourpackage.model.Note;
import com.yourpackage.viewmodel.NoteViewModel;
import java.util.Date;
public class NoteDetailActivity extends AppCompatActivity {
public static final String EXTRA_NOTE_ID = "extra_note_id";
private NoteViewModel noteViewModel;
private EditText titleInput, contentInput;
private TextInputLayout titleLayout, contentLayout;
private long noteId = -1;
private boolean isNewNote = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_detail);
// 设置Toolbar
setSupportActionBar(findViewById(R.id.toolbar));
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
// 初始化ViewModel
noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
// 初始化视图
titleInput = findViewById(R.id.title_input);
contentInput = findViewById(R.id.content_input);
titleLayout = findViewById(R.id.title_layout);
contentLayout = findViewById(R.id.content_layout);
// 检查是否是编辑现有笔记
if (getIntent().hasExtra(EXTRA_NOTE_ID)) {
noteId = getIntent().getLongExtra(EXTRA_NOTE_ID, -1);
isNewNote = false;
loadNote();
}
// 设置保存按钮
findViewById(R.id.fab_save).setOnClickListener(view -> saveNote());
// 设置输入验证
setupTextWatchers();
}
private void loadNote() {
Note note = noteViewModel.getNoteById(noteId);
if (note != null) {
titleInput.setText(note.getTitle());
contentInput.setText(note.getContent());
}
}
private void setupTextWatchers() {
titleInput.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
validateInputs();
}
});
contentInput.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
validateInputs();
}
});
}
private boolean validateInputs() {
boolean titleValid = !titleInput.getText().toString().trim().isEmpty();
boolean contentValid = !contentInput.getText().toString().trim().isEmpty();
titleLayout.setError(titleValid ? null : getString(R.string.title_required));
contentLayout.setError(contentValid ? null : getString(R.string.content_required));
return titleValid && contentValid;
}
private void saveNote() {
if (!validateInputs()) return;
String title = titleInput.getText().toString().trim();
String content = contentInput.getText().toString().trim();
if (isNewNote) {
Note note = new Note(title, content);
noteViewModel.insert(note);
Snackbar.make(findViewById(android.R.id.content), "Note saved", Snackbar.LENGTH_SHORT).show();
} else {
Note existingNote = noteViewModel.getNoteById(noteId);
if (existingNote != null) {
existingNote.setTitle(title);
existingNote.setContent(content);
existingNote.setUpdated_at(new Date());
noteViewModel.update(existingNote);
Snackbar.make(findViewById(android.R.id.content), "Note updated", Snackbar.LENGTH_SHORT).show();
}
}
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}
6.3 创建 RecyclerView Adapter
在 com.yourpackage.adapter
包下创建 NotesAdapter.java
文件:
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.yourpackage.R;
import com.yourpackage.model.Note;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NoteViewHolder> {
private List<Note> notes = new ArrayList<>();
private OnItemClickListener listener;
@NonNull
@Override
public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_note, parent, false);
return new NoteViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
Note currentNote = notes.get(position);
holder.title.setText(currentNote.getTitle());
holder.content.setText(currentNote.getContent());
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault());
holder.date.setText(dateFormat.format(currentNote.getUpdated_at()));
holder.cardView.setOnClickListener(v -> {
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(notes.get(position));
}
});
}
@Override
public int getItemCount() {
return notes.size();
}
public void setNotes(List<Note> notes) {
this.notes = notes;
notifyDataSetChanged();
}
public Note getNoteAt(int position) {
return notes.get(position);
}
class NoteViewHolder extends RecyclerView.ViewHolder {
private TextView title, content, date;
private MaterialCardView cardView;
public NoteViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.note_title);
content = itemView.findViewById(R.id.note_content);
date = itemView.findViewById(R.id.note_date);
cardView = (MaterialCardView) itemView;
}
}
public interface OnItemClickListener {
void onItemClick(Note note);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
创建对应的列表项布局文件 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">
<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>
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">
<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 (Java)</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. 更新 AndroidManifest.xml
确保 AndroidManifest.xml 包含两个 Activity:
<application
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.SQLiteDemoJava">
<activity
android:name=".ui.NoteDetailActivity"
android:parentActivityName=".ui.NotesListActivity" />
<activity
android:name=".ui.NotesListActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
11. 运行和测试应用
现在,您可以运行应用程序并测试以下功能:
- 添加新笔记
- 查看笔记列表
- 编辑现有笔记
- 删除笔记
- 搜索笔记
- 删除所有笔记
12. 数据库调试技巧
12.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)打开该文件查看内容
12.2 使用 Stetho 进行调试
添加 Stetho 依赖到 build.gradle
:
implementation 'com.facebook.stetho:stetho:1.6.0'
创建一个自定义 Application 类:
import android.app.Application;
import com.facebook.stetho.Stetho;
public class NotesApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Stetho.initializeWithDefaults(this);
}
}
更新 AndroidManifest.xml 中的 application 标签:
<application
android:name=".NotesApplication"
...>
...
</application>
运行应用后,在 Chrome 浏览器中访问 chrome://inspect
可以查看和调试数据库。
13. 数据库迁移
当您需要更改数据库结构时(例如添加新表或修改现有表),需要进行数据库迁移。
13.1 修改实体类
例如,我们要为 Note 添加一个 is_pinned
字段:
@Entity(tableName = "notes")
public class Note {
// 现有字段...
private boolean isPinned;
// Getter 和 Setter
public boolean isPinned() {
return isPinned;
}
public void setPinned(boolean pinned) {
isPinned = pinned;
}
}
13.2 更新数据库版本
修改 AppDatabase.java
:
@Database(entities = {Note.class}, version = 2, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
// ...
}
13.3 添加迁移策略
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE notes ADD COLUMN is_pinned INTEGER NOT NULL DEFAULT 0");
}
};
// 在 databaseBuilder 中添加迁移
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "notes_database")
.addMigrations(MIGRATION_1_2)
.build();
14. 性能优化建议
- 使用事务:对于批量操作,使用事务可以显著提高性能:
@Dao
public interface NoteDao {
@Transaction
default void insertAll(List<Note> notes) {
for (Note note : notes) {
insertNote(note);
}
}
}
- 索引优化:为常用查询字段添加索引:
@Entity(tableName = "notes", indices = {@Index(value = {"title"}, unique = false)})
public class Note {
// ...
}
- 避免在主线程操作数据库:始终确保数据库操作在后台线程执行。
15. 总结
本指南详细介绍了在 Android Studio 中使用 Java 语言开发 SQLite 数据库应用的完整流程,包括:
- 设置项目和依赖
- 设计数据库结构
- 实现 Room 数据库组件(Entity, DAO, Database)
- 创建 Repository 层
- 实现 ViewModel
- 构建用户界面
- 添加数据库迁移支持
- 性能优化建议
通过遵循这些步骤,您可以构建一个功能完善、结构清晰的 Android 应用,充分利用 SQLite 数据库的强大功能。