1. 列表卡顿之谜

我们有一个展示照片墙的Grid View。当用户快速滑动时,界面出现严重的掉帧(Jank),甚至偶尔会卡死几秒钟。
查看Profiler,发现内存占用在滑动时飙升到500MB+,频繁触发GC。

2. 罪魁祸首:主线程解码与全尺寸加载

问题一:主线程解码
如果使用 Image { source: "file:///..." },Qt Quick通常会在后台线程解码图片。
但如果是通过C++ QAbstractListModel 提供 QImageQPixmap 给QML,很多开发者会直接在 data() 方法中加载图片。

// ❌ 绝对禁止的操作
QVariant MyModel::data(const QModelIndex &index, int role) const {
    if (role == Qt::DecorationRole) {
        QImage img("high_res_photo.jpg"); // IO + 解码,耗时50ms+
        return img;
    }
    return QVariant();
}

data() 是在主线程调用的,50ms的耗时意味着丢了3帧。

问题二:全尺寸加载
一张 4000x3000 的照片,解码后占用内存约 48MB (400030004 bytes)。
如果屏幕上只显示一个 200x200 的缩略图,加载原图纯属浪费内存和带宽。

3. 解决方案:异步加载与缩略图

策略图解

Request
Async
QImageReader
Scale Down
QImage
Texture
QML Image
QQuickImageProvider
ThreadPool
File System

实战:自定义ImageProvider

我们需要实现一个 QQuickAsyncImageProvider(Qt 6推荐)。

class ThumbnailProvider : public QQuickAsyncImageProvider {
public:
    QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override {
        auto response = new ThumbnailResponse(id, requestedSize);
        return response;
    }
};

关键优化:利用 QImageReader 缩放

在解码阶段直接缩放,而不是解码后再缩放。

void ThumbnailResponse::run() {
    QImageReader reader(m_filePath);
    
    // 这里的requestedSize是QML中 sourceSize 属性传递过来的
    if (m_requestedSize.isValid()) {
        reader.setScaledSize(m_requestedSize); 
    }
    
    m_image = reader.read(); // 此时读出来的已经是小图了
    emit finished();
}

QML 侧配合

在QML中,必须指定 sourceSize

Image {
    width: 200; height: 200
    // 告诉Provider我们需要多大的图,这对于节省内存至关重要
    sourceSize: Qt.size(width, height) 
    source: "image://thumbnail/path/to/photo.jpg"
    asynchronous: true // 确保异步
}

4. 鸿蒙特有的PixelMap优化

鸿蒙提供了高效的图片容器 PixelMap。如果可能,我们可以利用鸿蒙原生的图片解码能力(ImageSource API),解码出 PixelMap,然后通过NAPI传递给Qt(可能需要自定义Texture转换,较复杂)。

但在常规Qt开发中,优化 QImageReader 的使用已经能解决90%的问题

5. 缓存策略

除了异步和缩放,缓存也是关键。
Qt Quick的 Image 组件自带缓存。但在C++层,我们可以引入 QCache<QString, QImage> 来复用解码后的图片。

// 简单LRU缓存
if (m_cache.contains(id)) {
    return m_cache[id];
}
// Load...
m_cache.insert(id, newImage);

6. 总结

高性能图片加载的三板斧:

  1. 异步:绝不在主线程解码。
  2. 缩放QImageReader::setScaledSize,按需加载。
  3. 缓存:内存换CPU,避免重复解码。

通过这些优化,我们的Grid View在鸿蒙手机上即使滑动几千张高清大图,依然如丝般顺滑。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐