1. 项目概述:基于SQLite的安卓本地备忘录应用
这个纯安卓项目使用Android Studio开发,核心功能是通过SQLite数据库实现一个本地存储的记事本应用。不同于依赖网络服务的云笔记,它专注于离线场景下的快速记录与数据安全,适合需要轻量级、低延迟且隐私性强的备忘录需求。
从技术实现来看,项目采用了经典的MVP(Model-View-Presenter)架构模式。数据层使用Android原生SQLite数据库进行CRUD操作,视图层通过RecyclerView展示条目列表,业务逻辑层处理用户交互与数据持久化的衔接。这种设计保证了代码的可维护性和扩展性,比如未来可以轻松添加分类标签或搜索功能。
提示:选择SQLite而非Room等ORM框架,既降低了学习成本,也更适合作为教学项目展示安卓基础存储机制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建与项目初始化
2.1 Android Studio配置要点
建议使用最新稳定版Android Studio(当前为Giraffe 2022.3.1),安装时注意勾选以下组件:
- Android SDK Platform 33+
- Intel x86 Emulator Accelerator (HAXM installer)
- Android Emulator
首次创建项目时需注意:
- 选择Empty Activity模板
- 语言选Java(保持与大多数教学资源一致)
- Minimum SDK设为API 24(覆盖90%以上设备)
- 勾选Use legacy android.support libraries
2.2 关键依赖配置
在app/build.gradle中添加必要依赖:
groovy复制dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.cardview:cardview:1.0.0'
}
同步后检查Gradle构建是否报错,常见问题包括:
- 代理设置导致下载失败(需检查gradle.properties)
- JDK版本不兼容(建议使用JDK11)
- 缓存冲突(可尝试./gradlew clean)
3. SQLite数据库设计与实现
3.1 数据库表结构设计
创建notes表存储备忘录条目:
sql复制CREATE TABLE notes (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
关键设计考量:
- 自增主键_id作为唯一标识
- 设置NOT NULL约束避免空标题
- 自动记录创建和修改时间
- 没有使用外键关联保持简单性
3.2 数据库帮助类实现
继承SQLiteOpenHelper创建DatabaseHelper类:
java复制public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "notes.db";
private static final int DATABASE_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE notes (...)"); // 使用前述SQL
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
注意:实际生产环境应该实现更精细的onUpgrade逻辑,避免数据丢失。本例为简化直接重建表。
4. 核心功能实现细节
4.1 备忘录条目增删改查
实现NoteDAO数据访问对象类,封装CRUD操作:
java复制public class NoteDAO {
public long insertNote(SQLiteDatabase db, String title, String content) {
ContentValues values = new ContentValues();
values.put("title", title);
values.put("content", content);
return db.insert("notes", null, values);
}
public int updateNote(SQLiteDatabase db, long id, String title, String content) {
ContentValues values = new ContentValues();
values.put("title", title);
values.put("content", content);
values.put("updated_time", "datetime('now')");
return db.update("notes", values, "_id=?", new String[]{String.valueOf(id)});
}
public int deleteNote(SQLiteDatabase db, long id) {
return db.delete("notes", "_id=?", new String[]{String.valueOf(id)});
}
public Cursor getAllNotes(SQLiteDatabase db) {
return db.query("notes",
new String[]{"_id", "title", "content", "created_time"},
null, null, null, null,
"updated_time DESC");
}
}
4.2 RecyclerView适配器实现
创建NoteAdapter处理列表展示:
java复制public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.ViewHolder> {
private Cursor mCursor;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(long noteId);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView titleView;
public TextView timeView;
public ViewHolder(View itemView) {
super(itemView);
titleView = itemView.findViewById(R.id.note_title);
timeView = itemView.findViewById(R.id.note_time);
}
}
public NoteAdapter(Cursor cursor, OnItemClickListener listener) {
mCursor = cursor;
mListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_note, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
mCursor.moveToPosition(position);
holder.titleView.setText(mCursor.getString(1));
holder.timeView.setText(
DateUtils.formatDateTime(holder.itemView.getContext(),
mCursor.getLong(3),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
holder.itemView.setOnClickListener(v -> {
mListener.onItemClick(mCursor.getLong(0));
});
}
@Override
public int getItemCount() {
return mCursor != null ? mCursor.getCount() : 0;
}
public void swapCursor(Cursor newCursor) {
if (mCursor != null) mCursor.close();
mCursor = newCursor;
notifyDataSetChanged();
}
}
5. 界面设计与用户交互
5.1 主界面布局设计
使用CoordinatorLayout实现带悬浮按钮的列表布局:
xml复制<androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_add"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
5.2 编辑界面交互流程
实现NoteEditActivity处理创建/编辑:
java复制public class NoteEditActivity extends AppCompatActivity {
private EditText mTitleEdit;
private EditText mContentEdit;
private long mNoteId = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_edit);
mTitleEdit = findViewById(R.id.edit_title);
mContentEdit = findViewById(R.id.edit_content);
mNoteId = getIntent().getLongExtra("note_id", -1);
if (mNoteId != -1) {
// 编辑现有笔记
SQLiteDatabase db = new DatabaseHelper(this).getReadableDatabase();
Cursor cursor = db.query("notes",
new String[]{"title", "content"},
"_id=?",
new String[]{String.valueOf(mNoteId)},
null, null, null);
if (cursor.moveToFirst()) {
mTitleEdit.setText(cursor.getString(0));
mContentEdit.setText(cursor.getString(1));
}
cursor.close();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_note_edit, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_save) {
saveNote();
return true;
}
return super.onOptionsItemSelected(item);
}
private void saveNote() {
String title = mTitleEdit.getText().toString().trim();
if (TextUtils.isEmpty(title)) {
Toast.makeText(this, "标题不能为空", Toast.LENGTH_SHORT).show();
return;
}
SQLiteDatabase db = new DatabaseHelper(this).getWritableDatabase();
ContentValues values = new ContentValues();
values.put("title", title);
values.put("content", mContentEdit.getText().toString());
if (mNoteId == -1) {
db.insert("notes", null, values);
} else {
db.update("notes", values, "_id=?",
new String[]{String.valueOf(mNoteId)});
}
finish();
}
}
6. 性能优化与调试技巧
6.1 数据库操作优化
- 使用事务批量操作:
java复制db.beginTransaction();
try {
// 批量操作
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
- 索引优化:
sql复制CREATE INDEX idx_notes_time ON notes(updated_time);
- 避免内存泄漏:
- 在Activity的onDestroy()中关闭Cursor
- 使用WeakReference持有Context
6.2 常见问题排查
- 数据库锁定问题:
- 确保每次操作后关闭数据库连接
- 使用单例模式管理数据库实例
- UI更新延迟:
- 在onResume()中异步加载数据
- 使用LoaderManager自动管理Cursor生命周期
- 数据不同步:
- 实现ContentObserver监听数据变化
- 使用EventBus通知列表更新
7. 功能扩展方向
7.1 基础功能增强
- 分类标签系统:
- 新增tags表存储标签
- 使用多对多关联表note_tags建立关系
- 实现标签云界面
- 富文本支持:
- 集成Markdown编辑器
- 添加图片附件功能
- 回收站机制:
- 添加is_deleted字段软删除
- 定期清理过期项目
7.2 高级特性实现
- 本地备份恢复:
- 使用JSON格式导出数据
- 实现文件选择器接口
- 指纹/面部解锁:
- 集成Biometric API
- 加密存储敏感笔记
- 桌面小部件:
- 开发AppWidgetProvider
- 显示最近编辑的笔记
这个项目的完整源码已经托管在GitHub上,包含详细的代码注释和实现文档。通过这个案例,我们不仅掌握了SQLite的核心用法,还实践了安卓开发中的多项最佳实践,包括资源管理、线程优化和模块化设计。建议读者在理解基础实现后,可以尝试自己添加上述扩展功能,逐步构建一个功能完善的个人知识管理系统。
