ARTICLE DETAIL

资讯详情

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

AutoJs 脚本用 shell 操作 sqlite:高效数据查询与封装实践

AutoJs 脚本用 shell 操作 sqlite:高效数据查询与封装实践 简介这份资源是一份AutoJs源码模板主题为通过shell命令操作sqlite数据库面向具备一定AutoJs基础、希望扩展本地数据存储与命令行调用能力的开发者与脚本学习者。它解决的是在AutoJs环境中直接借助shell执行sqlite语句、完成建库建表与增删改查等操作的问题适合作为学习参考与项目模板使用。压缩包为7z格式仅含1个js文件体积约775B文件精简便于快速导入AutoJs运行与阅读源码逻辑。目前已有122人学习下载说明该模板在同类需求中具有一定参考价值。读者可从中获取shell调用sqlite的完整脚本结构、命令拼接方式与执行流程理解AutoJs与系统shell之间的交互思路并在此基础上按需修改表结构与查询语句用于本地数据缓存、配置存储等场景。资源仅供学习与参考请勿用于商业用途安装与使用前请确认自身AutoJs环境兼容。1. 用 shell 操作 sqliteAutoJs 脚本里被低估的一条数据通路很多人写 AutoJs 脚本数据一多就开始难受用storages存配置键值对一多就乱用 JSON 文件存列表几千条以后读写全量、查一条要遍历一遍想按条件筛选、排序、去重全靠手写循环。这个资源给的思路很直接——把数据交给 sqlite脚本里通过 shell 去调用 sqlite 命令行完成建库、建表、增删改查。它解决的不是“能不能存”的问题而是“数据量上来之后还能不能高效查”的问题。适合已经能跑通 AutoJs 基础脚本、手里有结构化数据采集记录、任务队列、账号配置、日志需要落库的人。前提是设备上有可用的 sqlite 可执行文件或者你能把库文件拿到电脑上用 DB Browser for SQLite 这类工具先建好再推回去。2. 先搞清楚 shell 调 sqlite 这条链路为什么不是直接上 Room2.1 AutoJs 里操作数据库的几种选型在 Android 上让脚本碰数据库常见做法有这么几条路。第一条是 AutoJs 自带的storages本质是 SharedPreferences 封装只适合小配置不适合当数据库用。第二条是走 Java 反射调 Android 原生的SQLiteDatabase功能全但代码量大、类型转换烦脚本里写起来很重。第三条就是这份资源的路子用shell执行 sqlite 命令行把 SQL 当字符串传进去读结果回来解析。选第三条的理由很实际。sqlite 命令行本身就是一个独立可执行文件不依赖你的脚本进程脚本崩了库还在SQL 语句是纯文本调试时可以直接在电脑上跑一遍再贴进脚本跨设备迁移只要拷一个.db文件。代价也有每次调用是一次进程启动频繁小写入会慢结果解析要自己处理分隔符。所以它适合“读多写少、查询条件复杂、需要跨端复用”的场景不适合高频逐条插入。2.2 确认设备上的 sqlite 可执行文件动手前先确认环境。AutoJs 的shell函数默认以当前应用权限执行命令能不能调起 sqlite 取决于系统里有没有这个二进制。常见做法是先探测# 依次尝试几个常见路径看哪个能返回版本号 which sqlite3 sqlite3 --version /system/bin/sqlite3 --version如果which没结果但/system/bin/sqlite3能出版本号说明二进制在但不在 PATH 里后面调用就写全路径。如果全都没有就得自己准备一个可执行的 sqlite3 放进脚本能访问的目录或者退一步在电脑上用 DB Browser for SQLite 建好库和表把.db文件推到手机存储脚本只做查询和少量更新。这一步别省很多“命令执行失败”的玄学问题根子就是二进制根本不存在。2.3 用 shell 建库建表的最小闭环环境确认后先跑通一个最小闭环建表、插一条、查回来。在 AutoJs 里可以这样写// 库文件放在脚本同级的 data 目录路径按自己设备改 var DB /sdcard/autojs/demo.db; // 建表IF NOT EXISTS 保证重复执行不报错 var createSql CREATE TABLE IF NOT EXISTS task ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, status INTEGER DEFAULT 0, created_at TEXT);; // 用 shell 执行注意 sqlite3 后面跟库路径SQL 用引号包住 var r1 shell(sqlite3 DB \ createSql \, true); console.log(建表结果:, r1.code, r1.result); // 插入一条字符串字段的引号要转义好 var insertSql INSERT INTO task (name, status, created_at) VALUES (采集任务A, 0, datetime(now,localtime));; var r2 shell(sqlite3 DB \ insertSql \, true); console.log(插入结果:, r2.code, r2.result); // 查询用 -separator 指定列分隔符方便后面 split var querySql SELECT id, name, status FROM task;; var r3 shell(sqlite3 -separator | DB \ querySql \, true); console.log(查询结果:, r3.result);逻辑说明shell(cmd, true)第二个参数为true表示等待命令执行完再返回返回对象里有code退出码和result标准输出。建表用IF NOT EXISTS是为了脚本可重复运行不然第二次执行直接报错。查询时加-separator |是关键默认分隔符是竖线但显式指定后解析逻辑更可控避免字段里本身含空格导致切分错位。参数说明DB路径要落在脚本有读写权限的目录/sdcard/下一般没问题应用私有目录反而可能因为权限拿不到。status用整数而不是文本是为了后面按状态筛选时能走索引。created_at用datetime(now,localtime)存本地时间字符串比存时间戳直观代价是排序要按字符串比。3. 把增删改查封装成可复用函数参数绑定与结果解析3.1 为什么不能直接拼字符串上面那段能跑但直接拼 SQL 有两个坑。一是字段里带单引号比如任务名是OBrien拼进去 SQL 直接语法错误。二是外部输入拼进 SQL 有注入风险虽然脚本场景风险低但语法错误一样让人翻车。sqlite 命令行支持参数绑定常见做法是用.param或者把值转义。更稳的方式是写一个转义函数把单引号翻倍// 把字符串里的单引号翻倍再包上单引号避免语法错误 function quote(str) { if (str null || str undefined) return NULL; return String(str).replace(//g, ) ; } // 数字直接返回避免被当字符串 function num(n) { return Number(n); }逻辑说明SQL 标准里字符串内的单引号用两个单引号表示replace(//g, )就是干这个。quote对 null 返回NULL关键字保证插入空值语义正确。这样拼出来的 SQL 既不会因为引号报错也顺手挡掉了最简单的注入。3.2 封装查询函数并解析结果查询是这套方案里最需要打磨的部分因为结果要解析成脚本能用的结构。封装一个返回对象数组的函数var DB /sdcard/autojs/demo.db; // 执行任意 SQL返回 shell 结果对象 function exec(sql) { var cmd sqlite3 -separator | DB quote(sql); return shell(cmd, true); } // 查询并解析成对象数组columns 传列名顺序 function query(sql, columns) { var r exec(sql); if (r.code ! 0) { console.error(SQL 失败:, sql, r.result); return []; } var lines r.result.trim(); if (!lines) return []; return lines.split(\n).map(function (line) { var parts line.split(|); var obj {}; columns.forEach(function (col, i) { obj[col] parts[i]; }); return obj; }); } // 用法查所有未完成任务 var tasks query( SELECT id, name, status FROM task WHERE status 0 ORDER BY id DESC;, [id, name, status] ); tasks.forEach(function (t) { console.log(t.id, t.name, t.status); });逻辑说明exec把 SQL 用quote包成单引号字符串传给 sqlite3这样 SQL 内部的单引号已经被翻倍不会截断命令。query先判断退出码非零直接返回空数组并打日志避免拿脏数据继续跑。解析时按行切、按|切再按传入的列名顺序映射成对象列名顺序必须和 SELECT 的字段顺序一致这是使用约定。参数说明columns数组长度要和 SELECT 字段数一致多了会得到 undefined少了会丢字段。如果某列本身可能含|要么换一个不会出现在数据里的分隔符比如\x1f要么在 SQL 里用replace把竖线替换掉。r.result.trim()是为了去掉末尾换行否则会多解析出一个空行。3.3 更新和删除的写法与影响行数更新和删除不需要返回结果集但要知道影响了几行。sqlite3 命令行可以用changes()拿上一次操作的影响行数常见做法是拼在同一个 SQL 里// 更新指定任务状态并返回影响行数 function updateStatus(id, status) { var sql UPDATE task SET status num(status) WHERE id num(id) ; SELECT changes();; var r exec(sql); if (r.code ! 0) { console.error(更新失败:, r.result); return 0; } return parseInt(r.result.trim(), 10) || 0; } // 删除已完成任务 function deleteDone() { var sql DELETE FROM task WHERE status 1; SELECT changes();; var r exec(sql); return parseInt(r.result.trim(), 10) || 0; }逻辑说明把SELECT changes();和写操作放在同一次 shell 调用里用分号隔开sqlite3 会顺序执行并输出最后一条 SELECT 的结果这样一次进程启动就拿到影响行数省一次调用。parseInt兜底成 0防止解析异常时返回 NaN 污染后续判断。参数说明num()保证 id 和 status 是数字避免被quote包成字符串导致类型不匹配。如果表上 id 是主键WHERE id ?走索引影响行数最多 1deleteDone这种批量删除在大表上会锁库建议放在脚本空闲时段跑。4. 避坑与排查shell 调 sqlite 最容易翻车的几处4.1 命令返回 code 非零但没报错信息现象shell返回code是 1 或 127result却是空字符串完全不知道哪错了。原因通常是命令本身没找到127 是 command not found或者 sqlite3 把错误写到了标准错误而不是标准输出而shell默认只拿标准输出。解决先单独跑sqlite3 --version确认二进制存在执行时把标准错误重定向进来命令写成sqlite3 ... 21这样错误信息会进result再打日志就能看到具体是语法错还是库文件打不开。4.2 库文件路径带空格或中文导致打不开现象建表命令返回unable to open database file。原因多半是路径里有空格或中文shell 解析时把路径截断了。解决路径统一用英文、无空格或者给路径加引号。注意这里有两层引号要区分——路径的引号和 SQL 的引号常见写法是sqlite3 DB ...这种容易绕晕更省事的做法是路径本身就不带特殊字符从源头避开。4.3 查询结果里字段含分隔符导致错位现象解析出来的对象字段串位name 里跑进了 status 的值。原因就是字段内容里出现了你选的|分隔符。解决换一个业务数据里几乎不会出现的分隔符比如用-separator $\x1f这种不可见字符或者在 SQL 层面对可能含分隔符的字段做replace(col, |, )清洗。我一般会在建表时就约定文本字段不允许存竖线插入前统一过滤。4.4 频繁调用导致脚本卡顿现象循环里逐条插入几百条脚本明显变慢甚至 ANR。原因是每次shell都是一次进程启动几百次就是几百次 fork开销全在启动上。解决把多条 INSERT 拼成一条 SQL用事务包起来一次 shell 调用完成// 批量插入BEGIN 开启事务多条 VALUES 一次提交 var values []; for (var i 0; i 200; i) { values.push(( quote(任务 i) , 0)); } var sql BEGIN; INSERT INTO task (name, status) VALUES values.join(,) ; COMMIT;; exec(sql);逻辑说明BEGIN和COMMIT之间所有写入作为一个事务sqlite 只在提交时刷盘一次比逐条自动提交快一个数量级。values.join(,)把多组值拼成一条 INSERT 的 VALUES 列表注意单条 SQL 别太长几百到一千组值比较稳再多就分批。4.5 库文件被占用或损坏现象某次执行后库打不开报database disk image is malformed。原因通常是写入过程中脚本被强杀事务没提交完或者多个进程同时写同一个库。解决写操作尽量集中在单点别让多个脚本实例同时写重要数据定期用sqlite3 old.db .backup new.db备份真损坏了用.recover尝试抢救。血泪经验是别把库文件放在会被系统清理的缓存目录放稳定目录并定期导出。5. 进阶用事务和索引把查询压到毫秒级再留一手备份数据量到几万条以后光靠封装函数还不够得从表结构和执行方式上优化。第一件事是加索引。如果经常按status筛选、按created_at排序就建复合索引-- 按状态筛选 按时间排序的复合索引 CREATE INDEX IF NOT EXISTS idx_task_status_time ON task (status, created_at DESC);逻辑说明复合索引的列顺序要和查询条件、排序顺序匹配WHERE status 0 ORDER BY created_at DESC这种查询能直接走索引不用全表扫。代价是写入时多维护一份索引所以别给每个字段都建只给高频查询条件建。第二件事是善用EXPLAIN QUERY PLAN验证索引有没有生效# 看查询计划确认是否走了索引 sqlite3 /sdcard/autojs/demo.db \ EXPLAIN QUERY PLAN SELECT id FROM task WHERE status 0 ORDER BY created_at DESC;输出里出现USING INDEX idx_task_status_time就说明命中了出现SCAN就是全表扫得回头检查索引列顺序或查询写法。这一步很多人跳过结果索引建了没用上还以为是玄学。第三件事是备份。sqlite 的.backup命令能在库使用中做一致性备份比直接拷文件安全// 备份到带日期的文件避免覆盖 var stamp new Date().getTime(); var backup /sdcard/autojs/backup/demo_ stamp .db; shell(mkdir -p /sdcard/autojs/backup, true); shell(sqlite3 DB \.backup backup \, true);逻辑说明.backup是 sqlite3 的元命令不是 SQL所以要用双引号包住整条命令传给 shell内部路径再用单引号。mkdir -p保证目录存在否则备份会失败。备份文件带时间戳方便回滚到某个时间点。参数说明备份频率按数据重要程度定我一般脚本启动时备一次、每天再备一次。备份目录别和库文件放同一个分区避免一起丢。恢复时直接用备份文件替换原库或者用.restore命令。从那以后我每次给脚本加数据库功能都强制先跑一遍EXPLAIN QUERY PLAN确认索引命中再写个备份函数挂在启动流程里——数据这东西后悔药只有提前备好才有得吃。希望帮到你。本文还有配套的精品资源点击获取
返回列表