news 2026/10/15 11:15:52

Android备忘录源码实战:SQLite本地存储与增删改查避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android备忘录源码实战:SQLite本地存储与增删改查避坑指南

简介:这份Android备忘录源码压缩包面向具备一定Android基础的开发者与移动应用学习者,提供一套可直接研读的完整项目实例,用于理解备忘录类应用的工程组织与核心功能实现。包内共190个文件,以74个png界面切图、61个class编译产物、22个xml布局与配置、20个java源码为主,另含prefs偏好设置、ogg提示音、apk与dex等运行文件,整体约1.29MB,体量轻便,便于快速导入Android Studio对照分析。源码覆盖Activity入口、SQLite数据库持久化、Adapter列表适配、ViewModel数据保持、Notification提醒以及AndroidManifest权限声明等典型模块,并附带drawable、values、styles等UI资源,可帮助读者梳理从界面布局到数据存储的完整链路。目前已有1802人学习下载,适合作为课程设计、练手项目或架构入门的参考素材,通过拆解目录结构与关键类,逐步掌握Android应用的构建思路与调试方法。

1. 从一份备忘录源码说起:Android 本地存储到底该怎么写

很多人第一次拿到「Android代码-备忘录源码.zip」这种资源,第一反应是解压、导入、跑起来,然后盯着界面看两眼就放下了。但真正值得看的不是界面,而是它怎么把一条条笔记落到本地、怎么在 Activity 重建时不丢数据、怎么在列表刷新时不出错。备忘录这个场景看着简单,实际上把 Android 四大组件、SQLite、RecyclerView、权限、生命周期全串了一遍,是练手本地存储最合适的载体。我见过不少同学能背出 SQLiteOpenHelper 的四个回调,却写不出一个能稳定增删改查的备忘录,问题就出在没把「数据流」和「生命周期」对齐。这篇笔记就按一份典型备忘录源码的结构,把能复现的路径、参数和踩坑点讲清楚,适合刚学完基础组件想找个完整小项目练手的开发者,也适合想回头补本地存储基本功的熟手。

2. 备忘录源码的骨架:先看清数据从哪来到哪去

一份能跑的备忘录源码,通常不会只有一个 Activity。它至少包含三个层次:界面层负责展示和接收输入,数据层负责持久化,模型层负责在两者之间传递。很多人导入源码后直接改布局,改完发现数据对不上,就是因为没先理清这三层怎么连。

2.1 三个核心类:Activity、Adapter、SQLiteOpenHelper

界面层一般是一个列表 Activity 加一个编辑 Activity。列表 Activity 持有 RecyclerView 和 Adapter,编辑 Activity 负责接收标题和内容,点保存后写库并通知列表刷新。数据层是一个继承 SQLiteOpenHelper 的类,里面定义建表语句和增删改查方法。模型层就是一个 Note 实体类,字段通常有 id、title、content、createTime、updateTime。

先看模型类,它决定了后面所有方法的参数形态:

public class Note { private long id; private String title; private String content; private long createTime; private long updateTime; // 无参构造给查询结果用,带参构造给新建用 public Note() {} public Note(String title, String content) { this.title = title; this.content = content; this.createTime = System.currentTimeMillis(); this.updateTime = this.createTime; } // getter/setter 省略 }

这里有个容易忽略的点:createTime 和 updateTime 分开存。很多新手只存一个时间,结果编辑后排序就乱了。分开存之后,列表默认按 updateTime 倒序,最近改过的排最前,符合备忘录的使用直觉。

再看 SQLiteOpenHelper,建表语句和版本号是重点:

public class NoteDbHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; private static final int DB_VERSION = 1; public NoteDbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE note (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "title TEXT NOT NULL, " + "content TEXT, " + "create_time INTEGER, " + "update_time INTEGER)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 版本升级时先删表再重建,仅适合开发阶段 db.execSQL("DROP TABLE IF EXISTS note"); onCreate(db); } }

参数说明:DB_VERSION 从 1 开始,每次改表结构就加 1,否则 onUpgrade 不会触发。onUpgrade 里直接删表是开发期做法,上线后必须改成 ALTER TABLE 迁移,不然用户数据全丢。id 用 AUTOINCREMENT 保证删除后新增不会复用旧 id,避免列表错位。

2.2 增删改查四个方法的参数怎么定

数据层的方法签名直接决定调用方怎么写。常见做法是每个方法接收 Note 对象或 id,返回 long 或 List。插入返回新行 id,更新和删除返回受影响行数,查询返回列表。

public long insert(Note note) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put("title", note.getTitle()); values.put("content", note.getContent()); values.put("create_time", note.getCreateTime()); values.put("update_time", note.getUpdateTime()); return db.insert("note", null, values); } public List<Note> queryAll() { List<Note> list = new ArrayList<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query("note", null, null, null, null, null, "update_time DESC"); while (cursor.moveToNext()) { Note note = new Note(); note.setId(cursor.getLong(cursor.getColumnIndexOrThrow("id"))); note.setTitle(cursor.getString(cursor.getColumnIndexOrThrow("title"))); note.setContent(cursor.getString(cursor.getColumnIndexOrThrow("content"))); note.setUpdateTime(cursor.getLong(cursor.getColumnIndexOrThrow("update_time"))); list.add(note); } cursor.close(); return list; }

逻辑说明:insert 用 ContentValues 而不是拼 SQL 字符串,避免注入和转义问题。queryAll 的排序参数写 "update_time DESC",让最近修改的排前面。cursor 用完必须 close,否则反复查询会泄漏。getColumnIndexOrThrow 比 getColumnIndex 好,列名写错时直接抛异常,不会静默返回 -1。

2.3 列表刷新:为什么改完数据界面没变

Adapter 的数据源是一个 List,查询返回新 List 后,如果直接赋值给成员变量而不通知 Adapter,界面不会更新。正确做法是清空再加、然后 notifyDataSetChanged,或者用 DiffUtil 做增量更新。

private void reloadNotes() { noteList.clear(); noteList.addAll(dbHelper.queryAll()); adapter.notifyDataSetChanged(); }

注意:notifyDataSetChanged 会重绘整个列表,数据量大时卡顿。备忘录条目一般不多,够用。如果条目上千,换成 DiffUtil 或 ListAdapter。另外,编辑 Activity 保存后要通知列表 Activity 刷新,常见做法是用 startActivityForResult 或 ActivityResultLauncher,在回调里调 reloadNotes。

3. 把源码跑起来:从导入到第一条笔记落库

理清骨架后,动手跑一遍才能发现细节问题。这一章按导入、建库、写第一条、验证四步走,每步都给可抄的命令或代码。

3.1 导入前的环境检查与 Gradle 配置

导入前先确认本地 Android Studio 版本和 Gradle 插件版本匹配。源码里的 build.gradle 如果写的是老版本插件,直接同步会报错。常见做法是打开项目级 build.gradle,把插件版本改成和本地 Studio 匹配的版本,再同步。

# 查看本地已安装的 Gradle 版本 gradle -v # 如果项目用的是 wrapper,直接跑 wrapper 命令 ./gradlew tasks

参数说明:gradlew 是项目自带的 wrapper,版本由 gradle-wrapper.properties 里的 distributionUrl 决定。如果下载慢,可以换成国内镜像地址,但要注意镜像的 Gradle 版本要和插件兼容。同步失败时先看 Build 窗口的报错行,多数是 compileSdk 或 targetSdk 写得太新或太旧。

3.2 建库与建表:onCreate 只跑一次这件事

SQLiteOpenHelper 的 onCreate 只在数据库文件不存在时调用一次。如果第一次跑的时候建表语句写错,后面改对了也不会重新执行,因为文件已经存在。血泪经验:调试阶段改表结构,要么改 DB_VERSION 触发 onUpgrade,要么在设备上卸载应用重装。

// 调试期可以在 Application 或主 Activity 里临时删库 context.deleteDatabase("note.db");

注意:这行只能放调试代码里,上线前删掉。删库后下次 getWritableDatabase 会重新走 onCreate。

3.3 写第一条笔记:插入与查询的完整链路

在编辑 Activity 里点保存,走的是「取输入 → 构造 Note → 调 insert → 关页面 → 通知刷新」这条链路。

saveBtn.setOnClickListener(v -> { String title = titleEt.getText().toString().trim(); String content = contentEt.getText().toString().trim(); if (title.isEmpty()) { Toast.makeText(this, "标题不能为空", Toast.LENGTH_SHORT).show(); return; } Note note = new Note(title, content); long id = dbHelper.insert(note); if (id > 0) { setResult(RESULT_OK); finish(); } else { Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show(); } });

逻辑说明:title 做非空校验,content 允许为空。insert 返回 -1 表示失败,通常是约束冲突或磁盘问题。setResult 配合列表 Activity 的 launcher 回调触发刷新。这里没在主线程做耗时查询,insert 本身很快,但如果有大量数据或复杂事务,要放到子线程。

3.4 用 App Inspection 验证数据真的落库了

Android Studio 自带 Database Inspector,可以在应用运行时直接看 note.db 里的数据。打开方式:View → Tool Windows → App Inspection → Database Inspector,选中进程后展开 databases。

检查项正常表现异常表现与排查
数据库文件出现 note.db没有文件说明 getWritableDatabase 没调用
note 表表结构含 5 个字段字段缺失说明 onCreate 没执行或版本没升
数据行新增后多一行行数不变说明 insert 返回 -1
update_time每次编辑后变大不变说明更新方法没写 update_time

这个表建议对照着看,比盲猜快得多。如果 Database Inspector 连不上,先确认设备是 API 26 以上,低版本不支持。

4. 避坑与排查:备忘录源码里最容易翻车的五处

跑通之后,真正花时间的是那些「看着对但就是不对」的问题。下面五条是我在类似项目里反复遇到的,按现象、原因、解决写清楚。

4.1 旋转屏幕后输入框内容消失

现象:在编辑页输入一半,旋转屏幕,标题和内容全没了。原因:Activity 重建,成员变量被重置,而 EditText 的内容如果没有保存状态就会丢。解决:给 EditText 加 android:id,系统会自动保存;或者在 onSaveInstanceState 里手动存,onCreate 里恢复。

@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("title", titleEt.getText().toString()); outState.putString("content", contentEt.getText().toString()); }

注意:手动保存要在 setContentView 之后、恢复逻辑之前理清顺序,否则取到空控件。

4.2 列表点击编辑后数据错位

现象:点第 3 条编辑,改完保存,结果第 1 条变了。原因:Adapter 里用 position 当 id 传给编辑页,但列表排序变了或数据刷新后 position 和 id 不再对应。解决:传 note.getId(),编辑页按 id 查询和更新,不要用 position。

// 错误做法 intent.putExtra("position", position); // 正确做法 intent.putExtra("note_id", noteList.get(position).getId());

4.3 数据库版本升级后应用崩溃

现象:改了表结构,DB_VERSION 加 1,运行后查询报 no such column。原因:onUpgrade 里只删了表没重建,或者重建语句和查询字段不一致。解决:onUpgrade 里先 DROP 再 onCreate,并确保查询用的列名和新表一致。上线版本要用 ALTER TABLE 逐版本迁移,不能删表。

4.4 主线程查询导致列表卡顿

现象:笔记多了以后,打开列表页要等一两秒才显示。原因:queryAll 在主线程执行,数据量大时阻塞 UI。解决:把查询放到子线程,用 ExecutorService 或 Kotlin 协程,查完再切主线程更新 Adapter。

ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { List<Note> notes = dbHelper.queryAll(); runOnUiThread(() -> { noteList.clear(); noteList.addAll(notes); adapter.notifyDataSetChanged(); }); });

参数说明:newSingleThreadExecutor 保证查询串行,避免并发写库冲突。runOnUiThread 把结果切回主线程更新 UI。如果项目用 Kotlin,直接 viewModelScope + Dispatchers.IO 更简洁。

4.5 忘记关闭 Cursor 导致内存泄漏

现象:反复进出列表页,内存占用持续上涨。原因:Cursor 没 close,底层游标资源没释放。解决:用 try-finally 包住,或者用 try-with-resources(API 16 以上 Cursor 实现了 Closeable)。

try (Cursor cursor = db.query("note", null, null, null, null, null, "update_time DESC")) { while (cursor.moveToNext()) { // 读取数据 } }

注意:try-with-resources 会自动 close,比手动写 finally 更稳。如果查询方法返回 Cursor 给调用方,关闭责任要明确,别两边都不关。

5. 从能跑到好用:三个让备忘录更稳的技巧

跑通和排查之后,如果还想把这个方向做扎实,有三个技巧值得花时间。它们不改变功能,但决定这个备忘录能不能长期用。

第一个是给数据库加索引。备忘录列表按 update_time 排序,数据量上千后排序会变慢。在 onCreate 里加一句 CREATE INDEX idx_update_time ON note(update_time),查询排序就走索引。索引不是越多越好,写操作会变慢,备忘录这种读多写少的场景加一个排序索引刚好。

db.execSQL("CREATE INDEX idx_update_time ON note(update_time)");

第二个是用事务包住批量操作。如果以后加导入功能,一次插几百条,逐条 insert 会频繁开事务,慢且容易中途失败。用 beginTransaction 和 setTransactionSuccessful 包起来,要么全成功要么全回滚。

db.beginTransaction(); try { for (Note note : notes) { insert(note); } db.setTransactionSuccessful(); } finally { db.endTransaction(); }

第三个是给查询加限制。列表页一次只显示 50 条,滑到底再加载更多。query 方法的 limit 参数写 "50",配合 offset 做分页。这样即使有几千条笔记,打开列表也是秒开。分页的 offset 用 (page - 1) * pageSize 算,page 从 1 开始。

技巧适用场景关键参数效果
加索引列表排序变慢update_time 列排序查询走索引
用事务批量插入/更新beginTransaction减少 IO,保证原子性
分页查询数据量上千limit + offset首屏加载快

最后说个我自己的习惯:每次改完数据层方法,先在 Database Inspector 里手动跑一遍增删改查,确认数据对得上,再去改界面。界面问题十有八九是数据层没写对,先看数据能省很多来回。备忘录源码的价值不在界面多好看,而在它把本地存储这条链路完整暴露出来,照着改一遍、踩一遍坑,比看十篇原理文章都管用。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 11:15:51

Android备忘录源码解析:SQLite持久化与RecyclerView增删改查实战

简介&#xff1a;这份Android备忘录源码压缩包面向具备一定Android基础的开发者与移动应用学习者&#xff0c;提供一套可直接研读的完整项目实例&#xff0c;帮助理解备忘录类应用的架构设计与核心功能实现。包内共190个文件&#xff0c;以74个png界面素材、61个class编译产物、…

作者头像 李华
网站建设 2026/10/15 11:15:51

aardvark-api-windows-x86_64-v5.15.zip 硬件调试避坑指南

简介&#xff1a;aardvark-api-windows-x86_64-v5.15.zip 是 Total Phase 公司为 Aardvark 适配器提供的官方 API 与驱动工具包&#xff0c;面向在 Windows 64 位平台上进行 I2C/SPI 总线调试的嵌入式工程师与电子开发者。它解决了主机与适配器之间高效通信、总线读写与从设备模…

作者头像 李华
网站建设 2026/10/15 11:15:37

Qt集成VLC库:libvlc播放器开发从基础到避坑实战

简介&#xff1a;基于Qt框架与VLC库打造的播放器DEMO源码&#xff0c;适合有一定C基础、想深入掌握Qt GUI编程与libvlc集成的开发者。项目演示了从界面构建到媒体播放的完整流程&#xff0c;包括按钮事件绑定、进度条同步、音量控制及后台播放线程设计&#xff0c;帮助理解事件…

作者头像 李华
网站建设 2026/10/15 11:15:20

Windows下libcurl静态库配置与HTTP请求实战指南

简介&#xff1a;这份资源是面向Windows平台C网络开发者的自用库文件合集&#xff0c;打包了libcurl、ws2_32与winmm三个常用链接库&#xff0c;主要解决网络通信程序在编译链接阶段缺少依赖的问题。包内共23个文件&#xff0c;以h头文件、lib静态库、dll动态库及in、am、cmake…

作者头像 李华
网站建设 2026/10/15 11:14:59

从Kubernetes单体教训到AI智能体运行环境:如何避免重蹈覆辙

我最近帮一个朋友排查他搭的AI智能体平台&#xff0c;问题不大不小&#xff1a;他在某个Agent里加了一个文件搜索工具&#xff0c;结果改完配置一保存&#xff0c;整个运行环境要重启&#xff0c;所有正在跑的对话任务全部断掉。那一刻我突然觉得特别眼熟——这场景跟我早年维护…

作者头像 李华