1. 问题背景
在将Qt应用移植到鸿蒙平台时,资源加载往往是继编译之后的第二个大坑。在Desktop平台,我们习惯使用QFile直接读取本地文件系统,或者使用qrc:/资源系统。在Android上,Qt提供了assets:/前缀来访问APK内的资源。
然而,在OpenHarmony上,应用的资源被打包在HAP中,且受到了严格的沙箱限制。很多开发者发现,直接使用QFile("assets:/config.ini")或者相对路径读取文件,都会返回失败。
典型报错:
QFile::open: No such file or directory
QImage: Failed to load image "assets:/bg.png"
2. 鸿蒙的资源管理机制
OpenHarmony提供了Rawfile机制来存放任意格式的原始文件。这些文件位于项目的resources/rawfile目录下。
但是,Rawfile并不直接映射到一个普通的文件系统路径。它需要通过ResourceManager NDK接口进行访问。这意味着标准的C++ std::ifstream 或 Qt 的 QFile 默认是无法读取它们的,除非Qt for OpenHarmony进行了特定的适配。
资源访问流程图
graph LR
A[Qt App] -->|QFile("rawfile://...")| B{Qt Abstract File Engine}
B -->|Adaptation Layer| C[ResourceManager NDK]
C -->|Read| D[HAP/HSP Rawfile Directory]
D -->|Data Stream| C
C -->|Buffer| B
B -->|QByteArray| A
3. 实战Bug:配置文件读取失败
场景
我们的应用在启动时需要读取一个config.json,用于初始化网络服务器地址。
错误代码
// 试图直接读取
QFile file("resources/rawfile/config.json");
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to load config!"; // 这里总是被触发
return;
}
或者尝试Android习惯:
QFile file("assets:/config.json"); // 鸿蒙不支持 assets:/ 协议
解决方案 A:使用Qt for OpenHarmony的特殊前缀
Qt 6.x for OpenHarmony(取决于具体版本和发行版,如LTS版本)通常实现了一个自定义的文件引擎。经过深入源码分析和文档挖掘,我们发现正确的前缀往往是assets:/(部分版本实现了兼容)或者需要我们自己实现文件引擎。
但在标准鸿蒙NDK开发中,最稳健的方式是使用原生Rawfile接口读取,然后传递给Qt,或者将Rawfile拷贝到沙箱的可写目录下。
解决方案 B:拷贝策略(推荐用于SQLite或频繁读取的小文件)
因为Rawfile在压缩包内,随机读写性能较差且只读。最好的办法是在App首次启动时,将关键文件从Rawfile拷贝到QStandardPaths::AppDataLocation。
核心代码实现:
我们需要编写一个Helper类,利用鸿蒙的NativeResourceManager。
// RawfileHelper.h
#include <rawfile/raw_file_manager.h>
#include <QString>
#include <QStandardPaths>
#include <QFile>
#include <QDebug>
class RawfileHelper {
public:
// 初始化资源管理器(需要从NAPI层传入NativeResourceManager*)
static void init(NativeResourceManager* resMgr);
// 读取Rawfile内容
static QByteArray readRawFile(const QString& fileName);
// 拷贝Rawfile到沙箱目录
static bool copyRawFileToSandbox(const QString& rawFileName, const QString& destFileName);
private:
static NativeResourceManager* s_resMgr;
};
// RawfileHelper.cpp
NativeResourceManager* RawfileHelper::s_resMgr = nullptr;
QByteArray RawfileHelper::readRawFile(const QString& fileName) {
if (!s_resMgr) {
qWarning() << "ResourceManager not initialized!";
return QByteArray();
}
// 打开Rawfile
RawFile* rawFile = OpenRawFile(s_resMgr, fileName.toUtf8().constData());
if (!rawFile) {
qWarning() << "Failed to open rawfile:" << fileName;
return QByteArray();
}
// 获取文件大小
long length = GetRawFileSize(rawFile);
QByteArray buffer(length, 0);
// 读取内容
int ret = ReadRawFile(rawFile, buffer.data(), length);
if (ret < 0) {
qWarning() << "Failed to read rawfile";
}
// 关闭
CloseRawFile(rawFile);
return buffer;
}
bool RawfileHelper::copyRawFileToSandbox(const QString& rawFileName, const QString& destFileName) {
QByteArray data = readRawFile(rawFileName);
if (data.isEmpty()) return false;
// 目标路径:/data/app/el2/100/base/com.example.app/files/...
QString fullPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + destFileName;
// 确保目录存在
QDir dir = QFileInfo(fullPath).absoluteDir();
if (!dir.exists()) dir.mkpath(".");
QFile dest(fullPath);
if (dest.open(QIODevice::WriteOnly)) {
dest.write(data);
dest.close();
return true;
}
return false;
}
4. 集成到Qt启动流程
为了让这个Helper工作,我们需要在NAPI初始化阶段获取NativeResourceManager。
Init.cpp (NAPI Layer):
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <rawfile/raw_file_manager.h>
#include "RawfileHelper.h"
// 在模块初始化或Ability创建时调用
void InitResourceManager(napi_env env, napi_value jsResMgr) {
NativeResourceManager* nativeResMgr = OH_ResourceManager_InitNativeResourceManager(env, jsResMgr);
RawfileHelper::init(nativeResMgr);
}
一旦拷贝完成,Qt代码就可以像往常一样工作了:
// 业务代码
QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/config.json";
QFile file(configPath); // 现在这是标准文件系统路径,QFile完美支持
// ...
5. 进阶:qrc vs Rawfile
Q: 为什么不直接用 qrc (Qt Resource System)?
A: qrc是最好的跨平台方案。如果你的资源文件不大(如图标、小配置、QML文件),强烈建议使用qrc。它们会被编译进二进制文件,不依赖鸿蒙的文件系统API,完全跨平台。
使用Rawfile的场景:
- 文件体积很大(视频、大数据库),编译进二进制会导致so过大,加载变慢。
- 需要动态更新的资源(虽然Rawfile也是只读,但通常作为初始版本)。
- 与其他鸿蒙原生组件共享的资源。
6. 总结
- 小资源:直接用Qt的资源系统 (
qrc:/),最省心。 - 大文件/配置:放在
resources/rawfile。 - 读取方式:
- 不要指望
QFile("assets:/")开箱即用(除非验证过你的Qt版本支持)。 - 实现一个Native Helper类,利用鸿蒙NDK API读取。
- 对于需要随机读写的文件(如SQLite DB),必须先拷贝到沙箱可写目录(
QStandardPaths::AppDataLocation)。
- 不要指望
通过这种"拷贝+标准访问"的策略,我们既规避了文件引擎的兼容性问题,又保留了Qt代码的纯净性。

1485

被折叠的 条评论
为什么被折叠?



