news 2026/10/2 23:10:43

Android Studio源码:记事本备忘录应用,SQLite数据库+登陆注册+增删改查全流程拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android Studio源码:记事本备忘录应用,SQLite数据库+登陆注册+增删改查全流程拆解

1. 从零跑通一个能用的 Android 记事本:SQLite 建表、登录注册与增删改查到底怎么串起来

很多人第一次做 Android 本地应用,卡住的地方往往不是界面画不出来,而是数据存进去读不出来、登录状态传不过去、列表删完不刷新。这个记事本备忘录项目正好把三个最容易踩坑的模块串在一起:SQLite 数据库建表、登录注册校验、备忘录增删改查。它适合刚学完 Activity 和 RecyclerView、想找一个完整闭环练手的人,也适合需要交课程设计或快速搭原型的人。

我试过把这个项目从导入到跑通完整走了一遍,发现真正决定成败的不是代码量,而是几个关键点有没有处理对:数据库版本号写没写对、用户 ID 有没有正确带到备忘录表、Cursor 用完有没有关。下面按实际开发顺序拆开讲,每一步都给可直接复制的代码和验证方法。你不需要先理解所有原理,跟着敲完再回头看,会顺很多。

整个项目结构不复杂,核心就四个类:DBHelper 负责建库建表,LoginActivity 和 RegisterActivity 处理账号,MainActivity 配合 MemoAdapter 展示和操作备忘录。数据库用 SQLite,不依赖任何网络,装到模拟器上就能离线跑。推荐 Android Studio 3.5 以上,模拟器选 Pixel 3 API 29,数据持久化表现最稳。

2. 前置准备:TaoToken 在本地开发调试里的实际作用

这个项目本身是纯本地 SQLite,不联网也能跑。但在实际开发过程中,你大概率会遇到需要查文档、让模型帮你补全 SQL 语句、或者排查 Gradle 报错的情况。这时候如果有一个稳定的模型调用入口,效率会高很多。TaoToken 在这里的角色就是统一管理模型访问,你可以在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 了解它的定位,API 入口是 https://taotoken.net/api。

具体到开发场景,我一般用它做三件事:一是让模型帮我检查 SQLite 建表语句有没有语法问题,二是生成 RecyclerView Adapter 的模板代码,三是排查 Cursor 越界或空指针这类运行时错误。你不需要把它想得很复杂,本质上就是一个可以按需调用的模型服务。

如果你打算长期做 Android 开发或者需要频繁调试,可以看看 Coding Plan 这类方案,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它适合需要持续调用模型辅助编码的场景。只是想临时验证一下模型输出,用模型对话页面就够了:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。

需要说明的是,这个记事本项目不依赖 TaoToken 也能完整运行,它只是开发过程中的辅助工具。你完全可以先跑通项目,再决定要不要接入模型辅助。

3. 可复制配置:DBHelper 建表、登录注册与 CRUD 完整代码

3.1 DBHelper 数据库帮助类

先建数据库。继承 SQLiteOpenHelper,在 onCreate 里建两张表:users 存账号,memos 存备忘录,通过 user_id 关联。注意 DATABASE_VERSION 改成 2 的时候,onUpgrade 要处理旧表,否则模拟器上会报表已存在。

public class DBHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "memo.db"; private static final int DATABASE_VERSION = 2; private static final String CREATE_USER_TABLE = "CREATE TABLE users (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "username TEXT UNIQUE," + "password TEXT)"; private static final String CREATE_MEMO_TABLE = "CREATE TABLE memos (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "user_id INTEGER," + "content TEXT," + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"; public DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_USER_TABLE); db.execSQL(CREATE_MEMO_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS users"); db.execSQL("DROP TABLE IF EXISTS memos"); onCreate(db); } }

这里 created_at 用了 CURRENT_TIMESTAMP 自动记录时间,省得在 Java 层手动拼时间戳。user_id 是外键逻辑关联,SQLite 默认不强制外键约束,所以插入时要自己保证 userId 有效。

3.2 注册与登录方法

注册用 ContentValues 插入,insert 返回 -1 表示失败,通常是用户名重复。登录用 rawQuery 查匹配记录,Cursor 移到第一条说明账号密码正确。

public boolean registerUser(String username, String password) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("username", username); values.put("password", password); long result = db.insert("users", null, values); return result != -1; } public int loginUser(String username, String password) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery( "SELECT id FROM users WHERE username = ? AND password = ?", new String[]{username, password}); int userId = -1; if (cursor.moveToFirst()) { userId = cursor.getInt(cursor.getColumnIndexOrThrow("id")); } cursor.close(); return userId; }

登录返回 userId 而不是 boolean,是因为后面备忘录查询需要按用户隔离。实际项目里密码要做哈希加盐,这里为了演示保持明文,你二次开发时记得改。

3.3 备忘录增删改查

添加用事务保证原子性,insertOrThrow 出错直接抛异常。查询按 user_id 过滤,删除按 id 定位。

public void addMemo(int userId, String content) { SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); try { ContentValues values = new ContentValues(); values.put("user_id", userId); values.put("content", content); db.insertOrThrow("memos", null, values); db.setTransactionSuccessful(); } catch (Exception e) { Log.e("DB_ERROR", "添加备忘失败", e); } finally { db.endTransaction(); } } public Cursor getMemosByUser(int userId) { SQLiteDatabase db = this.getReadableDatabase(); return db.rawQuery( "SELECT id, content, created_at FROM memos WHERE user_id = ? ORDER BY created_at DESC", new String[]{String.valueOf(userId)}); } public void deleteMemo(int memoId) { SQLiteDatabase db = this.getWritableDatabase(); db.delete("memos", "id = ?", new String[]{String.valueOf(memoId)}); } public void updateMemo(int memoId, String newContent) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("content", newContent); db.update("memos", values, "id = ?", new String[]{String.valueOf(memoId)}); }

查询返回 Cursor 而不是 List,是为了配合 CursorAdapter 或手动绑定 RecyclerView。注意 Cursor 用完必须 close,否则反复查询会内存泄漏。

3.4 RecyclerView 适配器绑定 Cursor

适配器里用 swapCursor 更新数据,onBindViewHolder 里 moveToPosition 后取值。

public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.MemoViewHolder> { private Cursor cursor; public void swapCursor(Cursor newCursor) { if (cursor != null) cursor.close(); cursor = newCursor; notifyDataSetChanged(); } @Override public void onBindViewHolder(MemoViewHolder holder, int position) { cursor.moveToPosition(position); String content = cursor.getString(cursor.getColumnIndexOrThrow("content")); holder.tvContent.setText(content); } @Override public int getItemCount() { return cursor == null ? 0 : cursor.getCount(); } static class MemoViewHolder extends RecyclerView.ViewHolder { TextView tvContent; MemoViewHolder(View itemView) { super(itemView); tvContent = itemView.findViewById(R.id.tv_content); } } }

swapCursor 里先关旧 Cursor 再赋值,这是防止内存泄漏的关键。notifyDataSetChanged 会触发列表重绘。

4. 验证请求与成功结果:模拟器上逐项确认数据读写

代码敲完后,在模拟器上按顺序验证。先跑注册:输入用户名 test、密码 123456,点注册,如果返回登录页说明 users 表插入成功。再登录,进入主界面。此时可以打开 Android Studio 的 Device File Explorer,路径 data/data/你的包名/databases/memo.db,导出后用 SQLite 工具打开,确认 users 表里有记录。

接着添加备忘录:输入一段文字点保存,列表应该立刻出现新条目。下拉或重启 Activity 后数据还在,说明持久化成功。删除一条,列表刷新后少一条。修改一条,内容更新。每一步都可以在 Device File Explorer 里重新导出数据库对照。

如果你在开发过程中用 TaoToken 辅助排查,可以在控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 查看调用记录,API Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 管理。需要查接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

验证成功的标准很简单:注册后能登录、登录后能看到自己的备忘录、增删改后列表和数据库一致、退出重进数据不丢。四项都过,说明整套链路通了。

5. 常见报错排查:401、local proxy failed、reading choices 与 OAuth 对照

这个项目本身不涉及网络请求,但如果你在开发中接入了模型辅助或其它 API,可能会遇到几类典型报错。下面按真实场景对照。

401 Unauthorized:通常是 API Key 没填对或过期。检查请求头里的 Authorization 字段,确认 Key 没有多余空格。如果你用的是 TaoToken,去 API Keys 页面重新生成一个再试。

local proxy failed:本地代理配置问题。检查你的网络设置,确认没有残留的代理配置指向不存在的端口。Android Studio 的 Gradle 代理在 gradle.properties 里,检查 systemProp.http.proxyHost 这类字段是否该删。

reading choices 报错:一般是模型返回结构和你解析的字段不匹配。确认你请求的接口版本和返回格式,choices 数组里取 message.content 而不是直接取 text。

OAuth 相关错误:如果你接的是需要 OAuth 的服务,检查 client_id、redirect_uri 是否和注册时一致。Android 里 redirect_uri 常用自定义 scheme,确认 AndroidManifest 里 intent-filter 配对了。

另外这个项目本身最常见的报错是表已存在,原因是 DATABASE_VERSION 改了但 onUpgrade 没写,或者模拟器里旧数据库没删。解决办法是卸载重装应用,或者在 onUpgrade 里先 DROP 再 CREATE。

如果你用 Claude Code 做辅助开发,接入时三件套要写全:Base URL 填 https://taotoken.net/api,Key 填你生成的,Model ID 按文档填对应模型名。缺一个都会报错。Claude Code 的接入说明在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claude-code&utm_campaign=rewrite 。

6. 继续扩展与长期开发建议

跑通基础版之后,你可以按几个方向扩展:给备忘录加分类标签,在 memos 表加 category 字段;加时间轴展示,用 strftime 格式化 created_at;加搜索功能,用 LIKE 查询 content。这些都不需要改表结构太多,属于增量开发。

如果你打算长期做 Android 或者需要频繁让模型辅助写代码、查报错,Coding Plan 会比单次调用更省心,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。日常验证模型输出用模型对话就行:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。

最后提醒一个实际开发中的习惯:每次改完数据库版本号,先在模拟器上卸载应用再跑,避免旧表结构干扰。Cursor 用完立刻 close,RecyclerView 的 swapCursor 里先关旧再赋新。这两条能帮你省掉大部分诡异 bug。项目源码导入后如果 Gradle 报版本不匹配,改 gradle-wrapper.properties 里的 distributionUrl 到本地已有版本即可。

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

PHP的array_slice函数截取数组时偏移量怎么计算才准确

前言array_slice() 大概是「看一眼就会、用起来就错」的典型函数。它只有四个参数&#xff0c;但每一个都有正负号、每一个都有边界情况&#xff0c;叠在一起就成了一个小型的状态机。你很可能遇到过下面这些现象&#xff1a;分页列表第一页少了第一条&#xff0c;或者第二页重…

作者头像 李华
网站建设 2026/10/2 22:59:13

VSCode配置C/C++开发环境:编译、调试、智能提示全链路指南

简介&#xff1a;本资源是一套开箱即用的VSCode C/C开发环境配置方案&#xff0c;面向初学者及中级开发者&#xff0c;解决Windows平台下VSCode无法直接编译调试C/C程序的核心痛点。资源包含25个文件&#xff0c;以9个JSON配置文件&#xff08;如c_cpp_properties.json、tasks.…

作者头像 李华
网站建设 2026/10/2 22:57:27

Linux内核图解笔记:从手写认知建模到工程级调试能力

1. 这份“狗剩笔记”到底是什么&#xff1a;一份被低估的Linux学习原始素材“2021韩顺平图解linux_狗剩学习笔记”——这个标题在技术社区里常被当作一个模糊的搜索关键词&#xff0c;甚至带点调侃意味。但如果你真去翻过它&#xff0c;会发现它根本不是什么“盗版课件”或“速…

作者头像 李华
网站建设 2026/10/2 22:56:57

工业Agent与实时控制:边界、落地与工程实践

1. 先搞清楚大家在争什么&#xff1a;工业Agent与实时控制的边界 "实时控制的工业Agent"这个说法&#xff0c;最近一年在圈子里被反复提起。做AI的人觉得这是下一个爆发点&#xff0c;做工业自动化的人听完往往只是笑笑。我两边都待过&#xff0c;既写过梯形图&#…

作者头像 李华
网站建设 2026/10/2 22:55:49

OpenRig:面向本地大模型推理的命令行编排工具详解

1. 项目概述&#xff1a;OpenRig 是什么&#xff0c;它解决的到底是什么问题 OpenRig 这个名字在当前技术社区里出现得越来越频繁&#xff0c;但很多人第一次看到时会下意识把它和“挖矿 rig&#xff08;矿机&#xff09;”或者“硬件测试平台”联系起来——这其实是个典型的误…

作者头像 李华