ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android平台Qt连接MySQL:NDK交叉编译QMYSQL驱动实战

2026/9/15 3:01:24 拓冰建站 浏览量
Android平台Qt连接MySQL:NDK交叉编译QMYSQL驱动实战 简介面向实际需要在 Qt 5.12.1 Android 应用中接入 MySQL 的开发者这份资源围绕 Qt 默认不含 Android 版 MySQL 驱动的常见问题系统整理了 Ubuntu 与 Windows 环境下从依赖准备、驱动编译、Qt SQL 插件配置、APK 打包到 QSqlDatabase 连接调试的完整参考流程适合中高级 Qt 工程师与移动端跨平台开发人员对照学习。压缩包共 934 个文件约 16.51MB以 C/C 头文件.h、源码.c、txt 说明、m4/configure 构建脚本以及 po/gmo 多语言翻译文件为主不少文件涉及 iconv 字符集转换与多平台兼容处理可为 Android NDK 交叉编译时排查依赖关系提供更加直接的参考依据。已有 776 人学习。内容涵盖依赖安装、armeabi-v7a/arm64-v8a 架构交叉编译、qsql_mysql 驱动集成、数据库加密连接、查询性能优化和版本兼容性检查等关键点读者可利用清晰的目录结构快速定位所需头文件或编译脚本降低在 Qt 5.12.1 Android 工程中接入 MySQL 的环境配置与排错成本。无论用于驱动移植验证、架构扩展还是学习 Qt 插件机制都有较好的参考价值。1. Android 上 Qt 5.12 为什么连不上 MySQL驱动在编译期就欠债了把 Qt 5.12.1 的 Android 工程搭起来QSqlDatabase::addDatabase(QMYSQL)一跑第一次写 App 的人基本都会撞上同一个现象drivers()里只有 QSQLITEQMYSQL 连影子都看不见。这不是你QT sql写错而是 Qt 官方分发的 Android 版默认只编了 SQLite 插件MySQL 的 qsqlmysql 插件需要你拿着 NDK 自己交叉编译出来再和 APK 一起部署。麻烦的还不止编译本身MySQL 8 默认的caching_sha2_password认证插件、armeabi-v7a 与 arm64-v8a 的架构匹配、Android 设备网络环境下 TCP 连接的超时表现三座山叠在一起。这篇文章按一条能完整复现的链路走给 NDK 交叉编译 MySQL Connector/C用 Qt 源码树里的 qsql_mysql 编出驱动插件再讲清楚连接参数、编码和线程模型最后把真机排错时最有用的 logcat 套路收尾。2. 给 Android 交叉编译 MySQL Connector/CNDK 与 CMake 的取舍2.1 为什么非要自己编一个 libmysqlclientWindows 桌面上装个 MySQL Connector/CQt 的 qsqlmysql 插件直接链动态库就能跑。Android 不是这么回事你不可能在设备上装一个libmysqlclient.so.18让程序去系统目录里找Android 的 APK 里也没有 MySQL 官方提供的 prebuilt 客户端库。所以第一步是拿 MySQL 的 C API 源码用 Android NDK 的交叉工具链编出目标架构的libmysqlclient这一步不完成后面 qsql_mysql 插件没有头文件和库可链。我在 Ubuntu 20.04 上处理这个问题时用的是 Qt 5.12.1 官方文档要求的 NDK 版本范围内一条工具链具体版本建议和你 Qt Creator 里自动检测到的保持一致。NDK r19c 之后默认用 clangQt 5.12.1 与其兼容良好你不需要再去找老旧的 gcc 工具链。2.2 交叉编译命令与参数说明把 MySQL Connector/C 源码解压后用它的 CMake 工程配合 NDK 的android.toolchain.cmake是最省事的方式。下面以输出armeabi-v7a静态库为例export ANDROID_NDK/opt/android-ndk-r19c export MYSQL_SRC/work/mysql-connector-c cmake -S $MYSQL_SRC -B /work/build-android \ -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABIarmeabi-v7a \ -DANDROID_PLATFORMandroid-21 \ -DBUILD_SHARED_LIBSOFF \ -DWITH_UNIT_TESTSOFF \ -DWITH_EXAMPLEOFF \ -DCMAKE_INSTALL_PREFIX/work/mysqlclient-android cmake --build /work/build-android --target mysqlclient -j8 cmake --install /work/build-android这里的关键参数逐个说ANDROID_ABI决定编译目标armeabi-v7a是 32 位 ARM 老设备arm64-v8a是 64 位后续 qsqlmysql 插件也要按同一 ABI 编译混用会出现dlopen failed加载错误ANDROID_PLATFORMandroid-21对应 Android 5.0Qt 5.12 的最低支持版本就是 21比它低编译出来的 so 反而可能带一些老系统没有的符号BUILD_SHARED_LIBSOFF是我推荐的静态链接方案把 libmysqlclient 以.a形式链进 qsqlmysql 插件APK 里只需要部署一个 so动态链接库在 Android 的库搜索路径上导致的libmysqlclient.so not found问题直接从源头消失。提示如果 cmake 配置阶段报找不到 zlib把-DCMAKE_INCLUDE_PATH$ANDROID_NDK/sysroot/usr/include和-DCMAKE_LIBRARY_PATH$ANDROID_NDK/sysroot/usr/lib/arm-linux-androideabi加上。NDK r19c 自带 zlib 头文件和库不需要额外下载。2.3 产物理清头文件与静态库各在哪里安装完成后目录下会有两件东西include/mysql.h和include/mysql_com.h等头文件以及lib/libmysqlclient.a。后面编 qsqlmysql 插件时INCLUDEPATH指到这个 include 目录LIBS链这个静态库。验证产物是否确实是 Android 的 ARM 架构这个动作值得做避免你编了半天发现用的是宿主机工具链file /work/mysqlclient-android/lib/libmysqlclient.a输出里能看到ARM字样才算通过。如果出来的是x86-64说明 cmake 的 toolchain 文件没生效回过去检查CMAKE_TOOLCHAIN_FILE路径是否真实指向 NDK 的build/cmake目录。3. qsqlmysql 插件编译与 APK 打包让 QSqlDatabase 认出 QMYSQL3.1 从 Qt 源码树里拆出 qsql_mysqllibmysqlclient 到手后下一步是编出 Qt 的 MySQL 驱动插件。常见做法是重新 configure 整个 Qt for Android但那要跑一遍完整的 Qt 构建时间成本太高。我一般直接从 Qt 源码里把 sqldrivers 目录中需要的部分单独拿出来编找到 qt-everywhere 源码树里的qtbase/src/plugins/sqldrivers复制qsql_mysql目录以及同级的qsqldriverbase.pri到自己的工程目录。qsql_mysql.cpp 内部引用了qsqlcachedresult_p.h、qsqldriver_p.h这些 Qt 私有头无法直接放进普通 App 工程里编译。处理方式是让 pro 文件的INCLUDEPATH指向 Qt for Android 安装目录里自带的 private 头路径这个路径在安装包中是存在的QT core sql TARGET qsqlmysql TEMPLATE lib CONFIG plugin c11 PLUGIN_TYPE sqldrivers SOURCES qsql_mysql.cpp HEADERS qsql_mysql.h MYSQL_INC $$PWD/mysqlclient-android/include MYSQL_LIB $$PWD/mysqlclient-android/lib INCLUDEPATH $$MYSQL_INC \ $$[QT_INSTALL_HEADERS]/QtSql/$$QT_VERSION/QtSql/private LIBS $$MYSQL_LIB/libmysqlclient.a -lzPLUGIN_TYPE sqldrivers这个变量很重要Qt 在 Android 上运行时QSqlDatabase的工厂加载器会固定去 sqldrivers 子目录找驱动 so类型不对或者目录不对插件就算编译出来也扫描不到。$$QT_VERSION在 Android 的 Qt 安装里会自动展开成 5.12.1不用你手写死版本号。3.2 用 Android 版 qmake 编译插件编译时不能用桌面的 qmake要用 Qt for Android 那套路径通常在~/Qt5.12.1/5.12.1/android_armv7/bin/qmakeexport ANDROID_NDK_ROOT/opt/android-ndk-r19c ~/Qt5.12.1/5.12.1/android_armv7/bin/qmake qsql_mysql.pro make -j8编译产物是libqsqlmysql.so。如果 make 阶段报mysql.h: No such file or directory检查MYSQL_INC路径是否指到了 2.3 节验证过架构的那个 include 目录如果报一堆链接错误优先确认LIBS里-lz没有丢静态 libmysqlclient 在 ARM 上依赖 zlib漏掉会报undefined reference to inflate这类符号错误。3.3 把插件塞进 APK--extra-plugins 的正确姿势Android 上 Qt 插件的部署路径是 APK 内的lib/abi/plugins/sqldrivers/androiddeployqt提供了--extra-plugins参数专门处理这一类自定义插件。先建一个目录结构这样放extra_plugins/ └── armeabi-v7a/ └── plugins/ └── sqldrivers/ └── libqsqlmysql.so然后执行androiddeployqt --input android-App-deployment-settings.json \ --output android-build \ --deployment bundled \ --android-platform android-21 \ --gradle \ --extra-plugins extra_pluginsandroid-App-deployment-settings.json是 Qt Creator 构建 Android 工程时会生成的部署配置文件里面已经写好了 APK 包名、架构、Qt 库列表不需要手工维护。部署完用 unzip 检查 APK确认 so 进去了unzip -l android-build/app-debug.apk | grep qsqlmysql检查项期望结果失败时的线索libqsqlmysql.so所在目录lib/armeabi-v7a/plugins/sqldrivers/插件没进 APK运行时QSqlDatabase::drivers()里没有 QMYSQLAPK 内架构与 App 主 so 一致32 位插件装进 64 位 APK运行时静默不加载so 文件大小在几百 KB 以上可能静态链接失败插件编成了空壳提示如果不用--extra-plugins直接把 so 扔到android/libs/armeabi-v7a/并加ANDROID_EXTRA_LIBS那只会把 so 部署到 APK 的lib/abi/根目录不落在 sqldrivers 子目录里QSqlDatabase 一样找不到它。这两种方式的路由不同别混。4. 连接参数、字符集与线程模型QMYSQL 在真机上稳定跑起来4.1 连接参数表与 setConnectOptions驱动部署到位后QSqlDatabase的连接代码可以老老实实写了。一个能在 Android 真机上连远程 MySQL 的最小连接块长这样#include QSqlDatabase #include QSqlQuery #include QSqlError bool openMysql(const QString host, quint16 port, const QString dbName, const QString user, const QString passwd) { QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL, app_conn); db.setHostName(host); db.setPort(port 0 ? port : 3306); db.setDatabaseName(dbName); db.setUserName(user); db.setPassword(passwd); db.setConnectOptions(MYSQL_OPT_CONNECT_TIMEOUT5); if (!db.open()) { qCritical() open mysql failed: db.lastError().text(); return false; } return true; }setConnectOptions里我明确设置了MYSQL_OPT_CONNECT_TIMEOUT5。QSqlDatabase 不开这个选项时底层 libmysqlclient 的默认 TCP 超时在 60 秒级别Android 设备在蜂窝网络或弱 Wi-Fi 环境下一次无效 IP 的连接会卡住整个界面的等待逻辑用户早就划走了。5 秒是相对合理的折中。连接项典型值说明setHostName192.168.1.10或域名不能用localhostAndroid 上没有 MySQL 的 unix socket必须走 TCP/IPsetPort3306不显式设置时默认 3306但显式写有利于排查网络问题setDatabaseNameappdb对应服务端的 database 名称setConnectOptionsMYSQL_OPT_CONNECT_TIMEOUT5连接超时单位秒不要和查询超时混淆MySQL 服务端bind-address如果是127.0.0.1那手机永远连不上。这一步看着是 MySQL 安装配置里最基础的事情但恰恰是 Android 连 MySQL 排错里出现频率最高的原因。服务端要监听0.0.0.0或内网地址并且 MySQL 账号的 host 字段要覆盖手机来源qtapp%是开发环境最简单也最管用的写法。4.2 字符集SET NAMES utf8mb4 必须放在连接之后Qt 5.12 的 qsql_mysql 驱动在建立连接时会用MYSQL_SET_CHARSET_NAME设置成 utf8这里的 utf8 在 MySQL 里是 utf8mb3存储不了 emoji 和部分生僻汉字。我每连上一个 MySQL 8 实例第一件事就是执行一条语句SET NAMES utf8mb4;配合服务端建库时指定ALTER DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;utf8mb4_unicode_ci比 MySQL 8 默认的utf8mb4_0900_ai_ci兼容面广旧版本驱动和服务端在拿 0900 系列规则比较时可能报排序规则冲突用utf8mb4_unicode_ci能少踩这个坑。SET NAMES 只影响当前连接所以连接池每次新建连接都要执行一遍不要指望一次设置全局生效。4.3 线程模型QSqlDatabase 有线程亲和性QSqlDatabase 连接不能在创建它的线程之外使用这是 Qt 文档反复强调的边界Android 上尤其容易踩。界面线程创建了一个连接然后丢到QtConcurrent::run的 lambda 里执行查询轻则报QSqlDatabasePrivate::database: requested database does not belong to the calling thread重则直接 crash。正确做法是把数据库操作整体放进工作线程在线程函数内部创建连接并使用class DbWorker : public QThread { Q_OBJECT protected: void run() override { QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL, worker_conn); db.setHostName(m_host); db.setPort(m_port); db.setDatabaseName(m_db); db.setUserName(m_user); db.setPassword(m_passwd); if (db.open()) { QSqlQuery query(db); query.exec(SELECT id, name FROM user WHERE level 10); // 处理结果集并 signal 回主线程 } db.close(); QSqlDatabase::removeDatabase(worker_conn); } };removeDatabase必须在同一个线程内、连接关闭后调用。很多人在这里只 close 忘了 removeDatabase下次 addDatabase 同名连接时 Qt 会打印connection still in use警告且底层句柄没有真正释放。Android 的 Activity 在旋转屏幕时会重建如果连接被保存成 Activity 成员变量重建后那个 QSqlDatabase 对象已经失效。把所有数据库访问收敛到后台线程、和界面生命周期解耦是 Qt for Android 上做 MySQL 访问最稳的架构选择。4.4 连接前的 TCP 层探活在 Android 上MySQL 3306 端口不通的暴露往往很晚因为驱动层报的是Cant connect to MySQL server on ...但用户看到的是转圈几十秒。我习惯在建立 QSqlDatabase 之前先用 QTcpSocket 探测一次端口把 TCP 层问题和 MySQL 认证问题分开QTcpSocket probe; probe.connectToHost(host, 3306); if (!probe.waitForConnected(3000)) { qWarning() port 3306 unreachable: probe.errorString(); return false; } probe.disconnectFromHost();Android 9 之后的 NetworkSecurityPolicy 不会拦 QTcpSocket 这类原生 socket所以 MySQL 3306 走明文 TCP 不受usesCleartextTraffic影响不用担心这个配置误伤数据库连接。5. logcat 三连驱动没加载、认证插件冲突、端口探活的排错套路驱动和 APK 都部署完后真机验证看三条 logcat 线索就够定位九成问题。第一类QSqlDatabase::drivers()输出里没有 QMYSQL调用 open 报Driver not loaded。用 logcat 过滤adb logcat -v time | grep -i -E qsql|mysql|plugin看到QSqlDatabase: MYSQL driver not loaded时先unzip -l app-debug.apk | grep qsqlmysql确认插件在 APK 的lib/abi/plugins/sqldrivers/里插件存在但还报 not loaded就是架构不匹配检查 APK 是 arm64 而插件编的 armeabi-v7a或者反过来。64 位设备上如果只放了 32 位插件Qt 不会主动降级去加载。第二类连接时报Authentication plugin caching_sha2_password cannot be loaded。这是 MySQL 8 默认认证插件和你手编的旧版 libmysqlclient 不兼容。MySQL 官方 8.0.11 之前的客户端库不认识 caching_sha2_password而 Qt 5.12 时代编出来的多数是 6.1.x 版本。不用重编库直接在服务端给这个账号换回老插件ALTER USER qtapp% IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;第三类TCP 层通、MySQL 没连接上。复用 4.4 节的 QTcpSocket 探针把waitForConnected(3000)的返回值打成日志。探活失败先查服务器安全组和bind-address探活成功但 QSqlDatabase open 失败则检查账号 host 是否覆盖设备出口 IP、密码是否正确以及服务端max_connect_errors是不是已经把这个 IP 拉黑了。最后补一个能少走弯路的部署技巧把 libmysqlclient 的 CMake 编译脚本和 qsql_mysql 的 pro 文件放进独立目录封装成一个 shell 脚本ANDROID_ABI作为唯一参数。换新项目时只需要重跑脚本同一个libqsqlmysql.so提前部署到多项目共享的本地 maven 仓库比每个人在各自电脑上重编一遍省出一个下午的排错时间。本文还有配套的精品资源点击获取