接上篇: 图片后台下载+缓存+占位符效果
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