简介:这份资源面向需要在 Qt 5.12.1 的 Android 应用中接入 MySQL 数据库的开发者,尤其适合具备一定 Qt 与 NDK 交叉编译基础、希望打通移动端数据存储链路的中高级工程师。内容围绕 MySQL 驱动在 Android 平台的编译与集成展开,覆盖依赖安装、驱动交叉编译、Qt 插件创建、打包配置、连接测试及安全与性能优化等关键环节,可帮助读者解决默认 Qt 库不含 Android MySQL 支持这一常见痛点。压缩包共 934 个文件,约 16.51MB,以 h 头文件、txt 说明、c 源码、m4 与 configure 构建脚本、po 与 gmo 本地化文件为主,另含 pro 工程文件、so 动态库及 readme、changelog 等文档,目录结构完整,便于按模块查阅与复用。目前已有 777 人学习下载,适合作为跨平台数据库集成的实操参考。
1. Qt Android 连 MySQL:为什么直连是死路,中间层才是活路
很多人第一次听到「Qt Android 连 MySQL」这个组合,脑子里浮现的画面很直接:Qt 写个界面,Android 上跑起来,代码里QSqlDatabase::addDatabase("QMYSQL")一挂,查询就出来了。我当初也是这么想的,直到在真机上跑出第一行报错——驱动加载失败,因为 Qt for Android 的预编译包里压根没带 MySQL 驱动。这不是配置问题,是架构问题。
这个标题真正要解决的需求很明确:用 Qt 做 Android 端应用,数据存在 MySQL 里,怎么让两端通起来。适合两类人:一是手里已经有 MySQL 数据库、想用 Qt 快速搭一个 Android 客户端的开发者;二是正在做 Qt 跨平台项目、需要移动端访问服务端数据的团队。核心结论先摆出来:Qt Android 不要直连 MySQL,走 HTTP 中间层。下面把这条路从选型到跑通拆开讲。
2. 三条技术路线的取舍:直连、JDBC 桥、HTTP 中间层
2.1 直连 MySQL 为什么在 Android 上走不通
Qt 的 SQL 模块通过插件机制加载数据库驱动,桌面端编译 Qt 时如果带上 MySQL 客户端库,QMYSQL驱动就能用。但 Android 平台是另一回事:Qt for Android 的官方预编译包只包含 SQLite 驱动,MySQL 驱动需要你自己交叉编译 libmysqlclient 和对应的 Qt 插件,还要处理 Android NDK 的 ABI 兼容、OpenSSL 依赖、动态库打包进 APK 的路径问题。这一套下来,光是编译环境就能耗掉几天。
更关键的是安全问题。MySQL 的连接凭据要硬编码在 APK 里,APK 是可以反编译的,等于把数据库账号密码直接交给每一个装了应用的人。数据库端口暴露在公网,任何拿到凭据的人都能直连操作你的库。这不是「注意一下」能解决的,是方案本身就不该这么设计。
提示:如果你在网上看到有人贴出「Qt Android 直连 MySQL 成功」的代码,先确认他是不是在桌面端跑的,或者用的是本地局域网内的测试库。真机 + 公网 + 生产库,这条路走不通。
2.2 JDBC 桥接方案的适用边界
Android 原生开发里连 MySQL 常见做法是走 JDBC,通过mysql-connector-java在 Java 层建立连接。Qt Android 理论上可以借助 JNI 调用 Java 层的 JDBC 代码,把查询结果再传回 C++。这个方案能跑通,但代价是:你得同时维护 C++ 和 Java 两套代码,JNI 的类型转换和线程模型容易出问题,而且 JDBC 驱动本身在 Android 上也有兼容性坑。最要命的是,它依然没有解决凭据暴露和数据库直连的安全问题,只是换了个语言来直连。
我一般会把这个方案定位成「过渡方案」:如果你已经有一个 Java 层的模块在跑 JDBC,Qt 端只是临时接一下,可以用。但如果是新项目,不值得为它引入 JNI 的复杂度。
2.3 HTTP 中间层:我实际项目里用的方案
最终落地的方案是:Qt Android 端用QNetworkAccessManager发 HTTP 请求,服务端用任意语言(PHP、Python、Java、Node.js 都行)写接口,接口层去连 MySQL。这样做的收益是多方面的:
- Android 端不需要任何 MySQL 驱动,Qt 的网络模块开箱即用
- 数据库凭据只存在于服务端,客户端拿不到
- 接口层可以做鉴权、限流、参数校验,安全边界清晰
- 换数据库、改表结构,客户端不用重新发版
代价是多了一层服务端要维护。但对于任何要上线的应用来说,这层本来就该有。下面几章就按这个方案,从服务端接口到 Qt 端调用,一步步跑通。
3. 服务端接口:用 PHP 写一个最小可用的 MySQL 查询接口
3.1 数据库准备与建表
先在 MySQL 里建一个测试库和表。假设你已经装好了 MySQL(Windows 上用安装包,Linux 上用apt install mysql-server或yum install mysql-server),登录后执行:
-- 创建测试数据库 CREATE DATABASE qt_android_demo DEFAULT CHARACTER SET utf8mb4; -- 切换到该库 USE qt_android_demo; -- 建一张用户表,字段类型覆盖常见场景 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, age INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入两条测试数据 INSERT INTO users (username, age) VALUES ('zhangsan', 25), ('lisi', 30);这里几个参数值得说明:utf8mb4是为了支持完整的 Unicode(包括 emoji),比utf8更稳妥;DEFAULT 0是给 age 字段设默认值,对应热搜里「mysql设置默认值为0」的场景;ENGINE=InnoDB是事务型引擎,生产环境基本都用它。
3.2 PHP 接口脚本
服务端用 PHP 写一个最简单的查询接口,返回 JSON。文件放在 Web 服务器的可访问目录下,命名为get_users.php:
<?php // 设置响应类型为 JSON header('Content-Type: application/json; charset=utf-8'); // 数据库连接参数 $host = '127.0.0.1'; $dbname = 'qt_android_demo'; $user = 'demo_user'; // 不要用 root $pass = 'your_password'; // 换成实际密码 try { // 使用 PDO 连接,指定字符集 $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass); // 让 PDO 在出错时抛异常,而不是静默失败 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 查询用户列表 $stmt = $pdo->query("SELECT id, username, age FROM users ORDER BY id"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // 返回统一格式的 JSON echo json_encode(['code' => 0, 'data' => $rows], JSON_UNESCAPED_UNICODE); } catch (PDOException $e) { // 出错时返回错误码,不暴露具体错误信息给客户端 http_response_code(500); echo json_encode(['code' => 1, 'msg' => 'server error']); }逻辑说明:PDO 是 PHP 访问 MySQL 的推荐方式,支持预处理语句,能防 SQL 注入。JSON_UNESCAPED_UNICODE保证中文不被转义成\uXXXX。错误处理里故意不把$e->getMessage()返回给客户端,避免泄露数据库结构。
参数说明:$host用127.0.0.1而不是localhost,因为localhost在部分环境下会走 Unix socket,对应热搜里error 2002 (HY000): can't connect to local MySQL server through socket那个经典报错。用127.0.0.1强制走 TCP,行为更可预期。
3.3 验证接口能通
在浏览器或命令行里访问这个接口,确认返回正常:
# 用 curl 测试接口 curl -s http://你的服务器地址/get_users.php期望输出类似:
{"code":0,"data":[{"id":1,"username":"zhangsan","age":25},{"id":2,"username":"lisi","age":30}]}如果返回 500 或空,先看 PHP 错误日志,再确认 MySQL 用户权限。常见问题是demo_user没有从127.0.0.1访问的权限,需要执行GRANT SELECT ON qt_android_demo.* TO 'demo_user'@'127.0.0.1' IDENTIFIED BY 'your_password';然后FLUSH PRIVILEGES;。
4. Qt Android 端:用 QNetworkAccessManager 调接口并解析 JSON
4.1 项目配置与网络权限
在 Qt Creator 里新建一个 Qt Quick Application 或 Qt Widgets Application,目标平台选 Android。关键配置在.pro文件里:
QT += core gui networknetwork模块必须显式加上,否则QNetworkAccessManager用不了。Android 端还需要在AndroidManifest.xml里声明网络权限:
<uses-permission android:name="android.permission.INTERNET"/>Qt Creator 的 Android 构建套件通常会自动生成 Manifest,如果没有,在项目目录下找到android/AndroidManifest.xml手动加上这一行。另外,Android 9 以后默认禁止明文 HTTP,如果接口是 HTTP 而非 HTTPS,需要在 Manifest 的<application>标签里加android:usesCleartextTraffic="true"。生产环境建议上 HTTPS。
4.2 发起请求与解析 JSON 的完整代码
下面是一个可复用的网络请求封装,放在main.cpp或单独的类里都行:
#include <QCoreApplication> #include <QNetworkAccessManager> #include <QNetworkRequest> #include <QNetworkReply> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QDebug> // 发起 GET 请求并解析返回的 JSON void fetchUsers() { QNetworkAccessManager *manager = new QNetworkAccessManager(); // 替换成你的接口地址 QUrl url("http://你的服务器地址/get_users.php"); QNetworkRequest request(url); // 设置超时,避免网络异常时一直挂起 request.setTransferTimeout(10000); QNetworkReply *reply = manager->get(request); // 用信号槽处理异步返回 QObject::connect(reply, &QNetworkReply::finished, [reply, manager]() { if (reply->error() != QNetworkReply::NoError) { qDebug() << "请求失败:" << reply->errorString(); reply->deleteLater(); manager->deleteLater(); return; } // 读取原始字节 QByteArray data = reply->readAll(); QJsonParseError parseError; QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (parseError.error != QJsonParseError::NoError) { qDebug() << "JSON 解析失败:" << parseError.errorString(); reply->deleteLater(); manager->deleteLater(); return; } // 解析顶层对象 QJsonObject root = doc.object(); if (root.value("code").toInt() != 0) { qDebug() << "接口返回错误"; reply->deleteLater(); manager->deleteLater(); return; } // 遍历 data 数组 QJsonArray users = root.value("data").toArray(); for (const QJsonValue &item : users) { QJsonObject user = item.toObject(); qDebug() << "id:" << user.value("id").toInt() << "username:" << user.value("username").toString() << "age:" << user.value("age").toInt(); } reply->deleteLater(); manager->deleteLater(); }); }逻辑说明:QNetworkAccessManager的请求是异步的,必须用信号槽或 lambda 处理finished信号。setTransferTimeout设置 10 秒超时,对应热搜里 Qt 网络请求卡死的场景。JSON 解析分两步:先QJsonDocument::fromJson拿到文档,再逐层取object和array。deleteLater是 Qt 对象的安全释放方式,不能在信号槽里直接delete。
参数说明:QNetworkRequest::setTransferTimeout在 Qt 5.15 及以上可用,如果你用的是更早版本,需要自己用QTimer实现超时。接口地址里的 IP 或域名要确保 Android 设备能访问到,模拟器访问宿主机一般用10.0.2.2代替127.0.0.1。
4.3 在 Android 真机上跑通的检查清单
代码写完后,从编译到真机运行,有几个必查项:
| 检查项 | 常见问题 | 处理方式 |
|---|---|---|
| 网络权限 | Manifest 缺 INTERNET 权限 | 补<uses-permission> |
| 明文 HTTP | Android 9+ 默认禁止 | 加usesCleartextTraffic="true" |
| 地址可达 | 用了 127.0.0.1 | 换成服务器实际 IP 或域名 |
| 防火墙 | 服务端 80 端口未开放 | 检查安全组和 iptables |
| 字符编码 | 中文乱码 | 服务端和客户端统一 utf8mb4 |
这张表里的每一项我都在不同项目里踩过。尤其是「地址可达」这条,模拟器上跑通不代表真机跑通,真机的网络环境和模拟器完全不同。
5. 避坑与排查:Qt Android 连 MySQL 中间层最常见的 5 个翻车点
5.1 现象:应用启动就闪退,日志里出现cannot mix incompatible Qt library
原因:APK 里打包了多个版本的 Qt 库,或者设备上残留了旧版本。这在热搜里fatal: cannot mix incompatible qt library (version ex50601) with this librar就是典型表现。
解决:清理构建目录,重新执行qmake和make,确保QT_VERSION一致。如果设备上装过旧版应用,先卸载再装。用adb logcat看完整堆栈,确认是哪个库版本冲突。
5.2 现象:请求一直 pending,界面卡住不返回
原因:QNetworkAccessManager默认没有超时,网络不通时会一直等。或者你在主线程里用了同步等待。
解决:设置setTransferTimeout,并且永远不要在 UI 线程里做同步网络请求。所有网络操作走异步信号槽。如果服务端响应慢,考虑加 loading 状态提示用户。
5.3 现象:JSON 解析出来是空,但接口在浏览器里正常
原因:服务端返回的 Content-Type 不是application/json,或者返回内容前面有 BOM 头。Qt 的QJsonDocument::fromJson对 BOM 敏感。
解决:服务端确保header('Content-Type: application/json; charset=utf-8'),并且 PHP 文件保存为无 BOM 的 UTF-8。用curl -v看响应头确认。
5.4 现象:真机上请求失败,模拟器正常
原因:模拟器通过10.0.2.2访问宿主机,真机需要走局域网 IP 或公网地址。另外真机的网络权限、代理设置、DNS 都可能不同。
解决:真机测试时,确保手机和服务器在同一网络,或者服务器有公网 IP。用手机浏览器先访问接口地址,确认网络层通。如果公司网络有代理,需要在 Qt 里配置QNetworkProxy。
5.5 现象:MySQL 连接数暴涨,服务端报Too many connections
原因:每次请求都新建 PDO 连接,没有复用。高并发下连接数迅速打满。
解决:PHP 端可以用持久连接(PDO::ATTR_PERSISTENT => true),但更推荐用连接池方案。如果服务端是 Java,用 HikariCP;Node.js 用mysql2的连接池。对应热搜里「mysql的数据库连接池」这个点,连接池不是可选项,是生产环境必选项。
6. 进阶:把接口层换成 Qt 自己的 HTTP Server,以及验证数据一致性的技巧
走到这里,基础链路已经通了。但如果你不想维护 PHP 环境,还有一个进阶玩法:用 Qt 自己写一个 HTTP Server,跑在服务端机器上,直接连 MySQL。Qt 从 5.0 开始就有QHttpServer(需要额外模块),或者用第三方库如QtWebApp。这样整个技术栈统一在 C++ 里,部署时只需要一个可执行文件。
具体做法是:在服务端用QSqlDatabase连 MySQL(桌面端 Qt 自带 QMYSQL 驱动,编译时加上即可),然后用QHttpServer暴露 REST 接口。Android 端代码完全不用改,还是发 HTTP 请求。这个方案的好处是团队如果只熟悉 Qt,不需要额外学 PHP 或 Java。代价是 Qt 的 HTTP Server 生态不如成熟 Web 框架丰富,鉴权、限流这些要自己实现。
验证数据一致性有个实用技巧:在服务端接口里加一个version字段,每次数据变更时递增。Android 端缓存上次的 version,请求时带上,服务端对比后如果没变就返回 304,减少数据传输。这在数据量大、变更不频繁的场景下很有效。
// Android 端带 version 请求 QUrl url("http://服务器地址/get_users.php?version=" + QString::number(lastVersion));服务端收到 version 后,先查当前版本号,一致就返回{"code":0,"data":[],"version":lastVersion},客户端跳过解析。这个模式在移动端弱网环境下能明显减少流量和等待时间。
我自己在这个方案上最大的教训是:一开始总想着「能不能少一层」,觉得多一个服务端就多一份运维成本。后来发现,那一层不是成本,是安全边界和灵活性。数据库直连省下来的那点开发时间,在第一次安全审计或者第一次改表结构时就会加倍还回去。希望帮到你。
本文还有配套的精品资源,点击获取