1

在使用单元格显示图像(从 HD 加载)的 UICollectionView 时,我遇到了一些性能问题。

我通过在后台加载图像解决了这个问题。

基本上,在我的“GetCell”方法中,我检查图像是否在我的 ImageCache 中。

  • 如果是这样,请在单元格中的 ImageView 上设置图像。
  • 如果没有,则在后台加载图像并请求重新加载该特定项目(我请求重新加载,因为我不知道同时单元格是否被回收,因此直接设置图像不安全)

我的后台进程片段:

ThreadPool.QueueUserWorkItem (delegate { 
                    ImagesCache.AddImageToCache(""+indexPath.Row,ImageForPosition(indexPath.Row));
                    InvokeOnMainThread (delegate { 
                        collectionView.ReloadItems(Converter.ToIndexPathArray(indexPath));
                    });
                }); 

它工作正常,但如果你快速滚动,它会加载所有这些异步任务,问题是它会按顺序执行请求(FIFO)。因此,当您快速滚动时,不可见单元格的图像将在可见单元格的图像之前加载。

有谁知道我如何优化这个过程以获得更好的用户体验?因为如果我将其扩展到包括来自互联网的图像,问题会更糟(因为下载)。

增加同时线程的最大数量将允许后来添加的线程已经立即启动,但它会降低整体性能/下载速度,所以这也不是一个真正的解决方案。

谢谢,马特

4

1 回答 1

0

简而言之,我的项目的解决方案是:thread下载queue支持的图像。加上检查目标UI控制,它没有出列以供重用。

长版:

  • queue使用方法Start/实现Stop。调用时Start,启动后台线程,在繁忙循环中(while true { DoSomething(); })将尝试从队列中取出请求。如果没有出队,请睡一会儿。如果出队,执行它(下载图像)。Stop方法应该说线程退出循环:
public void Start()
{
    if (started) {
        return;
    }
    started = true;

    new Thread (new ThreadStart (() => {
        while (started) {
            var request = GetRequest();
            if (request != null) {
                request.State = RequestState.Executing;
                Download (request);
            } else {
                Thread.Sleep (QueueSleepTime);
            }
        }
    })).Start ();
}

public void Stop()
{
    started = false;
}
  • 然后,使用这样的逻辑创建一个私有方法queue来下载图像:检查文件缓存中的图像。如果文件可用,请阅读并返回它。如果没有,请下载它,将其保存到文件中,返回(调用Action<UIImage> onDownload)或错误(调用Action<Exception> onError)。queue在的忙循环中调用此方法。命名它Download
public Download(Request request)
{
    try {
        var image = GetImageFromCache(request.Url);
        if (image == null) {
            image = DownloadImageFromServer(request.Url); // Could be synchronous
        }
        request.OnDownload(image);
    } catch (Exception e) {
        request.OnError(e);
    }
}
  • 然后,创建一个公共方法将请求添加到队列。模式Command对于包装队列请求很有用: storage Actions, current State。命名它DownloadImageAsync
public DownloadImageAsync(string imageUrl, Action<UIImage> onDownload, Action<Exception> onError)
{
    var request = MakeRequestCommand(imageUrl, onDownload, onError);
    queue.Enqueue(request);
}
  • UITableViewCell准备显示和请求下载图像的时间:
// Custom UITableViewCell method, which is called in `UITableViewSource`'s `GetCell`
public void PrepareToShow()
{
    var imageURLClosure = imageURL;
    queue.DownloadImageAsync(imageURL, (UIImage image) => {
        if (imageURLClosure == imageURL) {
            // Cell was not dequeued. URL from request and from cell are equals.
            imageView.Image = image;
        } else {
            // Do nothing. Cell was dequeued, imageURL was changed.
        }
    }, (Exception e) => {
        // Set default image. Log.
    });
}

检查 (imageURLClosure == imageURL) 对于避免在UIImageView快速滚动时将多个图像合二为一很重要。一个单元格可以初始化多个请求,但只能使用最后一个结果。

进一步的改进:

  • LIFO 执行。如果尚未运行任何请求,请添加新请求以开始。
  • 使用Action<byte[]> onDownload而不是Action<UIImage> onDownload实现跨平台代码兼容性;
  • download image当单元格变得不可见时取消请求的可用性( WillMoveToSuperview)。好吧,这不是很有必要。首次下载后,图像将在缓存中,因此任何进一步的图像请求都将快速完成。感谢缓存;
  • 内存缓存。因此,在最坏的情况下,链将是:

Find image in in-memory cache-> Find image in file cache-> Downloading from server

于 2013-07-04T08:10:05.747 回答