Windows Phone App基于文件系统的图片缓存方案

最近在做一个Windows Phone 8.1的应用,因为应用里面使用了很多图片,所以打算将图片文件缓存到文件系统里面,以便节省流量和提升体验。

一般显示图片的做法大多只需要将对应的Uri地址绑定到对应控件的ImageSource属性上即可,或者将Uri传入BitmapImage对象,其会自动将资源异步下载好然后加载。

为了不将缓存的逻辑侵入实体模型层,所以打算在Uri->BitmapImage绑定上做文章,也就是利用IValueConverter接口构造值转换器。

这里有一个问题就是,因为绑定的原因,IValueConverter接口只能是同步的方法,而图片的下载的过程是一个异步的过程,所以我们需要一种解决方案实现一个异步的ValueConverter。

这个解决方案我思考了很久,最后在这里受到了启发:http://stackoverflow.com/questions/15003827/async-implementation-of-ivalueconverter

具体而言,就是在同步的ValueConverter中构造一个Task-like的对象,然后将对应的属性绑定到这个Task-like对象的一个属性中去,然后Task-like待Task完成后更新对应的状态。

这个解决方案实现很巧妙,同时也没有对Model和ViewModel做任何的侵入,下面是我修改后用于图片缓存的相关代码:

首先是界面绑定所做的改动,大概如下,就是通过构造一个新的DataContext来实现绑定:

<Image DataContext="{Binding Image, Converter={StaticResource ImageConverter}}"
             Stretch="UniformToFill" Source="{Binding Result}" />

对应的ValueConverter类:

public class CacheImageValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            var image = value as Image;
            if (image == null || string.IsNullOrEmpty(image.Url))
            {
                var bitmap = new BitmapImage(new Uri("ms-appx:///assets/default.png"));
                var notifier = new TaskCompletionNotifier<BitmapImage>();
                notifier.SetTask(Task.FromResult(bitmap));
                return notifier;
            }
            else
            {
                var task = Task.Run(async () =>
                {
                    var cache = HiwedoContainer.Current.Resolve<ImageCache>();
                    var uri = await cache.GetImageSourceFromUrlAsync(image.Url);
                    return uri;
                });
                var notifier = new TaskCompletionNotifier<BitmapImage>();
                notifier.SetTask(task, c => new BitmapImage(c));
                return notifier;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

其次是Task-like的类。

public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged
    {
        private TResult _result;
        private IAsyncResult _task;

        public TaskCompletionNotifier()
        {
            this._result = default(TResult);
        }

        public void SetTask<T>(Task<T> task, Func<T, TResult> factoryFunc)
        {
            this._task = task;
            if (!task.IsCompleted)
            {
                var scheduler = (SynchronizationContext.Current == null)
                    ? TaskScheduler.Current
                    : TaskScheduler.FromCurrentSynchronizationContext();
                task.ContinueWith(t =>
                {
                    var propertyChanged = PropertyChanged;
                    if (propertyChanged != null)
                    {
                        this.OnPropertyChanged("IsCompleted");
                        if (t.IsFaulted)
                        {
                            InnerException = t.Exception;
                            this.OnPropertyChanged("ErrorMessage");
                        }
                        else
                        {
                            try
                            {
                                this._result = factoryFunc(task.Result);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine("Factory error: " + ex.Message);
                                this.InnerException = ex;
                                this.OnPropertyChanged("ErrorMessage");
                            }
                            this.OnPropertyChanged("Result");
                        }
                    }
                },
                    CancellationToken.None,
                    TaskContinuationOptions.ExecuteSynchronously,
                    scheduler);
            }
            else
            {
                this._result = factoryFunc(task.Result);
            }
        }

        public void SetTask(Task<TResult> task)
        {
            this._task = task;
            if (!task.IsCompleted)
            {
                var scheduler = (SynchronizationContext.Current == null)
                    ? TaskScheduler.Current
                    : TaskScheduler.FromCurrentSynchronizationContext();
                task.ContinueWith(t =>
                {
                    var propertyChanged = PropertyChanged;
                    if (propertyChanged != null)
                    {
                        this.OnPropertyChanged("IsCompleted");
                        if (t.IsFaulted)
                        {
                            InnerException = t.Exception;
                            this.OnPropertyChanged("ErrorMessage");
                        }
                        else
                        {
                            this._result = task.Result;
                            this.OnPropertyChanged("Result");
                        }
                    }
                },
                    CancellationToken.None,
                    TaskContinuationOptions.ExecuteSynchronously,
                    scheduler);
            }
            else
            {
                this._result = task.Result;
            }
        }

        public TResult Result
        {
            get { return this._result; }
        }

        public bool IsCompleted
        {
            get { return _task.IsCompleted; }
        }

        public Exception InnerException { get; set; }

        public string ErrorMessage
        {
            get { return (InnerException == null) ? null : InnerException.Message; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

提供一个Task和Factory的函数重载是必须的,因为对于像BitmapImage这样的类,其初始化需要在对应的UI线程中,不能直接将BitmapImage作为结果返回,所以在下面也可以看到,实际上Task返回的只是Uri,然后构造BitmapImage是在ContinueWith的方法中完成的。

最后就是用于图片缓存的类,这个类比较简单,就是如果判断如果图片已经存在,就直接从文件系统返回,如果不存在就先下载再返回对应的Uri。然后会有一个变量缓存所有已经缓存的文件名。

public class ImageCache
    {
        private const string ImageCacheFolder = "ImageCaches";
        private StorageFolder _cacheFolder;
        private IList<string> _cachedFileNames;

        public async Task<Uri> GetImageSourceFromUrlAsync(string url)
        {
            string fileName = url.Substring(url.LastIndexOf(‘/‘) + 1);
            if (this._cachedFileNames.Contains(fileName))
            {
                return new Uri("ms-appdata:///local/ImageCaches/" + fileName);
            }
            if (await DownloadAndSaveAsync(url, fileName))
            {
                _cachedFileNames.Add(fileName);
                return new Uri("ms-appdata:///local/ImageCaches/" + fileName);
            }
            Debug.WriteLine("Download image failed. " + url);
            return new Uri(url);
        }

        private async Task<bool> DownloadAndSaveAsync(string url, string filename)
        {
            try
            {
                var request = WebRequest.CreateHttp(url);
                request.Method = "GET";
                using (var response = await request.GetResponseAsync())
                {
                    using (var responseStream = response.GetResponseStream())
                    {
                        var file =
                            await this._cacheFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
                        using (var fs = await file.OpenStreamForWriteAsync())
                        {
                            await responseStream.CopyToAsync(fs);
                            Debug.WriteLine("Downloaded: " + url);
                            return true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error: " + ex.Message);
                return false;
            }
        }

        public async Task LoadCache()
        {
            var folders = await ApplicationData.Current.LocalFolder.GetFoldersAsync();
            foreach (var folder in folders)
            {
                if (folder.Name == ImageCacheFolder)
                {
                    this._cacheFolder = folder;
                    break;
                }
            }
            if (this._cacheFolder == null)
            {
                this._cacheFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(ImageCacheFolder);
            }
            this._cachedFileNames = (await this._cacheFolder.GetFilesAsync()).Select(c => c.Name).ToList();
        }

        public IList<string> CachedFileNames
        {
            get { return _cachedFileNames; }
            set { _cachedFileNames = value; }
        }
    }
时间: 2024-07-28 20:45:02

Windows Phone App基于文件系统的图片缓存方案的相关文章

移动应用开发(IOS/android等)中一个通用的图片缓存方案讲解(附流程图)

在移动应用开发中,我们经常会遇到从网络请求图片到设备上展示的场景. 如果每次都重复发起请求,浪费流量.浪费电量,用户体验也不佳: 将图片持久化到磁盘也不失为一种策略:但每次从文件读取图片也存在一定的io开销,就算采用此策略,我们也需要控制磁盘缓存的容量,以免占用过多系统资源. 其实没有一个方案可以说是完美的方案,只有最适合自己业务需求的方案,才可以说是一个好方案. 我们下面所讲解的方案具备很强的通用性,设计思路简单而清晰: 1.假设每个网络图片的url具有唯一性,如果网络上的图片变化了,会引起输

Android的图片缓存ImageCache(转)

为什么要做缓存?       在UI界面加载一张图片时很简单,然而如果需要加载多张较大的图像,事情就会变得更加复杂.在许多情况下(如ListView.GridView或ViewPager等的组件),屏幕上的图片的总数伴随屏幕的滚动会大大增加,且基本上是无限的. 为了使内存使用保持在稳定范围内,防止出现OOM,这些组件会在子view画出屏幕后,对其进行资源回收,并重新显示新出现的图片,垃圾回收机制会释放掉不再显示的图片的内存空间.但是这样频繁地处理图片的加载和回收不利于操作的流畅性,而内存或者磁盘

android之图片缓存的探究

在用户界面(UI)加载一张图片时很简单,然而,如果你需要加载多张较大的图像,事情就会变得更加复杂,.在许多情况下(如与像的ListView GridView或ViewPager的组件),屏幕上的图片的总数伴随屏幕上滚动的骤然增加,且基本上是无限的.为使内存使用保持在稳定范围内,这些组件会在子view在屏幕中消失后,对其进行资源回收,垃圾回收机制会释放掉已加载的图片内存空间,所以建议你不要保持图片的常引用,这样做很好,为了保证页面的流畅性和响应速度,你可能不愿意在页面返回时频繁处理加载过图片.通过

windows store app 读写图片

1 using System; 2 using System.Threading.Tasks; 3 using System.Runtime.InteropServices.WindowsRuntime; 4 using Windows.Graphics.Imaging; 5 using Windows.UI.Xaml.Media.Imaging; 6 using Windows.Storage; 7 using Windows.Storage.Pickers; 8 using Windows.

【转载】基于AFNetWorking3.0的图片缓存分析

原文出处: Yasin的简书 理论 不喜欢理论的可以直接跳到下面的Demo实践部分 缓存介绍 缓存按照保存位置可以分为两类:内存缓存.硬盘缓存(FMDB.CoreData…).我们常说的网络请求缓存包含内存缓存.硬盘缓存和URL缓存. 图片缓存思路 图片缓存流程图.png URL缓存 网络请求除了客户端需要做简单的配置外,最主要需要服务器支持,服务端也很简单,只需要在response里面设置Cache-Control字段就行了. 最常见的URL缓存实现方式:NSURLCache.NSURLCac

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============

Android图片缓存分析(一)

Android中写应用时,经常会遇到加载图片的事,由于很多图片是网络上下载获取的,当我们进页面时,便会去网络下载图片,一两次可能没啥问题,但如果同一张图片每次都去网络拉取,不仅速度慢,更影响用户体验,同时会浪费用户的流量. 基于此,很多人便想到了图片缓存的方法. 现在比较普遍的图片缓存主要有以下几个步骤: 一.从缓存中获取图片 二.如果缓存中未获取图片,则从存储卡中获取 三.如果存储卡中未获取图片,则从网络中获取 一.从缓存中获取图片 我们知道,Android中分配给每个应用的内存空间是有限的,

android使用ImageLoader实现图片缓存(安卓开发必备)

相信大家在学习以及实际开发中基本都会与网络数据打交道,而这其中一个非常影响用户体验的就是图片的缓存了,若是没有弄好图片缓存,用户体验会大大下降,总会出现卡顿情况,而这个问题尤其容易出现在ListView中的Item有图片的情况中. 前面与大家分享了一个网络连接框架Retrofit,里面也有类似的图片加载的picasso,大家都可以去体验,直通车:http://www.cnblogs.com/liushilin/p/5680135.html 当然还有当前我认为最好用的图片缓存加载框架Fresco,

iOS图片缓存库基准对比

原文:iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache),译者夜微眠(github地址),校对蓝魂(博客).Cocoa(博客).1.引言 过 去的几年里,iOS应用在视觉方面越来越吸引人.图像展示是其中很关键的部分,因为大部分图像展示都需要下载并且渲染.大部分开发者都要使用图像填充表格 视图(table views) 或者 集合视图(collection views) .下载图片消耗一些资源(如蜂窝数据.电池