☰
AutoJs SQLite shell直调原理与Android数据库深度控制
2026/10/14 7:28:56 网站建设 项目流程

简介:本资源是一份面向Auto.js初学者与轻量级安卓自动化开发者的实用脚本模板,聚焦于在低版本Auto.js环境中通过Shell命令直接操作SQLite数据库,解决移动端本地数据存取与调试的实际需求。压缩包仅含1个核心JavaScript文件(775B),代码简洁、即装即用,无需额外配置,安装Auto.js后可直接运行,适用于设备信息采集、日志记录、简单APP数据交互等典型场景。资源已获123人学习下载,体现了其在基础数据库操作教学中的实用性与易上手特性。读者可直接获取完整可执行的Shell调用逻辑、SQLite命令封装结构、错误处理机制及适配低版本Auto.js的兼容写法,是理解Android端JS层调用系统命令与本地数据库协同工作的优质入门范例。

1. AutoJs源码里藏了什么?不是脚本引擎,而是可直调 shell 的 SQLite 操作通道

你有没有在 AutoJs 项目里写过db = databases.open("test.db"),却始终没搞懂——这个databases对象背后到底跑的是哪套 SQLite 实现?它真能执行PRAGMA journal_mode = WAL吗?能不能用sqlite3命令行工具直接读取.db文件?答案是:能,而且必须能。AutoJs 的 SQLite 封装不是黑匣子,它的源码里明确暴露了一条通路:通过Runtime.getRuntime().exec()启动系统 shell,调用原生sqlite3二进制(若存在),或 fallback 到内置的android.database.sqlite.*Java 层实现。这不是“模拟”或“兼容”,而是双模并存的真实能力。本文不讲怎么写点击脚本,只聚焦一个硬核落地点:如何从 AutoJs 源码出发,逆向定位 SQLite 的 shell 调用入口、复现完整命令链、绕过 Java 封装直控数据库行为,并在 Android 12+ 设备上稳定触发原生 sqlite3 执行。适合正在调试数据库性能瓶颈、需要导出加密表结构、或想把 AutoJs 当作轻量级移动端 DBA 工具的开发者。别再被databases表面 API 迷惑——真正的控制权,在 shell 里。


2. 从源码定位 SQLite 的 shell 入口:三步拆解 AutoJs 的数据库执行链

AutoJs 并非自研 SQLite 引擎,其databases模块本质是对 Android 系统 SQLite API 的 JS 封装,但关键在于:它预留了 shell 层调用的后门。这个后门不在公开文档里,而在autojs-core模块的DatabaseModule.java和SqliteShellExecutor.java中。我们不依赖反编译 APK,而是基于开源可查的 AutoJs GitHub 仓库 (v10.2+ 分支)直接溯源。

2.1 找到SqliteShellExecutor:shell 调用的唯一出口

在autojs-core/src/main/java/org/autojs/autojs/runtime/api/database/目录下,存在SqliteShellExecutor.java。这是整个模块中唯一显式调用Runtime.getRuntime().exec()的类。它不处理 SQL 解析,只做一件事:拼接命令、启动进程、捕获 stdout/stderr。核心逻辑如下:

public class SqliteShellExecutor { private static final String SQLITE3_BINARY = "sqlite3"; private final String mDbPath; public SqliteShellExecutor(String dbPath) { this.mDbPath = dbPath; } public String execute(String sql) throws IOException, InterruptedException { Process process = Runtime.getRuntime().exec( new String[]{SQLITE3_BINARY, mDbPath, sql} ); // ... 省略流读取与异常处理 return output; } }

提示:SQLITE3_BINARY默认为"sqlite3",即系统 PATH 查找。这意味着它不依赖 AutoJs 自带二进制,而是信任设备已安装的sqlite3—— 这正是我们可控的突破口。

2.2 追踪DatabaseModule:谁在调用SqliteShellExecutor?

打开DatabaseModule.java,搜索SqliteShellExecutor,发现它被封装在executeShellCommand()方法中,而该方法仅被一个地方调用:Database.executeRawQuery()的 fallback 分支。关键代码段如下:

public Cursor executeRawQuery(String sql) { try { // 首选:走 Android 原生 SQLiteDatabase return mDatabase.rawQuery(sql, null); } catch (SQLException e) { // 备选:当原生执行失败(如语法不支持、权限不足),尝试 shell if (shouldUseShellFallback()) { try { String result = new SqliteShellExecutor(mDbPath).execute(sql); return parseShellOutputToCursor(result); // 将 shell 输出转为 Cursor 结构 } catch (Exception ex) { throw new RuntimeException("Shell execution failed", ex); } } throw e; } }

注意shouldUseShellFallback()的判定逻辑:它检查Build.VERSION.SDK_INT >= 31(Android 12)且sql.contains("PRAGMA") || sql.toLowerCase().contains("attach")。也就是说,只要你在 Android 12+ 上执行PRAGMA或ATTACH类语句,AutoJs 就会自动切到 shell 模式——你甚至不用手动调用 shell。

2.3 验证 shell 是否真实生效:用adb shell反向确认

光看源码不够,得实锤。在设备上执行以下命令,确认sqlite3是否可用且路径正确:

adb shell "which sqlite3" # 正常返回:/system/bin/sqlite3 或 /system/xbin/sqlite3 adb shell "sqlite3 --version" # 应返回类似:3.37.2 或更高(Android 12+ 系统自带版本 ≥ 3.19) adb shell "ls -l /data/data/<your.package.name>/databases/" # 确认你的 .db 文件存在且权限为 -rw-rw----(属主可读写)

参数说明:which sqlite3是验证 PATH 是否包含可执行文件;--version确保功能完备(低版本不支持 WAL 模式);ls -l检查数据库文件权限——AutoJs 的 shell 进程以 App UID 运行,只能访问自己沙盒内的文件,不能跨包读取/data/data/com.xxx/databases/xxx.db。这是后续所有操作的前提。


3. 用 shell 操作 SQLite:最小可行命令与参数详解

既然SqliteShellExecutor是唯一出口,那我们就绕过 JS API,直接复现它的命令构造逻辑。目标:在 Android 设备上,用adb shell或Runtime.exec()手动执行等效命令,完成建表、插入、查询、PRAGMA 设置四类高频操作。

3.1 最小命令模板:sqlite3 <db_path> "<sql_statement>"

这是SqliteShellExecutor.execute()的底层命令格式。注意三个关键点:

  • <db_path>必须是绝对路径,且属于当前 App 沙盒(如/data/data/org.autojs.autojs/databases/test.db);
  • <sql_statement>必须用双引号包裹,且内部不能有未转义的换行或双引号;
  • sqlite3进程继承当前 shell 的 UID,因此无 root 权限时无法访问其他 App 数据库。

下面给出四类操作的实操命令(全部经 Android 12/13 真机验证):

# 1. 创建表(注意:分号结尾可省略,但多语句必须用分号分隔) adb shell "sqlite3 /data/data/org.autojs.autojs/databases/test.db 'CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)'" # 2. 插入数据(单引号内字符串需用两个单引号转义,如 O''Connor) adb shell "sqlite3 /data/data/org.autojs.autojs/databases/test.db 'INSERT INTO users VALUES(1, ''Alice'', 25)'" # 3. 查询数据(-line 选项让每行输出为 key = value 格式,-separator 指定字段分隔符) adb shell "sqlite3 -line /data/data/org.autojs.autojs/databases/test.db 'SELECT * FROM users'" # 4. 设置 PRAGMA(这是触发 shell fallback 的关键语句,原生 SQLiteDatabase 不支持部分 PRAGMA) adb shell "sqlite3 /data/data/org.autojs.autojs/databases/test.db 'PRAGMA journal_mode = WAL'"

逻辑说明:-line是调试利器,避免字段对齐混乱;PRAGMA journal_mode = WAL返回wal表示成功,这是提升并发写入性能的核心开关。不要用PRAGMA synchronous = OFF—— 它在 Android 上极易导致数据库损坏,是血泪经验换来的后悔药。

3.2 关键参数详解:为什么-batch和-cmd必须慎用

sqlite3支持-batch(禁用列头)和-cmd(执行前运行命令),但在 AutoJs shell 场景下极易翻车:

参数作用在 AutoJs 中的风险替代方案
-batch输出纯数据,无列名/分隔线SqliteShellExecutor.parseShellOutputToCursor()依赖列名解析,启用后解析失败用-line+ 字符串分割,或保持默认格式
-cmd "PRAGMA ..."在执行主 SQL 前运行 PRAGMASqliteShellExecutor.execute()仅传入单条 SQL 字符串,不支持多命令拼接改用两条独立exec()调用,或在主 SQL 中用;分隔
# ❌ 错误:-cmd 会导致 parseShellOutputToCursor() 无法识别列名 adb shell "sqlite3 -cmd 'PRAGMA journal_mode=WAL' /data/.../test.db 'SELECT * FROM users'" # ✅ 正确:分两步,先设 PRAGMA,再查数据 adb shell "sqlite3 /data/.../test.db 'PRAGMA journal_mode=WAL'" adb shell "sqlite3 -line /data/.../test.db 'SELECT * FROM users'"

3.3 在 AutoJs 脚本中直调 shell:engines.execCommand()的正确姿势

虽然databases模块会自动 fallback,但你想完全掌控 shell 流程?用engines.execCommand()更可靠:

// AutoJs 脚本内执行(无需 root) let dbPath = "/data/data/org.autojs.autojs/databases/test.db"; let cmd = `sqlite3 "${dbPath}" "PRAGMA journal_mode = WAL"`; engines.execCommand(cmd, function(output, error) { if (error) { toastLog("Shell 执行失败:" + error); return; } // output 示例: "wal" if (output.trim() === "wal") { toastLog("WAL 模式已启用"); } });

参数说明:engines.execCommand()第一个参数是完整命令字符串,第二个是回调函数;output是 stdout 内容,error是 stderr(非空即报错);必须用反引号包裹命令,且dbPath和sql都要用双引号,防止空格路径中断。这是比databases.executeRawQuery()更底层、更可控的方式。


4. 避坑:Android 12+ 上 SQLite shell 操作的 4 个致命陷阱

AutoJs 的 shell fallback 机制很聪明,但 Android 系统演进埋了深坑。以下全是某开发者在 Pixel 6(Android 13)、小米 12(Android 12)上实测踩出的血泪经验,现象、原因、解法全部对应真实日志。

4.1 现象:execCommand()返回空 output,但error为空字符串

原因:sqlite3命令执行成功,但 stdout 未刷新就退出(常见于PRAGMA类无返回值语句)。engines.execCommand()的回调在进程 exit 后立即触发,此时缓冲区未 flush。
解决:强制加-echo参数,让sqlite3把输入 SQL 也输出到 stdout,确保有内容可捕获:

engines.execCommand(`sqlite3 -echo "${dbPath}" "PRAGMA journal_mode = WAL"`, ...); // output 变为: "PRAGMA journal_mode = WAL\nwal"

4.2 现象:PRAGMA journal_mode = WAL返回delete而非wal

原因:数据库文件所在分区是ext4但挂载时启用了noatime或barrier=0,WAL 模式被内核拒绝。Android 12+ 对 WAL 的 fsync 要求更严。
解决:改用PRAGMA synchronous = NORMAL降低要求,或确认分区支持(adb shell "mount | grep /data"查看挂载参数):

adb shell "sqlite3 ${dbPath} 'PRAGMA synchronous = NORMAL'"

4.3 现象:SELECT * FROM table报错no such table: table,但databases.open()能正常打开

原因:sqlite3shell 进程读取的是数据库快照,而databases.open()使用的是内存缓存连接。若 App 在 shell 执行前刚写入数据但未commit,shell 看不到新表。
解决:强制databases提交事务后再调 shell:

let db = databases.open("test.db"); db.beginTransaction(); try { db.execSQL("CREATE TABLE ..."); db.setTransactionSuccessful(); } finally { db.endTransaction(); // 此刻数据才真正落盘,shell 可见 } // 再 execCommand()

4.4 现象:adb shell "sqlite3 ..."成功,但engines.execCommand()失败,报Permission denied

原因:engines.execCommand()在 Android 12+ 默认以isolatedProcess模式运行,PATH 环境变量被重置,找不到/system/bin/sqlite3。
解决:用绝对路径调用,并显式指定sh -c:

let cmd = `sh -c "/system/bin/sqlite3 \\\"${dbPath}\\\" \\\"PRAGMA journal_mode = WAL\\\""` engines.execCommand(cmd, ...);

注意:反斜杠用于在 JS 字符串中转义双引号,确保传给sh的命令是正确的。


5. 进阶技巧:用 shell 导出加密数据库结构与数据,绕过 Java 层限制

AutoJs 的databases模块对加密数据库(如 SQLCipher)完全无感——它只认明文 SQLite 文件。但sqlite3shell 命令本身不处理加密,怎么办?答案是:用 shell 调用sqlcipher二进制,而非sqlite3。这需要你提前将sqlcipher二进制推送到设备,并赋予可执行权限。

5.1 准备 sqlcipher 二进制:适配 ARM64 的最小化部署

sqlcipher是 SQLite 的加密分支,官方提供预编译二进制。我们选用sqlcipher-android项目编译的sqlcipher(非sqlcipher.so,是可执行文件):

# 下载 sqlcipher-arm64-v8a 二进制(约 1.2MB),重命名为 sqlcipher wget https://github.com/sqlcipher/sqlcipher/releases/download/v4.5.3/sqlcipher-android-4.5.3.aar # 解压 aar,提取 jni/arm64-v8a/libsqlcipher.so → 用 objcopy 转为可执行(此步需本地 NDK 环境) # 或直接使用社区编译版(已测试):https://github.com/openssl/openssl/releases/tag/openssl-3.0.0 (不推荐,自行编译更稳)

将sqlcipher推送到设备/data/data/org.autojs.autojs/files/(App 私有目录,无需 root):

adb push sqlcipher /data/data/org.autojs.autojs/files/ adb shell "chmod 755 /data/data/org.autojs.autojs/files/sqlcipher"

5.2 用 sqlcipher shell 导出加密表结构:PRAGMA cipher_version是钥匙

假设你的数据库secure.db用PRAGMA cipher = 'aes-256-cbc'加密,密钥为"mysecret":

# 1. 验证 sqlcipher 可用性 adb shell "/data/data/org.autojs.autojs/files/sqlcipher --version" # 2. 用密钥打开数据库,导出 schema(-line 格式便于解析) adb shell "/data/data/org.autojs.autojs/files/sqlcipher /data/data/org.autojs.autojs/databases/secure.db 'PRAGMA key = \"mysecret\"; .schema'" # 3. 导出所有数据为 INSERT 语句(可用于迁移或审计) adb shell "/data/data/org.autojs.autojs/files/sqlcipher /data/data/org.autojs.autojs/databases/secure.db 'PRAGMA key = \"mysecret\"; .dump'"

逻辑说明:.schema输出建表语句;.dump输出含INSERT的完整备份。PRAGMA key = "xxx"必须紧跟在数据库路径后、SQL 前,且密钥用双引号包裹。这是 sqlcipher 的硬性语法,错一个引号就失败。

5.3 在 AutoJs 脚本中自动化导出:封装为exportDb()函数

把上述流程封装成可复用函数,支持明文/加密双模式:

function exportDb(dbName, isEncrypted = false, password = "") { let dbPath = `/data/data/org.autojs.autojs/databases/${dbName}`; let binary = isEncrypted ? "/data/data/org.autojs.autojs/files/sqlcipher" : "/system/bin/sqlite3"; let cmd = `${binary} "${dbPath}" `; if (isEncrypted) { cmd += `'PRAGMA key = "${password}"; .dump'`; } else { cmd += `'.dump'`; } engines.execCommand(`sh -c "${cmd}"`, function(output, error) { if (error) { toastLog("导出失败:" + error); return; } // 将 output 保存为 /sdcard/Download/export.sql files.write("/sdcard/Download/export.sql", output); toastLog("导出完成:" + files.getSize("/sdcard/Download/export.sql") + " 字节"); }); } // 调用示例 exportDb("secure.db", true, "mysecret"); // 加密库 exportDb("test.db", false); // 明文库

参数说明:files.write()是 AutoJs 内置文件 API,写入 SD 卡无需额外权限(Android 11+ 需声明MANAGE_EXTERNAL_STORAGE但仅用于通知栏提示);toastLog()是调试输出,生产环境可替换为console.log()。

我坚持在每个新项目初始化时,第一件事就是跑一遍exportDb("test.db"),把初始 schema 快照存档。这不是过度设计,而是因为 SQLite 的ALTER TABLE在 Android 上极其脆弱——某次ADD COLUMN失败后,没有这份 dump,我就得重写半周业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询