
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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 了解它的定位API 入口是 https://taotoken.net/api。具体到开发场景我一般用它做三件事一是让模型帮我检查 SQLite 建表语句有没有语法问题二是生成 RecyclerView Adapter 的模板代码三是排查 Cursor 越界或空指针这类运行时错误。你不需要把它想得很复杂本质上就是一个可以按需调用的模型服务。如果你打算长期做 Android 开发或者需要频繁调试可以看看 Coding Plan 这类方案地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它适合需要持续调用模型辅助编码的场景。只是想临时验证一下模型输出用模型对话页面就够了https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。需要说明的是这个记事本项目不依赖 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.AdapterMemoAdapter.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_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 查看调用记录API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 管理。需要查接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。验证成功的标准很简单注册后能登录、登录后能看到自己的备忘录、增删改后列表和数据库一致、退出重进数据不丢。四项都过说明整套链路通了。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/apiKey 填你生成的Model ID 按文档填对应模型名。缺一个都会报错。Claude Code 的接入说明在 https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-codeutm_campaignrewrite 。6. 继续扩展与长期开发建议跑通基础版之后你可以按几个方向扩展给备忘录加分类标签在 memos 表加 category 字段加时间轴展示用 strftime 格式化 created_at加搜索功能用 LIKE 查询 content。这些都不需要改表结构太多属于增量开发。如果你打算长期做 Android 或者需要频繁让模型辅助写代码、查报错Coding Plan 会比单次调用更省心地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。日常验证模型输出用模型对话就行https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。最后提醒一个实际开发中的习惯每次改完数据库版本号先在模拟器上卸载应用再跑避免旧表结构干扰。Cursor 用完立刻 closeRecyclerView 的 swapCursor 里先关旧再赋新。这两条能帮你省掉大部分诡异 bug。项目源码导入后如果 Gradle 报版本不匹配改 gradle-wrapper.properties 里的 distributionUrl 到本地已有版本即可。