本系列第 6/8 课,建议 45–60 分钟(估算)。先完成上一课。
这课解决什么
SQLite 是本平台内置组件。先打开数据库并检查真实就绪状态,再建表。绑定参数把数据和值分开,包含单引号的题目不用手工拼 SQL。迁移标记和卡片写入同一事务,失败回滚。
先预测,再运行
题目里有 O’Reilly,应该把它直接拼进 INSERT 文本吗?
先记下自己的答案,运行起始工程与完成工程比较;答案见文末。
开始实战
以上一课完成工程为起点,先导入并运行,再另存为本课作业。
阶段工程各自保存数据;导入下一课不会自动带走手机上的旧数据。练习使用本课示例数据,迁移方法在数据课程第六课单独讲解。
新增或调整的组件
| 实例名 | 组件 | 本课配置 |
|---|---|---|
标签_标题 |
标签 | Width=Fill parent;FontSize=24;TextColor=#183153;Text=知识卡片 · 第 6课 |
微数据库1 |
微数据库 | Namespace=CardsB06 |
SQLite1 |
SQLite | DBName=cards.sqlite3 |
计时器_数据库 |
计时器 | TimerEnabled=False;TimerInterval=200 |
逐步搭建
- 加入 SQLite1,DBName=cards.sqlite3;OpenDatabase() 不传文件名。
- 用独立计时器检查 IsDatabaseOpen;超时停止等待并提示,不能假造完成事件。
- 创建 cards 和 meta;仅未有 migration 标记时把旧卡片迁入,失败回滚。
- 关开工程多次,卡片不会再次迁入;保存含引号的题目,按 ID 更新与删除。

图来自本课最终工程执行积木后的状态,为 HTML 近似预览;不是手机截图。
核心积木与数据流
下面是本课完成工程中的核心代码,包含所需全局变量和过程。可在块图中追踪数值,再回到工程对照完整事件;合并到已有工程时,同名事件合并处理。
global 卡片 = list(makedict(pair("id", 1), pair("question", "中国历史朝代顺序从哪里查?"), pair("answer", "按资料的年代字段排序"), pair("category", "历史"), pair("wrong", 0)), makedict(pair("id", 2), pair("question", "列表第一个索引是多少?"), pair("answer", "1"), pair("category", "编程"), pair("wrong", 0)))
global 显示 = []
global 选中ID = 0
global 下个ID = 3
global 关键词 = ""
global 倒序 = false
global 就绪 = false
global 数据库等待 = 0
procedure 刷新() {
卡片 = []
var 行们 = SQLite1.SelectSQL("SELECT id,question,answer,category,wrong FROM cards ORDER BY id", list())
var 已存编号 = SQLite1.SelectSQL("SELECT v FROM meta WHERE k=?",list("next_id"))
if length(已存编号) > 0 { 下个ID = max(下个ID, 已存编号[1] * 1) }
foreach 行 in 行们 {
listadd(卡片, makedict(pair("id", 行[1]), pair("question", 行[2]), pair("answer", 行[3]), pair("category", 行[4]), pair("wrong", 行[5])))
下个ID = max(下个ID, 行[1] + 1)
}
显示 = []
var 标题 = []
foreach 项 in 卡片 {
if 关键词 = "" or textContains(lower(join(dictget(项,"question",""), " ", dictget(项,"category",""))), lower(关键词)) {
listadd(显示, 项)
listadd(标题, join("#", dictget(项,"id",0), " · ", dictget(项,"category",""), " · ", dictget(项,"question","")))
}
}
if 倒序 { var 新显示 = [] var 新标题 = [] for i = length(显示) to 1 step -1 { listadd(新显示,显示[i]) listadd(新标题,标题[i]) } 显示 = 新显示 标题 = 新标题 }
列表显示框_卡片.Elements = 标题
选中ID = 0
标签_详情.Text = "点击卡片查看详情"
标签_状态.Text = join("显示 ", length(显示), " / ", length(卡片), " 张卡片")
}
when 计时器_数据库.Timer() {
数据库等待 = 数据库等待 + 1
if SQLite1.IsDatabaseOpen() {
计时器_数据库.TimerEnabled = false
var 建表 = SQLite1.Execute("CREATE TABLE IF NOT EXISTS cards(id INTEGER PRIMARY KEY, question TEXT NOT NULL, answer TEXT NOT NULL, category TEXT NOT NULL, wrong INTEGER NOT NULL DEFAULT 0)", list())
var 元数据 = SQLite1.Execute("CREATE TABLE IF NOT EXISTS meta(k TEXT PRIMARY KEY, v TEXT)", list())
if 建表 and 元数据 {
var 版本 = SQLite1.SelectSQL("SELECT v FROM meta WHERE k = ?", list("migration"))
if length(版本) = 0 {
SQLite1.BeginTransaction()
var 成功 = true
foreach 项 in 卡片 {
var 写入 = SQLite1.Execute("INSERT OR IGNORE INTO cards(id,question,answer,category,wrong) VALUES(?,?,?,?,?)", list(dictget(项,"id",0),dictget(项,"question",""),dictget(项,"answer",""),dictget(项,"category",""),dictget(项,"wrong",0)))
if not 写入 { 成功 = false }
}
var 标记 = SQLite1.Execute("INSERT INTO meta(k,v) VALUES(?,?)", list("migration", "1"))
if 成功 and 标记 { SQLite1.CommitTransaction() 就绪 = true }
else { SQLite1.RollbackTransaction() 标签_状态.Text = "迁移失败,保留旧数据" }
} else { 就绪 = true }
if 就绪 { 刷新() }
} else { 标签_状态.Text = "建表失败" }
} else if 数据库等待 >= 30 {
计时器_数据库.TimerEnabled = false
标签_状态.Text = "数据库打开超时,请重新打开应用"
}
}
中途检查与排错
本课以下控制与数据流已执行最终归档中的真实积木检查。请在伴侣或安装的 APK 中按相同场景操作,记录实际结果;自动检查通过不代表手机已实测。
- 按索引读取详情。
- 空题不保存。
- 含引号逗号换行的记录保存。
- 重启完整恢复。
- 筛选后按稳定ID修改。
- 倒序保持记录映射。
- 取消删除保留。
- 确认删除正确对象。
- 实际读取嵌套asset JSON、重复导入去重。
- 错误顶层、缺字段、坏JSON与资源不存在拒绝。
- 已删除ID不复用。
- SQLite就绪、绑定参数和迁移只执行一次。
排查时先确定出错的是输入、状态、数据读取还是显示;用标签暂时显示关键值,每次只改变一个条件。
当前课仅实现表中对应功能,导出和备份在第八课加入。内置 SQLite 无需扩展。
两个理解题与独立改造
- 题目里有 O’Reilly,应该把它直接拼进 INSERT 文本吗?
- migration 标记为什么也必须在迁移事务里?
独立改造: 写一个只查“编程”分类的查询,保持按 ID 排序。
先完成自己的工程,再对照答案。检查成功路径,也检查空数据、重复操作和取消;写出你改动的规则。
查看预测与练习参考答案
预测: 不应该。SQL 写 ? 占位符,值放绑定参数列表,避免引号破坏语句。
追踪: 若提前写标记而卡片未迁完,重启会误以为迁移成功;事务保持一致。
改造思路: SELECT id,question,answer,category,wrong FROM cards WHERE category=? ORDER BY id,参数列表为[“编程”]。多列返回行列表,单列结果规则不同。
完成与下一步
保存自己的工程并导出 .aia;给文件命名以区分本课作业和参考源码。请用自己的话解释本课知识,并展示一次失败输入的处理。
扫码添加客服咨询