资讯动态

uniapp 中 SQLite 打包配置全解析:从 manifest 到真机验证

发布时间:2026/10/15 22:10:08 来源:尧图企业网站定制
1. 为什么要在 uniapp 里折腾 SQLite 而不是继续用 Storage很多人做 uniapp 项目一开始都是uni.setStorageSync走天下简单、直接、不用配置。但项目一旦上了规模尤其是涉及离线数据、批量查询、多表关联、分页排序这些场景Storage 那套键值对就开始捉襟见肘了。我印象特别深之前做一个离线台账类的应用数据量到两万条左右的时候每次冷启动读取全部数据再在内存里过滤页面白屏时间肉眼可见地拉长用户体验直接崩掉。这时候 SQLite 的价值就出来了。它是一个真正的关系型数据库支持 SQL 语法、索引、事务、聚合查询数据落在本地文件里查询走的是数据库引擎而不是 JS 数组遍历。对于 uniapp 这种一套代码跑多端的框架来说SQLite 在 App 端Android/iOS是原生支持的通过 plus 环境或者原生插件就能调用性能和数据管理能力完全不是 Storage 能比的。但问题也恰恰出在打包这两个字上。SQLite 不是一个纯 JS 模块它依赖原生层的动态库或静态库所以在 uniapp 里用它绕不开打包配置这一关。很多人卡就卡在这里代码写完了真机跑起来报模块未找到或者sqlite is not defined翻遍文档也找不到原因。这篇就先把这个打包 SQLite的环节彻底讲透包括为什么要这样配、每一步背后的逻辑是什么、以及我实际踩过的那些坑。需要提前说明的是本文聚焦的是 App 端Android 为主iOS 逻辑相通的 SQLite 打包集成不涉及小程序和 H5因为那两端根本没有原生 SQLite 环境硬套只会浪费时间。如果你做的正好是离线优先的 App 项目那接下来的内容应该能帮你少走不少弯路。2. 先搞清楚 uniapp 调用 SQLite 的几条技术路线在动手打包之前必须先把用哪种方式调 SQLite这件事定下来因为不同的路线打包配置完全不一样。我见过太多人一上来就照着某篇教程配结果配到一半发现路线根本不适合自己的项目只能推倒重来。2.1 路线一plus.sqlite 原生 API这是 uniapp 官方在 App 端内置的一套 SQLite 操作接口挂在plus.sqlite下面。它的特点是开箱即用不需要额外引入任何原生插件只要在manifest.json里把模块勾上打包时就会自动带上 SQLite 支持。API 形式大概是这样的plus.sqlite.openDatabase({ name: mydb, path: _doc/mydb.db, success: function(e) { console.log(数据库打开成功) }, fail: function(e) { console.log(打开失败 JSON.stringify(e)) } })这条路线的优势是轻量、官方维护、和 uniapp 生命周期贴合好。缺点是 API 偏底层全是回调风格没有 Promise 封装写起来比较啰嗦而且它只在 App 端有效条件编译必须写清楚。2.2 路线二原生插件市场里的 SQLite 插件插件市场里有不少封装好的 SQLite 插件有的支持 Promise有的支持 ORM 风格的链式调用用起来更顺手。但这类插件本质上是把原生代码打包成了 uni 原生插件你需要把插件引入项目然后在manifest.json的原生插件配置里声明打包时云端或本地打包机要能拉到对应的原生依赖。这条路线的坑在于插件质量参差不齐有的年久失修和新版 HBuilderX 的基座不兼容有的只提供了 Android 的 aariOS 那边根本没做。选之前一定要看清楚更新时间和平台支持说明。2.3 路线三自己写原生插件如果你团队有原生开发能力完全可以自己写一个 uni 原生插件把 SQLite 的封装做到最贴合业务。这条路最灵活但成本也最高需要懂 Android 的 Java/Kotlin 和 iOS 的 Objective-C/Swift还要熟悉 uni 原生插件的目录规范和打包流程。对于绝大多数项目我的建议是优先用路线一plus.sqlite够用、稳定、不引入额外依赖。只有当你有非常明确的 ORM 需求或者需要用到 plus.sqlite 没暴露的高级特性时才考虑路线二或路线三。本文后续的打包配置也主要围绕路线一来展开因为它是踩坑最多、也最需要讲清楚的一条。3. manifest.json 里那几个必须勾对的模块配置打包 SQLite 的核心战场就在manifest.json。这个文件是 uniapp 项目的总配置App 端打包时打包机会根据这里的声明来决定把哪些原生模块打进安装包。SQLite 相关的配置没勾对代码写得再漂亮也是白搭。3.1 模块权限里的 SQLite 勾选项打开manifest.json切到App 模块权限配置源码视图里对应app-plus.modules节点你会看到一长串模块列表。SQLite 对应的键是SQLite。在可视化界面里它通常显示为SQLite 数据库。把它勾上源码视图里就会多出这么一行app-plus: { modules: { SQLite: {} } }这里有个细节很多人忽略勾选 SQLite 之后打包体积会增加因为要把 SQLite 的原生库打进去。Android 端大概会增加 1MB 到 2MB 左右iOS 端类似。对于体积敏感的项目这是必须权衡的成本。但反过来说如果你不勾运行时就一定会报模块找不到所以这个成本是省不掉的。3.2 为什么有时候勾了还是报错我遇到过好几次明明勾了 SQLite真机还是报 plus.sqlite undefined的情况。排查下来原因基本集中在两个地方。第一个是基座问题。如果你用的是标准基座HBuilderX 自带的那种它里面其实已经包含了大部分常用模块包括 SQLite所以调试阶段可能感觉不到问题。但一旦你打了自定义基座或者正式包而manifest.json里没勾 SQLite那模块就不会被打进去运行时自然找不到。所以正确做法是调试阶段就用自定义基座并且确保 manifest 里勾了 SQLite让调试环境和正式环境保持一致。第二个是条件编译没写对。plus.sqlite 只在 App 端存在如果你在 H5 或小程序端也执行了这段代码那必然报错。正确的做法是用条件编译把代码包起来// #ifdef APP-PLUS plus.sqlite.openDatabase({ ... }) // #endif很多人图省事不写条件编译在 HBuilderX 的内置浏览器里预览时一切正常因为没走到那段代码一上真机就崩然后一头雾水。这个坑我踩过不止一次后来养成了习惯凡是 plus 开头的 API一律用 APP-PLUS 条件编译包住。3.3 权限配置里容易漏的一项除了模块勾选Android 端还有一个容易漏的地方存储权限。SQLite 数据库文件默认放在_doc目录下这个目录是应用私有目录理论上不需要额外的存储权限。但如果你把数据库路径改到了外部存储比如_downloads或自定义的外部路径那就需要在manifest.json的 Android 权限里声明读写外部存储的权限。我的建议是除非有明确的跨应用共享数据需求否则数据库一律放_doc私有目录。这样既不用申请敏感权限数据也更安全卸载应用时数据库会跟着一起清掉不会留下垃圾文件。4. 自定义基座打包从配置到真机验证的完整链路配置勾完了接下来就是打包验证。这一步是很多人真正卡住的地方因为涉及自定义基座、云端打包、证书配置等一系列操作。我把它拆成一条完整的链路来讲你可以照着走一遍。4.1 为什么要用自定义基座而不是标准基座标准基座是 HBuilderX 为了方便调试预置好的一个通用壳里面打包了一批常用模块。它的好处是即点即用不用等打包。但它的坏处也很明显你无法确认它到底包含了哪些模块也无法保证它和你正式包的模块配置一致。SQLite 这种依赖原生库的模块最稳妥的验证方式就是用自定义基座。自定义基座会根据你当前manifest.json的配置重新打一个包含你所需模块的调试包。这样你在自定义基座上跑通了正式包基本就不会有模块缺失的问题。操作路径是HBuilderX 菜单栏 → 运行 → 运行到手机或模拟器 → 制作自定义调试基座。然后选择使用云端证书或使用自有证书等打包完成再选择运行到自定义基座。4.2 云端打包和本地打包的取舍打包方式上uniapp 提供了云端打包和本地打包两种。云端打包简单点几下就行适合大多数团队本地打包需要自己配 Android SDK、iOS 环境适合对打包过程有强控制需求、或者有特殊原生依赖的场景。对于 SQLite 这种官方支持的模块云端打包完全够用没必要折腾本地打包。我唯一建议本地打包的场景是你同时引入了多个第三方原生插件且这些插件之间有依赖冲突需要手动调整 gradle 配置。纯 SQLite 项目用云端打包省心省力。打包时证书这块要注意Android 用自有证书的话记得把证书文件.keystore和密码、别名保管好后续每次打包都要用同一个证书否则覆盖安装会失败。iOS 则是证书和描述文件provisioning profile要匹配这块坑更多但和 SQLite 本身关系不大这里不展开。4.3 真机验证 SQLite 是否真的可用基座打好了装到真机上怎么确认 SQLite 真的可用我的做法是写一个最小的验证页面只做三件事打开数据库、建一张测试表、插入一条数据再查出来。// #ifdef APP-PLUS function testSQLite() { plus.sqlite.openDatabase({ name: testdb, path: _doc/testdb.db, success: function() { plus.sqlite.executeSql({ name: testdb, sql: CREATE TABLE IF NOT EXISTS t_test (id INTEGER PRIMARY KEY, name TEXT), success: function() { plus.sqlite.executeSql({ name: testdb, sql: INSERT INTO t_test (name) VALUES (hello), success: function() { plus.sqlite.selectSql({ name: testdb, sql: SELECT * FROM t_test, success: function(data) { console.log(查询结果, JSON.stringify(data)) } }) } }) } }) }, fail: function(e) { console.error(SQLite 打开失败, JSON.stringify(e)) } }) } // #endif如果控制台能打印出查询结果说明打包配置完全正确。如果openDatabase就失败了那基本可以断定是模块没打进去回头检查manifest.json的 SQLite 勾选和基座类型。提示验证阶段一定要用真机不要用模拟器。部分 Android 模拟器的 SQLite 原生库和真机行为不一致容易给出误导性的结果。5. 那些让我熬夜排查的打包报错与解决思路打包 SQLite 这件事顺利的话半小时搞定不顺利的话能折腾一整天。下面这几个报错是我和身边同行都真实遇到过的我把排查思路完整还原出来你遇到类似问题时可以照着走。5.1 plus is not defined 的三种可能这个报错最常见但原因不止一种。第一种是代码跑在了非 App 端比如你在 H5 里执行了 plus API那 plus 对象根本不存在。解决办法就是加// #ifdef APP-PLUS条件编译。第二种是在 App 端但 plus 还没 ready。uniapp 的 App 端plus 环境是在页面onLaunch之后才逐步就绪的如果你在onLoad的最开始就调用 plus.sqlite有可能 plus 还没初始化完。稳妥的做法是监听plusready事件或者把数据库初始化放到onLaunch里延迟执行。第三种比较隐蔽你用了 nvue 页面。nvue 的 JS 引擎和 vue 页面不同plus 对象的注入时机也有差异。如果必须在 nvue 里用 SQLite建议把数据库操作封装到一个独立的 JS 模块里在 vue 页面初始化好之后再调用。5.2 数据库文件路径写错导致的静默失败openDatabase的path参数写错了不一定会报错有时候会静默失败或者打开一个空库。我见过有人写成_doc/mydb没有 .db 后缀结果数据库文件确实生成了但后续操作各种异常。还有人写成绝对路径/sdcard/mydb.db在 Android 10 以上的分区存储机制下直接失败。正确的写法就是_doc/加上文件名文件名带上.db后缀。_doc是 uniapp 封装的私有目录常量对应 Android 的应用私有 files 目录iOS 的 Documents 目录。这个路径不需要任何额外权限也不会因为系统版本变化而失效。5.3 打包后体积暴涨的排查前面说过 SQLite 会增加包体积但如果你发现体积涨得离谱比如多了十几 MB那可能是重复打包了 SQLite 库。这种情况通常发生在你既勾了manifest.json里的 SQLite 模块又引入了一个自带 SQLite 原生库的第三方插件。两个库同时存在打包机不会去重体积自然翻倍。排查方法是看打包日志里的依赖列表或者用 Android Studio 的 APK 分析工具看 lib 目录下有没有重复的 .so 文件。解决办法就是二选一要么用官方模块要么用插件别混着来。5.4 iOS 端特有的打包注意事项iOS 这边SQLite 是系统自带的库libsqlite3.tbd所以理论上不需要额外引入。但 uniapp 打包 iOS 时如果manifest.json里勾了 SQLite 模块打包机会自动链接系统库。这里要注意的是iOS 的数据库路径_doc在 iOS 上对应的是应用沙盒的 Documents 目录这个目录的内容会被 iCloud 备份。如果你的数据库很大可能会影响审核苹果对备份体积有隐性要求。解决办法是把数据库放到 Library 目录下uniapp 里可以用_doc/../Library/这种相对路径来绕但更规范的做法是通过原生插件指定路径。纯 plus.sqlite 的话_doc是最省事的选择小数据库无所谓。6. 打包跑通之后数据库初始化的几个实战习惯打包配置搞定、真机验证通过只是万里长征第一步。真正在项目里用起来数据库的初始化和管理还有一堆细节要注意。这些细节文档里不会写但直接关系到线上稳定性。6.1 数据库打开时机与单例管理plus.sqlite 的openDatabase是可以重复调用的但重复打开同一个数据库并不会报错而是返回已打开的实例。不过为了逻辑清晰我习惯做一个单例封装全局只初始化一次其他模块通过一个统一的入口来获取数据库操作能力。// db.js let isOpen false export function openDB() { return new Promise((resolve, reject) { if (isOpen) { resolve() return } // #ifdef APP-PLUS plus.sqlite.openDatabase({ name: appdb, path: _doc/appdb.db, success: () { isOpen true resolve() }, fail: (e) reject(e) }) // #endif }) }这样封装的好处是页面里不用关心数据库有没有打开直接await openDB()就行。而且isOpen标志位避免了重复打开带来的潜在问题。6.2 建表语句的幂等处理初始化建表一定要用CREATE TABLE IF NOT EXISTS这个不用多说。但更关键的是版本管理。当你的表结构需要变更时加字段、改类型不能直接改建表语句就完事因为老用户的数据库里已经有旧表了IF NOT EXISTS会跳过创建新字段根本不会加上。我的做法是在数据库里维护一个版本号每次启动时对比版本需要升级就执行对应的 ALTER 语句。简单实现如下const DB_VERSION 2 function upgradeDB(currentVersion) { if (currentVersion 2) { // 从版本1升到版本2给 t_user 加一个 age 字段 plus.sqlite.executeSql({ name: appdb, sql: ALTER TABLE t_user ADD COLUMN age INTEGER DEFAULT 0 }) } }版本号可以存在一个单独的t_config表里或者用 Storage 存。用 Storage 更简单但要注意 Storage 和数据库可能不同步比如用户清了应用数据但 Storage 还在或者反过来所以更稳妥的是存在数据库自己的元信息表里。6.3 事务的使用与性能差异批量插入数据时如果不走事务每条 INSERT 都是一次独立的磁盘写入速度会慢到让你怀疑人生。我实测过插入一千条数据不用事务大概要好几秒用事务能压到几百毫秒以内。plus.sqlite 提供了transaction方法plus.sqlite.transaction({ name: appdb, operation: begin, success: function() { // 执行一批 executeSql // ... plus.sqlite.transaction({ name: appdb, operation: commit }) } })注意事务里的 executeSql 是异步的要确保所有插入都完成后再 commit否则可能丢数据。稳妥的做法是用 Promise 把所有插入包起来全部 resolve 之后再 commit。6.4 查询结果的字段类型陷阱plus.sqlite 的selectSql返回的是一个对象数组但字段类型有时候和你想的不一样。比如你建表时用了INTEGER查出来可能是数字也可能是字符串取决于原生层的实现细节。我遇到过COUNT(*)查出来是字符串5而不是数字5的情况直接拿去比较就出错了。所以我的习惯是查询结果拿到之后对关键字段做一次显式类型转换。数字用Number()布尔用!!别偷懒直接信原生返回的类型。这个习惯帮我避免了好几个线上 bug。7. 关于 SQLite 打包这件事我最后想说的几点经验绕了一大圈回到打包这个核心。SQLite 在 uniapp 里的打包本质上就是让原生模块正确进入安装包并让 JS 层能正确调用到它。听起来简单但每一个环节都有坑。我的核心经验可以浓缩成几条第一调试就用自定义基座别用标准基座自欺欺人第二manifest 里的 SQLite 模块必须勾这是硬性前提第三所有 plus API 都用 APP-PLUS 条件编译包住这是保命习惯第四数据库路径统一用_doc别自作聪明改路径第五建表用 IF NOT EXISTS改表用版本号管理这是长期维护的基础。还有一点值得单独提打包配置这件事最好在项目初期就定下来并写进文档。我见过太多项目前期用 Storage 凑合中期想换 SQLite结果因为打包配置没规划好改造成本极高。如果你现在正在做技术选型且项目有离线数据需求我的建议是尽早把 SQLite 的打包链路跑通哪怕先只建一张测试表也比后期返工强。至于数据库的增删改查封装、性能优化、大数据量分页这些话题属于打包跑通之后的进阶内容涉及的面更广我打算放在后续的分享里单独展开。这一篇先把能跑起来这件事做扎实地基打牢了上面盖什么楼都稳。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑