WP开发笔记-ImageCacheService

原创
2013/07/28 17:51
阅读数 245

接上篇: 图片后台下载+缓存+占位符效果

ImageCahceService对外只公开SetImageStream()方法:

public static void SetImageStream(string imageUrl, Action<byte[]> callBack)
说明:imageUrl为要下载图片的URL,callBack是当图片下载完成之后的回调。因为下载图片不能阻塞UI线程,所以需要使用回调的方式。

具体实现

public static void SetImageStream(string imageUrl, Action<byte[]> callBack)
        {
            #region if cached
            if (imageCacheDic.ContainsKey(imageUrl)
                && imageCacheDic[imageUrl].Data != null)
            {
                Debug.WriteLine("CACHED. " + imageUrl);
                callBack((byte[])imageCacheDic[imageUrl].Data);
                return;
            }
            #endregion
            #region init
            if (!isInit)
            {
                Init();
            }
            #endregion
            #region add to pending work list
            var pendingWork = new PendingWork
            {
                ImageUrl = imageUrl,
                CallBack = callBack
            };
            AddWork(pendingWork);
            #endregion
        }
 说明:先判断是否有缓存(imageCacheDic是一个字典属性)。如果没有缓存,需要加入到pending work列表:  
private static void AddWork(PendingWork pendingwork)
        {
            lock (pendingWorkLocker)
            {
                #region remove same task
                var sameWorks = pendingWorkList.Where(w => w.ImageUrl == pendingwork.ImageUrl).ToList();
                Debug.WriteLine("remove {0} same works.", sameWorks.Count);
                foreach (var same in sameWorks)
                {
                    pendingwork.CallBack += same.CallBack;
                    pendingWorkList.Remove(same);
                }
                #endregion
                Debug.WriteLine("add new work. url: {0}", pendingwork.ImageUrl);
                pendingWorkList.Add(pendingwork);
            }
            autoResetEvent.Set();
        }

说明:PendingWork是一个封装了callback和byte数组的类。

先判断列表里是否已经有这个新加入的URL了。如果有,去掉重复的内容。

然后释放资源信号(autoResetEvent.Set方法)。

Init方法做一些静态属性的初始化工作。

private static void Init()
        {
            for (int i = 0; i < MAX_WORKER_COUNT; i++)
            {
                var worker = new BackgroundWorker
                {
                    WorkerSupportsCancellation = true,
                };
                worker.RunWorkerCompleted += DownloadImageComplete;
                workers.Add(worker);
            }
            doWorker.DoWork += DoWork;
            doWorker.RunWorkerAsync();
            isInit = true;
        }
worker和workers是BackgroundWorker线程类。用于下载图片。

当worker收到信号量时,开始下载图片:

private static void DoWork(object argument, DoWorkEventArgs e)
        {
            while (!isStop)
            {
                if (pendingWorkList.Count == 0)
                {
                    Debug.WriteLine("wait for pending work...");
                    autoResetEvent.WaitOne();
                    Debug.WriteLine("start to wok. number is: {0}", pendingWorkList.Count);
                    continue;
                }
                lock (pendingWorkLocker)
                {
                    for (int i = 0; i < workers.Count; i++)
                    {
                        if (pendingWorkList.Count == 0)
                            break;
                        var worker = workers[i];
                        if (!worker.IsBusy)
                        {
                            worker.DoWork += DownloadImage;
                            var pendingwork = pendingWorkList[0];
                            pendingWorkList.RemoveAt(0);
                            Debug.WriteLine("worker {0} starts to work. url: {1}", i, pendingwork.ImageUrl);
                            worker.RunWorkerAsync(pendingwork);
                        }
                    }
                }
            }
        }
 说明:先判断是否有需要下载的内容。然后遍历workers,让not busy的worker异步下载图片。
private static void DownloadImage(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(random.Next(500));
            if (e.Cancel)
                return;
            var pendingwork = e.Argument as PendingWork;
            pendingwork.Request = WebRequest.CreateHttp(pendingwork.ImageUrl + "?random=" + DateTime.Now.Ticks.ToString("x"));
            pendingwork.Request.BeginGetResponse(DownloadImage, pendingwork);
        }
说明:因为临界资源的问题,可能导致pending work list里存在相同的URL。所以,为URL加上unique标识。因为URL相同时,silverlight 不会创建新的web请求。
private static void DownloadImage(IAsyncResult result)
        {
            var pendingwork = (PendingWork)result.AsyncState;
            try
            {
                var response = pendingwork.Request.EndGetResponse(result);
                using (Stream stream = response.GetResponseStream())
                {
                    var memoryStream = new MemoryStream();
                    stream.CopyTo(memoryStream);
                    var data = memoryStream.ToArray();
                    AddImageCache(pendingwork.ImageUrl, data);
                }
                response.Close();
                pendingwork.CallBack((byte[])imageCacheDic[pendingwork.ImageUrl].Data);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
                pendingwork.Request.Abort();
                if (pendingwork.RetryCount <= MAX_WORKER_COUNT)
                {
                    pendingwork.RetryCount += 1;
                    AddWork(pendingwork);
                }
            }
        }
说明:使用webrequest下载。获取下载之后的stream,转换成byte数组,添加到cache里。然后执行回调。设置UI上的图片。

如果出现http的错误,可以重复下载多次。

private static void AddImageCache(string imageUrl, byte[] data)
        {
            lock (cacheLocker)
            {
                cache_index += 1;
                imageCacheDic[imageUrl] = new WeakData(cache_index, data);
                Debug.WriteLine("add cache. url: {0}", imageUrl);
                if (imageCacheDic.Count > MAX_CACHE_ITEM)
                {
                    var items = imageCacheDic.OrderBy(d => d.Value.Index).Take(20).ToList();
                    foreach (var item in items)
                        imageCacheDic.Remove(item.Key);
                }
            }
        }
说明:添加图片缓存。当缓存数超过限制时,删除一些。


一些说明

0. 为什么使用webrequest而不直接使用image的opened方法?

    因为opened的方法不靠谱。。即使为URL加上了unique的参数。有时候也不会触发opened事件。。导致图片根本没有下载。要不就是后台debug显示下载完了图片,然而UI上没有显示,仍然是默认占位图片。这个问题困扰了我很长时间。怀疑是UI操作过多导致的。

1. 为什么使用byte数组?

    因为每次从cache中读取stream时需要设置position。会导致而数组不用。可以直接使用。

2. cache为什么不用weak模式?

    因为,weak模式会直接把每次的缓存删掉。。不需要使用weakobject。

3. gif图片如何处理?

    下一篇讲解。

示例代码GitHub>> 

示例代码说明:注意,示例代码仅作为演示,没有包含gif和bmp图片的处理。如果需要了解详情,可以看Chicken4WP的源码。

参考链接:

0. lazylistbox

1. LowProfileImageLoader

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部