QQuickImageProvider, async loading many images and threads.
-
Hello,
Here are problem: 150+ elements with image in GridLayout (or GridView, not so important) and I want to implement caching/handling images before they come to QML via QQuickImageProvider (or QQuickAsyncImageProvider).Approach 1: Make provider class that inherits from QQuickImageProvider, implement requestImage (like in official example imageprovider-example ) only with loading images via QNetworkManager + handling (rounding image) + caching.
Problem: If Image.asynchronous = false then image loading pretty slow and there are some visual laggs in qml (scene not redrawing while image loading); If Image.asynchronous = true then another 2 variant: 1.1 If NOT try to make method requestImage "reentrant" like written in docs then app crush; 1.2 If try to make MutexLocker in requestImage then no one image will load because loading get stucked on trying load first image (but app will work) right on string with eventLoop.exec():QImage MenuImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { QMutexLocker locker(&mutex); QUrl url("path_to_image_in_internet" + id); QNetworkAccessManager *manager = new QNetworkAccessManager; QNetworkReply* reply = manager->get(QNetworkRequest(url)); QEventLoop eventLoop; QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit())); eventLoop.exec(); // all get stucked here if using QMutexLocker locker(&mutex); if (reply->error() != QNetworkReply::NoError) return QImage(); image = QImage::fromData(reply->readAll()); // .. + some handling and caching stuff size->setWidth(image.width()); size->setHeight(image.height()); return image; }
Approach 2. Make provider class that inherits from QQuickAsyncImageProvider, implement requestImageResponse and so on like in official example imageresponseprovider-example ) also with loading images via QNetworkManager + handling + caching.
Problem: after creating 150+ elements with images via Repeater + model I see how about 100 images successfully loads but after i got error in console output (but app still working):QThread::start: Failed to create thread (Недостаточно памяти для обработки команды.)
translate from russian: Not enough memory for handle command
It happend on Windows. If I launch this app on slow Android with 1 gb memory then app got crush with message about unable creating thread.And if I not using any ImageProvider, and use in Image.source url to internet it works correctly (with Image.asynchronous = true or false, not important). So how QML Engine by default handling this, and why that ImageProviders (from official docs) doesn't works? Where my mistake?
There are implemetation of QQuickAsyncImageProvider:
- Base class for loading and handling images that inherits from QObject and QRunnable (like in docs)
class BaseAsyncImageResponseRunnable : public QObject, public QRunnable { Q_OBJECT signals: void done(QImage image); public: BaseAsyncImageResponseRunnable(const QString &id, const QSize &requestedSize, QString imagesPath) : m_id(id), m_requestedSize(requestedSize), m_imagesPath(imagesPath) {} void run() override { QImage image; QUrl url(m_imagesPath + m_id); QNetworkAccessManager *manager = new QNetworkAccessManager; QNetworkReply* reply = manager->get(QNetworkRequest(url)); QEventLoop eventLoop; QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit())); eventLoop.exec(); if (reply->error() != QNetworkReply::NoError) { emit done(image); return; } image = QImage::fromData(reply->readAll()); image = handleImage(image); emit done(image); } virtual QImage handleImage(QImage image) { return image; } protected: QString m_id; QSize m_requestedSize; QString m_imagesPath; };
- Implement QQuickImageResponse in base class
class BaseAsyncImageResponse : public QQuickImageResponse { public: BaseAsyncImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool, BaseAsyncImageResponseRunnable *runnable) { connect(runnable, &BaseAsyncImageResponseRunnable::done, this, &BaseAsyncImageResponse::handleDone); pool->start(runnable); } void handleDone(QImage image) { m_image = image; emit finished(); } QQuickTextureFactory *textureFactory() const override { return QQuickTextureFactory::textureFactoryForImage(m_image); } QImage m_image; };
- Implements QQuickAsyncImageProvider
class MyAsyncImageProvider : public QQuickAsyncImageProvider { public: MyAsyncImageProvider() : QQuickAsyncImageProvider() { } QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override { BaseAsyncImageResponse *response; QString imagesPath = "pat_to_images_in_internet"; auto runnable = new BaseAsyncImageResponseRunnable(id, requestedSize, imagesPath); response = new BaseAsyncImageResponse(id, requestedSize, &pool, runnable); return response; } private: QThreadPool pool; };