ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

在 Android 应用中使用数据库:SQLite 配置与 TaoToken 接入实践

在 Android 应用中使用数据库:SQLite 配置与 TaoToken 接入实践 1. Android 本地数据库为什么总在真机上翻车Android 应用里的 SQLite 数据库说白了就是 App 自带的一个小仓库不需要联网、不需要额外装服务建库建表、增删改查全在手机本地完成。它适合谁适合做离线记账、宠物档案、待办清单、设备日志缓存这类“数据量不大但要求随时可读可写”的场景。我见过太多新手在模拟器上跑得好好的一换真机就报no such table或者attempt to write a readonly database根因往往不是 SQLite 本身而是建表语句、版本号、Context 三者没对齐。这篇就按“能跟做”的标准来先交付一份可复制的SQLiteOpenHelper配置片段再补一个settings.json骨架最后用具体命令验证查询是否生效并完成一次 AI 辅助的 SQL 校验。整个过程围绕 Android、数据库、SQLite 三个关键词展开不绕弯子。需要提前说明的是AI 辅助这一环我走的是统一 Key/API 通道也就是 TaoToken。它的作用是把模型调用收敛到一个入口省得你在多个平台之间来回切 Key。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 后面配置里会用到。2. 前置准备TaoToken Key 与项目骨架在写数据库代码之前先把两件事定下来一是 Android 项目本身能编译运行二是 AI 通道的 Key 拿到手。数据库部分不依赖网络但 AI 辅助生成 SQL 和排查报错需要它。2.1 获取统一 Key进入控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建后复制那串以sk-开头的字符串先存到本地临时文件里别直接硬编码进 Git 仓库。如果你后面要做长期编码或 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。只是偶尔校验 SQL 的话用按量计费的 Key 就够了。2.2 settings.json 骨架很多同学问“Key 放哪”我一般建议放在项目根目录一个不进版本控制的settings.json里再由构建脚本或本地读取逻辑注入。骨架如下{ ai: { provider: taotoken, baseUrl: https://taotoken.net/api, apiKey: sk-替换成你自己的Key, model: claude-sonnet-4-20250514, timeoutMs: 30000 }, database: { name: shelter.db, version: 1 } }注意baseUrl后面不要多加/v1之类的后缀按文档给的根地址填。model字段按你实际可用的模型名替换这里只是示例。把这个文件加进.gitignore避免 Key 泄露。2.3 依赖与权限SQLite 是 Android 框架自带的不需要额外 Gradle 依赖。如果你要用网络请求调 AI记得在AndroidManifest.xml里加uses-permission android:nameandroid.permission.INTERNET /数据库读写本身不需要任何权限这点和外部存储不一样别被网上老教程带偏。3. 可复制配置Contract 类与 SQLiteOpenHelper这一章是全文的技术核心篇幅会给足。按 Android 官方推荐的两步走先定义 schema 和 contract再用SQLiteOpenHelper建库。3.1 为什么要有 Contract 类直接拼 SQL 字符串最容易出的错就是列名拼写不一致比如建表时写breed查询时写成breeds编译期不报错运行期直接崩。Contract 类把表名、列名都收成常量改一处即可全局生效。三个理由定义架构、避免拼写错误、方便升级。3.2 PetContract 完整代码在项目主包下新建data包创建PetContract.javapackage com.example.pets.data; import android.provider.BaseColumns; public final class PetContract { private PetContract() {} public static final class PetEntry implements BaseColumns { public static final String TABLE_NAME pets; public static final String _ID BaseColumns._ID; public static final String COLUMN_PET_NAME name; public static final String COLUMN_PET_BREED breed; public static final String COLUMN_PET_GENDER gender; public static final String COLUMN_PET_WEIGHT weight; public static final int GENDER_UNKNOWN 0; public static final int GENDER_MALE 1; public static final int GENDER_FEMALE 2; } }这里implements BaseColumns会自动带上_id列省得自己声明主键常量。性别用整数枚举0/1/2 分别对应未知、公、母比存字符串省空间也更好校验。3.3 PetDbHelper 完整代码同包下创建PetDbHelper.java继承SQLiteOpenHelperpackage com.example.pets.data; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.example.pets.data.PetContract.PetEntry; public class PetDbHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME shelter.db; private static final int DATABASE_VERSION 1; private static final String SQL_CREATE_PETS_TABLE CREATE TABLE PetEntry.TABLE_NAME ( PetEntry._ID INTEGER PRIMARY KEY AUTOINCREMENT, PetEntry.COLUMN_PET_NAME TEXT NOT NULL, PetEntry.COLUMN_PET_BREED TEXT, PetEntry.COLUMN_PET_GENDER INTEGER NOT NULL, PetEntry.COLUMN_PET_WEIGHT INTEGER NOT NULL DEFAULT 0);; private static final String SQL_DELETE_PETS_TABLE DROP TABLE IF EXISTS PetEntry.TABLE_NAME; public PetDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_PETS_TABLE); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_PETS_TABLE); onCreate(db); } }几个关键点DATABASE_VERSION一旦改动 schema 就必须加一否则onUpgrade不触发onUpgrade里我用了最粗暴的删表重建生产环境要改成ALTER TABLE保留数据AUTOINCREMENT保证_id单调递增。3.4 增删改查骨架在 Activity 里拿到可写数据库对象插入一条数据PetDbHelper mDbHelper new PetDbHelper(this); SQLiteDatabase db mDbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(PetEntry.COLUMN_PET_NAME, Garfield); values.put(PetEntry.COLUMN_PET_BREED, Tabby); values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE); values.put(PetEntry.COLUMN_PET_WEIGHT, 7); long newRowId db.insert(PetEntry.TABLE_NAME, null, values);insert返回新行 ID出错返回 -1这个返回值一定要接住判断。查询用query而不是rawQuery让框架帮你拼 WHEREString[] projection { PetEntry._ID, PetEntry.COLUMN_PET_NAME, PetEntry.COLUMN_PET_BREED, PetEntry.COLUMN_PET_WEIGHT }; String selection PetEntry.COLUMN_PET_NAME ?; String[] selectionArgs { Garfield }; String sortOrder PetEntry.COLUMN_PET_WEIGHT DESC; Cursor cursor db.query( PetEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder );?占位符配合selectionArgs是防 SQL 注入的标准做法别图省事直接拼字符串。用完cursor.close()否则内存泄漏。4. 验证请求跑通读写并做一次 AI 校验代码写完不代表数据库真的建起来了。这一章给可执行的验证步骤。4.1 用 displayDatabaseInfo 验证在 Activity 里加一个临时方法把行数打到 TextView 上private void displayDatabaseInfo() { PetDbHelper mDbHelper new PetDbHelper(this); SQLiteDatabase db mDbHelper.getReadableDatabase(); Cursor cursor db.rawQuery(SELECT * FROM PetEntry.TABLE_NAME, null); try { TextView displayView findViewById(R.id.text_view_pet); displayView.setText(pets 表行数: cursor.getCount()); } finally { cursor.close(); } }在onCreate里调用它。第一次运行会触发onCreate建表行数为 0插入一条后再运行行数变 1。如果直接崩在no such table说明建表语句没执行回去检查DATABASE_VERSION和onCreate是否被正确重写。4.2 用 adb 命令直接看库文件模拟器或已 root 的真机上可以用 adb 把数据库拉出来看adb shell run-as com.example.pets ls -l /data/data/com.example.pets/databases/ adb exec-out run-as com.example.pets cat /data/data/com.example.pets/databases/shelter.db shelter.db sqlite3 shelter.db .tables sqlite3 shelter.db SELECT * FROM pets;run-as只对 debug 包有效release 包需要 root。.tables能列出所有表如果输出为空说明建库失败。SELECT * FROM pets;能打出你插入的那条 Garfield 记录就证明读写链路通了。4.3 AI 辅助校验 SQL把建表语句丢给模型让它检查有没有语法或约束问题。走统一通道的话用 curl 就能测curl https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: sk-替换成你自己的Key \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 512, messages: [ {role: user, content: 检查这条 SQLite 建表语句是否有语法或约束问题CREATE TABLE pets (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, breed TEXT, gender INTEGER NOT NULL, weight INTEGER NOT NULL DEFAULT 0);} ] }返回里如果指出AUTOINCREMENT在 SQLite 中需要配合INTEGER PRIMARY KEY使用、NOT NULL列插入时必须给值就说明校验生效了。想直接在网页里对话验证用模型对话入口https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。5. 本篇常见错排查5.1 no such table: pets最常见。原因通常是DATABASE_VERSION没变但 schema 改了或者onCreate里execSQL的字符串拼错了。排查方法卸载 App 重装强制走一次onCreate或者把版本号加一触发onUpgrade。5.2 attempt to write a readonly databasegetReadableDatabase()在磁盘满或权限异常时会退化成只读。写操作必须用getWritableDatabase()。另外检查有没有在onCreate之外的地方误删了数据库文件。5.3 Cursor 没关闭导致内存泄漏Cursor持有底层资源不close()会累积。用try/finally包住或者用 try-with-resourcesAPI 16 的Cursor实现了Closeable。5.4 AI 请求返回 401Key 没带对或者x-api-key头写成了Authorization。不同模型接口的鉴权头不一样按文档来。Key 泄露了就去控制台吊销重建https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。5.5 真机上 adb 拉不到库run-as只对 debuggable 的包生效。检查build.gradle里debuggable true或者用 Android Studio 的 Device File Explorer 手动导出。6. 接入文档与后续路径数据库骨架跑通后AI 辅助可以继续用在两个地方一是让模型根据自然语言生成复杂查询二是把报错日志贴进去让它定位。接入细节看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用 Claude Code 做 Android 项目Anthropic 兼容配置参考https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 。我自己的习惯是建表语句先在模型对话里过一遍确认约束没问题再写进SQL_CREATE_PETS_TABLE运行期报错就把adb logcat里那段贴进去让它给出修改建议。这样比盲改快得多。数据库版本号记得每次改 schema 都加一这个坑我踩过不止一次。
返回列表