• 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

    Update: 顺藤摸瓜找到了这个问题的答主的一个开源库,主要是对async和await的拓展,推荐:https://github.com/StephenCleary/AsyncEx

    具体而言,就是在同步的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; }
            }
        }
  • 相关阅读:
    迭代器、生成器、装饰器(转)
    Python小数据池
    接阿里云oss有感
    VSCode快捷键
    前端跨域调请求 nginx反向代理
    Git生成密钥
    【westorm系列之二】配置格式化
    钉钉安卓端无法渲染数据
    express 写接口
    js正则匹配身份证号 有坑
  • 原文地址:https://www.cnblogs.com/harrywong/p/4230443.html
Copyright © 2020-2023  润新知