Qt使用內存、硬盤、網絡三級緩存來獲取圖片列表並列表顯示的例程

運行效果

主要代碼

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>

namespace Ui {
class MainWidget;
}

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MainWidget(QWidget *parent = 0);
    ~MainWidget();

private slots:

private:
    Ui::MainWidget *ui;
};

#endif // MAINWIDGET_H

mainwidget.cpp

#include "mainwidget.h"
#include "ui_mainwidget.h"

#include "imagelistviewmodel.h"
#include "imagelistviewitemdelegate.h"
#include "imagemanager.h"

#include <QDir>

MainWidget::MainWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWidget)
{
    ui->setupUi(this);

    ImageListViewModel *listViewModel=new ImageListViewModel(this);
    ui->listImage->setModel(listViewModel);

    ImageManager *imageManager=new ImageManager(this);
    connect(imageManager, SIGNAL(imageLoaded(int,QString)), listViewModel, SLOT(imageLoaded(int,QString)));
    connect(imageManager, SIGNAL(imageDownloadProgressChanged(int,QString)), listViewModel, SLOT(imageDownloadProgressChanged(int,QString)));

    ImageListViewItemDelegate *delegate=new ImageListViewItemDelegate(imageManager, this);
    ui->listImage->setItemDelegate(delegate);

    ui->listImage->setViewMode(QListView::IconMode);
    ui->listImage->setResizeMode(QListView::Adjust);
    ui->listImage->setSelectionMode(QListView::SingleSelection);
    ui->listImage->setSpacing(10);
    ui->listImage->setWordWrap(true);
    ui->listImage->setUpdatesEnabled(true);
}

MainWidget::~MainWidget()
{
    delete ui;
}

imagemanager.h

#ifndef IMAGEMANAGER_H
#define IMAGEMANAGER_H

#include <QObject>
#include <QMap>
#include <QCache>
#include <QSet>
#include <QQueue>
#include <QPair>

class ImageManager : public QObject
{
    Q_OBJECT
public:
    typedef struct{
        QImage *image;
        int row;
        QString imageName;
    }ImageResult;

    explicit ImageManager(QObject *parent = 0);

    QImage *getImage(int row, const QString &imageName);
    int getImageDownloadProgress(const QString &imageName);

private:
    QCache<QString, QImage> m_imageCache;
    void asyncGetImage(int row, const QString &imageName);
    QMap<QString, int> m_imageDownloadProgressMap;
    QQueue<QPair<int, QString>*> m_asyncGetImageQueue;

    void pushAsyncGetImageTask(int row, const QString &imageName);
    void pullAsyncGetImageTask();

signals:
    void imageLoaded(int row, const QString &imageName);
    void imageDownloadProgressChanged(int row, const QString &imageName);

public slots:
    void doAsyncGetImageFinished();
    void doDownloadImageProgress(int row, const QString &imageName, int percent);
};

#endif // IMAGEMANAGER_H

imagemanager.cpp

#include "imagemanager.h"
#include <QFutureWatcher>
#include <QFuture>
#include <QFile>
#include <QApplication>
#include <QDir>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTimer>
#include <QDebug>
#include <QtConcurrent>

#include "downloadimagewrapper.h"

/**************異步加載圖片到緩存的步驟**********************************************************
 * (1)先到文件系統中查找是否有緩存的圖片,如果存在則讀取該圖片,創建並返回一個QImage對象
 * (2)如果文件系統中沒有緩存的圖片,則下載雲端的圖片,下載成功後保存到文件系統中,創建並返回一個QImage對象
 * (3)如果文件系統和雲端都沒有找到該圖片,則返回空指針
 ****************************************************************************************/
ImageManager::ImageResult* asyncGetImageFromFileSystemAndNetwork(int row, const QString &imageName, ImageManager *imageManager)
{
    ImageManager::ImageResult *imageResult=new ImageManager::ImageResult;
    imageResult->image=0;
    imageResult->row=row;
    imageResult->imageName=imageName;

    QString fileSystemCachePath=QString("%1/%2").arg(QDir::tempPath()).arg(imageName);

    QImage *image=new QImage(fileSystemCachePath);
    if(!image->isNull())
    {
        imageResult->image=image;
        qDebug()<<"Load from file system cache : "<<imageName;
        return imageResult;
    }

    QEventLoop eventLoop;
    DownloadImageWrapper wrapper(row, imageName);
    QObject::connect(&wrapper, SIGNAL(finished()), &eventLoop, SLOT(quit()));
    QObject::connect(&wrapper, SIGNAL(imageDownloadProgress(int,QString,int)), imageManager, SLOT(doDownloadImageProgress(int,QString,int)));
    wrapper.startDownload();
    eventLoop.exec();

    if(!wrapper.hasError())
    {
        QImage *image=new QImage;
        image->loadFromData(wrapper.imageData(), "jpg");
        if(!image->isNull())
        {
            image->save(fileSystemCachePath);
            imageResult->image=image;
            qDebug()<<"Load from network : "<<imageName;
            return imageResult;
        }
        delete image;
    }

    if(imageResult->image==0)
    {
        qDebug()<<"Load from file system and network failed : "<<imageName;
    }

    return imageResult;
}

ImageManager::ImageManager(QObject *parent) : QObject(parent)
{
    m_imageCache.setMaxCost(10);  //設置最大緩存對象數量爲10,便於測試
}

QImage *ImageManager::getImage(int row, const QString &imageName)
{
    QImage *image=m_imageCache.object(imageName);
    if(image==0)
    {
        if(!m_imageDownloadProgressMap.contains(imageName))
        {
            pushAsyncGetImageTask(row, imageName);
        }
    }
    return image;
}

int ImageManager::getImageDownloadProgress(const QString &imageName)
{
    return m_imageDownloadProgressMap.value(imageName, 0);
}

/***************************
 * 異步讀取文件系統或雲端的圖片
 **************************/
void ImageManager::asyncGetImage(int row, const QString &imageName)
{
    m_imageDownloadProgressMap.insert(imageName, 0);
    QFutureWatcher<ImageResult*> *watcher=new QFutureWatcher<ImageResult*>;
    connect(watcher, SIGNAL(finished()), this, SLOT(doAsyncGetImageFinished()));
    QFuture<ImageResult*> future=QtConcurrent::run(asyncGetImageFromFileSystemAndNetwork, row, imageName, this);
    watcher->setFuture(future);
}

void ImageManager::pushAsyncGetImageTask(int row, const QString &imageName)
{
    while(m_asyncGetImageQueue.size()>=QThreadPool::globalInstance()->maxThreadCount())
    {
        QPair<int, QString> *pair=m_asyncGetImageQueue.dequeue();
        delete pair;
    }
    QPair<int, QString> *pair=new QPair<int, QString>;
    pair->first=row;
    pair->second=imageName;
    m_asyncGetImageQueue.enqueue(pair);
    pullAsyncGetImageTask();
}

void ImageManager::pullAsyncGetImageTask()
{
    if(m_asyncGetImageQueue.isEmpty())
    {
        return;
    }
    if(m_imageDownloadProgressMap.size()>=QThreadPool::globalInstance()->maxThreadCount())
    {
        return;
    }
    QPair<int, QString> *pair=m_asyncGetImageQueue.dequeue();
    asyncGetImage(pair->first, pair->second);
    delete pair;
}

void ImageManager::doAsyncGetImageFinished()
{
    QFutureWatcher<ImageResult*> *watcher=static_cast<QFutureWatcher<ImageResult*>*>(sender());
    if(watcher!=NULL)
    {
        ImageResult *imageResult=watcher->result();
        m_imageDownloadProgressMap.remove(imageResult->imageName);
        if(imageResult->image!=0)
        {
            m_imageCache.insert(imageResult->imageName, imageResult->image);
            emit imageLoaded(imageResult->row, imageResult->imageName);
        }
        watcher->deleteLater();
        delete imageResult;
        imageResult=0;
    }
    pullAsyncGetImageTask();
}

void ImageManager::doDownloadImageProgress(int row, const QString &imageName, int percent)
{
    m_imageDownloadProgressMap.insert(imageName, percent);
    emit imageDownloadProgressChanged(row, imageName);
}

imagelistviewmodel.h

#ifndef IMAGELISTVIEWMODEL_H
#define IMAGELISTVIEWMODEL_H

#include <QObject>
#include <QAbstractListModel>
#include <QList>

class ImageListViewModel : public QAbstractListModel
{
    Q_OBJECT
public:
    ImageListViewModel(QObject *parent=0);
    ~ImageListViewModel();

    int rowCount(const QModelIndex &parent) const;

    int columnCount(const QModelIndex &parent) const;

    QVariant data(const QModelIndex &index, int role) const;

private:
    QList<QString> m_dataList;

public slots:
    void imageLoaded(int row, const QString &imageName);
    void imageDownloadProgressChanged(int row, const QString &imageName);
};

#endif // IMAGELISTVIEWMODEL_H

imagelistview.cpp

#include "imagelistviewmodel.h"
#include <QSize>
#include <QDebug>

ImageListViewModel::ImageListViewModel(QObject *parent) : QAbstractListModel(parent)
{
    for(int i=1; i<=28; i++)
    {
        m_dataList.push_back(QString("%1.jpg").arg(i));
    }
}

ImageListViewModel::~ImageListViewModel()
{
}

int ImageListViewModel::rowCount(const QModelIndex &parent) const
{
    return m_dataList.size();
}

int ImageListViewModel::columnCount(const QModelIndex &parent) const
{
    return 1;
}

QVariant ImageListViewModel::data(const QModelIndex &index, int role) const
{
    switch (role) {
        case Qt::SizeHintRole:
        {
            return QSize(600, 400);
            break;
        }
        case Qt::UserRole:
        {
            return m_dataList.at(index.row());
            break;
        }
    }
    return QVariant();
}

void ImageListViewModel::imageLoaded(int row, const QString &imageName)
{
    emit dataChanged(this->index(row), this->index(row));  //通知QListView重繪指定的row
}

void ImageListViewModel::imageDownloadProgressChanged(int row, const QString &imageName)
{
    emit dataChanged(this->index(row), this->index(row));  //通知QListView重繪指定的row
}

imagelistviewitemdelegate.h

#ifndef IMAGELISTVIEWITEMDELEGATE_H
#define IMAGELISTVIEWITEMDELEGATE_H

#include <QObject>
#include <QStyledItemDelegate>

class ImageManager;

class ImageListViewItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit ImageListViewItemDelegate(ImageManager *imageManager, QObject *parent = 0);
    ~ImageListViewItemDelegate();

protected:
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;

private:
    ImageManager *m_imageManager;

signals:

public slots:
};

#endif // IMAGELISTVIEWITEMDELEGATE_H

imagelistviewitemdelegate.cpp

#include "imagelistviewitemdelegate.h"
#include <QPainter>
#include <QFontMetrics>
#include <QDebug>
#include "imagemanager.h"

ImageListViewItemDelegate::ImageListViewItemDelegate(ImageManager *imageManager, QObject *parent) : QStyledItemDelegate(parent)
{
    this->m_imageManager=imageManager;
}

ImageListViewItemDelegate::~ImageListViewItemDelegate()
{

}

void ImageListViewItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyledItemDelegate::paint(painter, option, index);

    QImage *image=m_imageManager->getImage(index.row(), index.data(Qt::UserRole).toString());
    if(image==0)  //如果內存緩存中不存在要顯示的圖片,則繪製“加載中...”文字信息
    {
        QPen pen=painter->pen();
        pen.setColor(QColor(200, 200, 200));
        painter->setPen(pen);
        QFont font=painter->font();
        font.setPointSize(25);
        painter->setFont(font);
        QString txt=QString("圖片加載中···(%1%)").arg(m_imageManager->getImageDownloadProgress(index.data(Qt::UserRole).toString()));
        QFontMetrics fm(font);
        QRect rect;
        rect.setWidth(fm.width(txt));
        rect.setHeight(fm.capHeight());
        painter->fillRect(option.rect, QColor(240, 240, 240));
        painter->drawText(option.rect.left()+(option.rect.width()-rect.width())/2, option.rect.top()+(option.rect.height()-rect.height())/2+rect.height(), txt);
    }
    else   //如果內存中存在要顯示的圖片,則繪製該圖片
    {
        painter->drawImage(option.rect, *image);
    }
}

downloadimagewrapper.h

#ifndef DOWNLOADIMAGEWRAPPER_H
#define DOWNLOADIMAGEWRAPPER_H

#include <QObject>
#include <QTimer>
#include <QNetworkAccessManager>

class QNetworkReply;

class DownloadImageWrapper : public QObject
{
    Q_OBJECT
public:
    explicit DownloadImageWrapper(int row, const QString &imageName, QObject *parent = 0);
    ~DownloadImageWrapper();

    void startDownload();
    QByteArray imageData();
    bool hasError();

private:
    int m_row;
    QString m_imageName;
    QNetworkReply *m_reply;
    QNetworkAccessManager m_manager;
    QTimer m_timer;

signals:
    void finished();
    void imageDownloadProgress(int row, const QString &imageName, int percent);

public slots:
    void doDownloadProgress(qint64, qint64);
};

#endif // DOWNLOADIMAGEWRAPPER_H

downloadimagewrapper.cpp

#include "downloadimagewrapper.h"
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>

DownloadImageWrapper::DownloadImageWrapper(int row, const QString &imageName, QObject *parent) : QObject(parent)
{
    this->m_reply=0;
    this->m_row=row;
    this->m_imageName=imageName;
    this->m_timer.setInterval(20*1000); //超時時間20秒
    this->m_timer.setSingleShot(true);
}

DownloadImageWrapper::~DownloadImageWrapper()
{
    if(this->m_reply!=0)
    {
        delete this->m_reply;
        this->m_reply=0;
    }
    m_timer.stop();
}

void DownloadImageWrapper::startDownload()
{
    QNetworkRequest request;
    request.setUrl(QString("http://118.24.201.167:8080/test/image/%1").arg(m_imageName));
    m_reply=m_manager.get(request);
    connect(m_reply, SIGNAL(finished()), this, SIGNAL(finished()));
    connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(doDownloadProgress(qint64, qint64)));
    connect(&m_timer, SIGNAL(timeout()), this, SIGNAL(finished()));
    m_timer.start();
}

QByteArray DownloadImageWrapper::imageData()
{
    return m_reply->readAll();
}

bool DownloadImageWrapper::hasError()
{
    return m_reply->error()!=QNetworkReply::NoError;
}

void DownloadImageWrapper::doDownloadProgress(qint64 received, qint64 total)
{
    if(total==0)
    {
        return;
    }
    int percent=(int)(100*(received/(total*1.0)));
    emit imageDownloadProgress(m_row, m_imageName, percent);
}

main.cpp

#include "mainwidget.h"
#include <QApplication>
#include <QThreadPool>
#include <QDir>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    /***** 設置線程池最大線程數爲4 ******/
    QThreadPool::globalInstance()->setMaxThreadCount(4);

    /***** 清除緩存文件,便於測試 ******/
    QDir dir=QDir::temp();
    dir.setFilter(QDir::Files);
    QFileInfoList list = dir.entryInfoList();
    for (int i = 0; i < list.size(); ++i) {
        QFileInfo fileInfo = list.at(i);
        QFile::remove(fileInfo.absoluteFilePath());
    }

    MainWidget w;
    w.show();

    return a.exec();
}

(-----------------完-------------------)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章