一、应用场景
在淘宝应用首页,会有很多张图片,而这些首页图片不会经常改变,所以就需要缓存下来。这样就不必每次都从网络获取。
二、比较对象
1.系统缓存
对于系统缓存,我们不需要做什么处理。只需要把网络图片的URL赋值给Image控件就行了。这样系统就会在每次需要用到图片的时候,有限查找缓存里有没有之前下载好的。
2.自建缓存区域
自建缓存不给Image控件赋URL,而是把图片DownLoad下来,生成一个bitmap,然后把bitmap赋值给Image。同时将这个bitmap存储下来。当下次要用到这幅图的时候,就直接从存储的位置找到这幅图。
三、自建缓存方法
下边这段代码将uri[]数组中的图片下载下来,然后通过WriteToFile()函数将图片保存到本地,同时,记下存储的文件名。
SoftwareBitmap sb = await DownloadImage(uri[i]); if (sb != null) { //sb = await ReadFromFile(fileName[i]); SoftwareBitmapSource source = new SoftwareBitmapSource(); await source.SetBitmapAsync(sb); this.insideImage.Source = source; sb = await DownloadImage(uri[i]); fileName[i] = await WriteToFile(sb); }
当你需要使用图片的时候,使用下列代码,通过ReadFromFile()函数将图片读取出来就行了。
for (int i = 0; i < 50; i++) {
//SoftwareBitmap sb = await DownloadImage(uri[i]); SoftwareBitmap sb = await ReadFromFile(fileName[i]); SoftwareBitmapSource source = new SoftwareBitmapSource();
await source.SetBitmapAsync(sb); this.insideImage.Source = source;
//source.Dispose(); }
四、效率对比
下边我通过对比两种缓存机制发现各有用武之地。
1.对于几百K到几兆的大图片,系统缓存有速度优势。
2.对于几K到几十K的小图片,自建缓存区有速度优势。
测试背景1:三张大图片,循环33次(共99次)
图片大小:338k 618k 1810k
PC测试 |
|||||
系统缓存(CPU周期) |
3066584 |
3058505 |
3079367 |
3078989 |
3076976 |
自建缓存(CPU周期) |
53669280 |
51842991 |
52839051 |
52078772 |
52305373 |
Phone测试 |
|||||
系统缓存(CPU周期) |
31852799 |
32008575 |
32200748 |
31970601 |
31839003 |
自建缓存(CPU周期) |
741909215 |
750950455 |
765863510 |
760865505 |
781048686 |
结论一:对于几百K到几兆的大图片,系统缓存有速度优势。
测试背景2:三张小图片,循环33次(共99次)
图片大小:3k 6k 60k
PC测试
系统缓存(CPU周期) |
3057284 |
3057637 |
3080880 |
3063350 |
3059105 |
自建缓存(CPU周期) |
1316247 |
1318369 |
1364584 |
1333684 |
1362956 |
Phone测试
系统缓存(CPU周期) |
32085084 |
31751734 |
31744715 |
31852230 |
32064768 |
自建缓存(CPU周期) |
27114317 |
26041012 |
26821794 |
27365796 |
30211258 |
结论二:对于几K到几十K的小图片,自建缓存区有速度优势。
手机淘宝项目测试数据:
测试背景:50张小图片,循环一次(共50次)
系统缓存 |
CPU周期 |
23689650 |
21589548 |
25409150 |
25186302 |
23121251 |
RAM |
51 |
52 |
50 |
52 |
52 |
|
自建缓存 |
CPU周期 |
3186761 |
2892837 |
2963193 |
2942235 |
2741501 |
RAM |
61 |
63 |
61 |
60 |
59 |
PS:RAM占用是峰值,稳定后两种方式RAM占用相同。
五、测试方法
通过给一个Image控件赋值,来看到效果。
1、系统缓存
系统缓存测试不能通过直接改变url的方式,因为系统缓存是异步的,他不会等一个图片加载好再加载另一个图,而是直接忽略了之前的改变。
private async void test1() { stopwatch.Reset(); stopwatch.Start(); BitmapImage bi = new BitmapImage(); bi.UriSource = new Uri(uri[0]); this.insideImage.Source = bi; } private void insideImage_ImageOpened(object sender, RoutedEventArgs e) { times++; if (times == 50) { stopwatch.Stop(); textBox.Text = "任务"+testnum.ToString()+"用时:" + stopwatch.ElapsedTicks + "."; return; } BitmapImage bi = new BitmapImage(); bi.UriSource = new Uri(uri[times]); this.insideImage.Source = bi; }
2、自建缓存
private async void test2() { stopwatch.Reset(); stopwatch.Start(); for (int i = 0; i < 50; i++) { //SoftwareBitmap sb = await DownloadImage(uri[i]); SoftwareBitmap sb = await ReadFromFile(fileName[i]); SoftwareBitmapSource source = new SoftwareBitmapSource(); await source.SetBitmapAsync(sb); if (i % 3 == 0) { this.insideImage.Source = source; }
else if (i % 3 == 1) { this.insideImage1.Source = source; }
else if (i % 3 == 2) { this.insideImage2.Source = source; } //source.Dispose();
} stopwatch.Stop(); textBox.Text = "任务" + testnum.ToString() + "用时:" + stopwatch.ElapsedTicks + "."; }
附:关键代码代码
ReadFromFile()函数通过文件名读取图片 ,特别注意这句话
SoftwareBitmapsoftwareBitmap = awaitdecoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
一定要加上编码方式,不然会报错。
public async Task<SoftwareBitmap> ReadFromFile(string filename) { StorageFile file = await _localFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists); //var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri( filename)); using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read)) { // Create the decoder from the stream BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); // Get the SoftwareBitmap representation of the file SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); return softwareBitmap; } }
WriteToFile()函数将bitmap写入存储区
public async Task<string> WriteToFile(SoftwareBitmap softwareBitmap) { string fileName = Path.GetRandomFileName(); if (softwareBitmap != null) { // save image file to cache StorageFile file = await _localFolder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists); using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite)) { BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream); encoder.SetSoftwareBitmap(softwareBitmap); await encoder.FlushAsync(); } } return fileName; }
DownloadImage()函数通过url下载图片,返回bitmap
private async Task<SoftwareBitmap> DownloadImage(string url) { try { HttpClient hc = new HttpClient(); HttpResponseMessage resp = await hc.GetAsync(new Uri(url)); resp.EnsureSuccessStatusCode(); IInputStream inputStream = await resp.Content.ReadAsInputStreamAsync(); IRandomAccessStream memStream = new InMemoryRandomAccessStream(); await RandomAccessStream.CopyAsync(inputStream, memStream); BitmapDecoder decoder = await BitmapDecoder.CreateAsync(memStream); SoftwareBitmap softBmp = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); return softBmp; } catch (Exception ex) { return null; } }