首页 > 代码库 > 【WP8】图片缓存控件

【WP8】图片缓存控件

在做图片相关的应用的时候,经常需要用大图片的缓存,默认的Image控件不支持缓存的支持,本文自定义一个支持图片缓存的控件

  当图片的地址是网络图片时候

    根据Url判断该图片是否存在本地,如果存在,则直接从本地读取,如果不存在,则通过Http请求下载该图片,保存到本地,然后读取到Image控件中

  当图片为本地地址的时候,直接从本地读取,设置到Image控件中

 

1、在定义可缓存图片控件之前,先封装一下文件存储的帮助类

using System;using System.IO;using System.IO.IsolatedStorage;using System.Text;using System.Threading.Tasks;using System.Windows;using Windows.ApplicationModel;using Windows.Storage;using Newtonsoft.Json;using XTuOne.Common.Helpers;namespace XTuOne.Utility.Helpers{    public class StorageHelper : IStorageHelper    {        #region 单例        public static IStorageHelper Instance { get; private set; }        public static object LockObject;        static StorageHelper()        {            Instance = new StorageHelper();            LockObject = new object();        }        private StorageHelper()        {        }        #endregion                #region 同步读写方法        public Stream ReadFile(string filePath)        {            lock (LockObject)            {                using (var sf = IsolatedStorageFile.GetUserStoreForApplication())                {                    if (!sf.FileExists(filePath))                    {                        throw new FileNotFoundException(string.Format("没有找到文件:{0}", filePath));                    }                    using (var fs = sf.OpenFile(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))                    {                        var stream = new MemoryStream();                        fs.CopyTo(stream);                        stream.Seek(0, SeekOrigin.Begin);                        return stream;                    }                }            }        }        public string CreateFile(Stream stream, string filePath, bool replace = false)        {            lock (LockObject)            {                using (var sf = IsolatedStorageFile.GetUserStoreForApplication())                {                    var directory = Path.GetDirectoryName(filePath);                    if (directory != null && !sf.DirectoryExists(directory))                    {                        //如果目录不存在,则创建                        sf.CreateDirectory(directory);                    }                    if (FileExist(filePath))                    {                        if (!replace)                        {                            return filePath;                        }                        sf.DeleteFile(filePath);                    }                    //如果不存在或者存在且替换                    using (var fs = sf.CreateFile(filePath))                    {                        stream.CopyTo(fs);                    }                }            }            return filePath;        }        public string CreateFile(byte[] data, string filePath, bool replace = false)        {            lock (LockObject)            {                using (var sf = IsolatedStorageFile.GetUserStoreForApplication())                {                    var directory = Path.GetDirectoryName(filePath);                    if (directory != null && !sf.DirectoryExists(directory))                    {                        //如果目录不存在,则创建                        sf.CreateDirectory(directory);                    }                    if (FileExist(filePath))                    {                        if (!replace)                        {                            return filePath;                        }                        sf.DeleteFile(filePath);                    }                    //如果不存在或者存在且替换                    using (var fs = new IsolatedStorageFileStream(filePath, FileMode.OpenOrCreate, sf))                    {                        fs.Write(data, 0, data.Length);                    }                }            }            return filePath;        }        public string ReadAllText(string fileName)        {            using (var reader = new StreamReader(ReadFile(fileName)))            {                return reader.ReadToEnd();            }        }        public string WriteAllText(string fileName, string text, bool replace)        {            return CreateFile(Encoding.UTF8.GetBytes(text), fileName, replace);        }        #endregion        #region 异步读写方法        public async Task<Stream> ReadFileAsync(string filePath)        {            var storageFile = await GetStorageFileAsync(filePath);            return await storageFile.OpenStreamForReadAsync();        }        public async Task<string> CreateFileAsync(Stream stream, string filePath, bool replace = false)        {            var storageFile = await GetStorageFileAsync(filePath);            if (storageFile != null)            {                if (FileExist(filePath))                {                    if (replace)                    {                        //替换先删除                        await storageFile.DeleteAsync(StorageDeleteOption.PermanentDelete);                    }                    else                    {                        return filePath;                    }                }                storageFile = await GetStorageFileAsync(filePath);                var destStream = await storageFile.OpenStreamForWriteAsync();                await stream.CopyToAsync(destStream);            }            return filePath;        }        public async Task<string> CreateFileAsync(byte[] data, string filePath, bool replace = false)        {            var storageFile = await GetStorageFileAsync(filePath);            if (storageFile != null)            {                if (FileExist(filePath))                {                    if (replace)                    {                        //替换先删除                        await storageFile.DeleteAsync(StorageDeleteOption.PermanentDelete);                    }                    else                    {                        return filePath;                    }                }                storageFile = await GetStorageFileAsync(filePath);                var destStream = await storageFile.OpenStreamForWriteAsync();                await destStream.WriteAsync(data, 0, data.Length);            }            return filePath;        }        public async Task<string> ReadAllTextAsync(string fileName)        {            using (var reader = new StreamReader(await ReadFileAsync(fileName)))            {                return await reader.ReadToEndAsync();            }        }        public async Task<string> WriteAllTextAsync(string fileName, string text, bool replace)        {            return await CreateFileAsync(Encoding.UTF8.GetBytes(text), fileName, replace);        }                #endregion        #region 普通方法:判断文件(文件夹)存在,创建(删除)文件夹,获取文件(文件夹)        public bool FileExist(string fileName)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                return sf.FileExists(fileName);            }        }        public bool DirectoryExist(string directory)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                return sf.DirectoryExists(directory);            }        }        public void DeleteFile(string fileName)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                if (sf.FileExists(fileName))                {                    sf.DeleteFile(fileName);                }            }        }        public void CreateDirectory(string directory)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                if (sf.DirectoryExists(directory))                {                    sf.DeleteDirectory(directory);                }            }        }        public void DeleteDirectory(string directory, bool isDeleteAll)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                if (sf.DirectoryExists(directory))                {                    if (isDeleteAll)                    {                        var files = GetFiles(directory);                        foreach (var file in files)                        {                            DeleteFile(file);                        }                        var directories = GetDirectories(directory);                        foreach (var s in directories)                        {                            DeleteDirectory(s, true);                        }                    }                    sf.DeleteDirectory(directory);                }            }        }        public string[] GetFiles(string directory)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                return sf.GetFileNames(directory);            }        }                /// <summary>        /// 获取本地文件夹中的文件        /// </summary>        public string[] GetDirectories(string directory)        {            using (var sf = IsolatedStorageFile.GetUserStoreForApplication())            {                return sf.GetDirectoryNames(directory);            }        }        #endregion        #region 拷贝文件(从安装包到本地)        /// <summary>        /// 从安装包拷贝文件到本地        /// </summary>        public async Task CopyPackageFileToLocalAsync(string source, string target = null, bool replace = false)        {            using (var stream = GetResourceStream(source))            {                await CreateFileAsync(stream, target ?? source, replace);            }        }        /// <summary>        /// 从安装包拷贝路径到本地        /// </summary>        public async Task CopyPackageFolderToLocalAsync(string source, string target = null, bool replace = false)        {            target = target ?? source;            var packagePath = Package.Current.InstalledLocation;            var folder = await GetStorageFolderAsync(packagePath, source);            //拷贝文件            var files = await folder.GetFilesAsync();            foreach (var storageFile in files)            {                var fileName = storageFile.Name;                using (var stream = await storageFile.OpenStreamForReadAsync())                {                    await CreateFileAsync(stream, target + fileName, replace);                }            }            //拷贝子文件夹(递归)            var folders = await folder.GetFoldersAsync();            foreach (var storageFolder in folders)            {                await                    CopyPackageFolderToLocalAsync(source + storageFolder.Name + "/", target + storageFolder.Name + "/",                        replace);            }        }        #endregion        #region 从安装包(安装路径)中读取(同步)        public Stream GetResourceStream(string file)        {            //引用安装路径的文件的时候不以‘/‘开头            file = file.TrimStart(/);            return Application.GetResourceStream(new Uri(file, UriKind.Relative)).Stream;        }                #endregion        #region 序列化        public void Serialize<T>(string fileName, T obj, bool replace)        {            var json = JsonConvert.SerializeObject(obj);            WriteAllText(fileName, json, replace);        }        T IStorageHelper.DeSerialize<T>(string fileName)        {            var json = ReadAllText(fileName);            return JsonConvert.DeserializeObject<T>(json);        }        public async Task SerializeAsync<T>(string fileName, T obj, bool replace)        {            var json = JsonConvert.SerializeObject(obj);            await WriteAllTextAsync(fileName, json, replace);        }        public async Task<T> DeSerializeAsync<T>(string fileName)        {            var json = await ReadAllTextAsync(fileName);            return JsonConvert.DeserializeObject<T>(json);        }         #endregion        #region 辅助方法        /// <summary>        /// 根据路劲获取StorageFolder        /// </summary>        private async Task<StorageFolder> GetStorageFolderAsync(StorageFolder folder, string directory)        {            var directories = directory.Split(new[] { / }, StringSplitOptions.RemoveEmptyEntries);            foreach (var s in directories)            {                folder = await folder.CreateFolderAsync(s, CreationCollisionOption.OpenIfExists);            }            return folder;        }        /// <summary>        /// 根据文件名异步获取本地文件夹StorageFolder(如果路径不存在,则创建路径)        /// </summary>        private async static Task<StorageFolder> GetStorageFolderAsync(string filePath)        {            var localFolder = ApplicationData.Current.LocalFolder;            var directory = Path.GetDirectoryName(filePath);            if (!string.IsNullOrEmpty(directory))            {                var directories = directory.Split(new[] {\\, /}, StringSplitOptions.RemoveEmptyEntries);                foreach (var s in directories)                {                    localFolder = await localFolder.CreateFolderAsync(s, CreationCollisionOption.OpenIfExists);                }            }            return localFolder;        }        /// <summary>        /// 根据路径得到StoreageFile        /// </summary>        private async static Task<StorageFile> GetStorageFileAsync(string filePath)        {            var folder = await GetStorageFolderAsync(filePath);            var fileName = Path.GetFileName(filePath);            if (fileName != null)            {                return await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);            }            return null;        }        #endregion    }}

图片的写入和读取都使用了线程锁,在最后说明

注意:上面的异步方法是线程不安全的,在多线程的情况下,当文件被一个线程写入的时候,另一个线程调用读的方法会抛出异常 Access Deny,访问被阻止

 

 

实现了StorageHelper,下面是CacheableImage的实现,支持占位图片,加载失败图片,配置保存路径

2、自定义可缓存图片控件的实现

<UserControl x:Class="XTuOne.Controls.CacheableImage"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    mc:Ignorable="d"    FontFamily="{StaticResource PhoneFontFamilyNormal}"    FontSize="{StaticResource PhoneFontSizeNormal}"    Foreground="{StaticResource PhoneForegroundBrush}"    d:DesignHeight="480" d:DesignWidth="480">        <Grid x:Name="LayoutRoot">        <Image x:Name="Image" Stretch="Fill"></Image>    </Grid></UserControl>
CacheableImage.xaml

 

using System;using System.IO;using System.Net;using System.Threading.Tasks;using System.Windows;using System.Windows.Media;using System.Windows.Media.Imaging;using XTuOne.Utility.Helpers;namespace XTuOne.Controls{    /// <summary>    /// 支持本地缓存的图片空间    /// </summary>    public partial class CacheableImage    {        public CacheableImage()        {            InitializeComponent();        }        public static readonly DependencyProperty CachedDirectoryProperty = DependencyProperty.Register(            "CachedDirectory", typeof (string), typeof (CacheableImage), new PropertyMetadata("/ImageCached/"));        public static readonly DependencyProperty FaildImageUrlProperty = DependencyProperty.Register(            "FaildImageUrl", typeof(Uri), typeof(CacheableImage), new PropertyMetadata(default(string)));        public static readonly DependencyProperty LoadingImageUrlProperty = DependencyProperty.Register(            "LoadingImageUrl", typeof(Uri), typeof(CacheableImage), new PropertyMetadata(default(string)));        public static readonly DependencyProperty StretchProperty = DependencyProperty.Register(            "Stretch", typeof (Stretch), typeof (CacheableImage), new PropertyMetadata(default(Stretch), StretchPropertyChangedCallback));        private static void StretchPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)        {            var cachedImage = (CacheableImage)dependencyObject;            var stretch = (Stretch)dependencyPropertyChangedEventArgs.NewValue;            if (cachedImage.Image != null)            {                cachedImage.Image.Stretch = stretch;            }        }        public Stretch Stretch        {            get { return (Stretch) GetValue(StretchProperty); }            set { SetValue(StretchProperty, value); }        }                /// <summary>        /// 加载失败的图片        /// </summary>        public Uri FaildImageUrl        {            get { return (Uri)GetValue(FaildImageUrlProperty); }            set { SetValue(FaildImageUrlProperty, value); }        }        /// <summary>        /// 加载中显示的图片(需要进行网络请求时)        /// </summary>        public Uri LoadingImageUrl        {            get { return (Uri)GetValue(LoadingImageUrlProperty); }            set { SetValue(LoadingImageUrlProperty, value); }        }        /// <summary>        /// 缓存到本地的目录        /// </summary>        public string CachedDirectory        {            get { return (string) GetValue(CachedDirectoryProperty); }            set { SetValue(CachedDirectoryProperty, value); }        }        public static readonly DependencyProperty ImageUrlProperty = DependencyProperty.Register(            "ImageUrl", typeof (string), typeof (CacheableImage), new PropertyMetadata(default(string), ImageUrlPropertyChangedCallback));        public string ImageUrl        {            get { return (string)GetValue(ImageUrlProperty); }            set { SetValue(ImageUrlProperty, value); }        }        private static async void ImageUrlPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)        {            var cachedImage = (CacheableImage) dependencyObject;            var imageUrl = (string)dependencyPropertyChangedEventArgs.NewValue;            if (string.IsNullOrEmpty(imageUrl))            {                return;            }            if (imageUrl.StartsWith("http://") || imageUrl.Equals("https://"))            {                var fileName = cachedImage.CachedDirectory + Uri.EscapeDataString(imageUrl);                //网络图片,判断是否存在                if (!StorageHelper.Instance.FileExist(fileName))                {                    try                    {                        if (cachedImage.LoadingImageUrl != null)                        {                            cachedImage.Image.Source =                                new BitmapImage(cachedImage.LoadingImageUrl);                        }                        //请求                        var request = WebRequest.CreateHttp(imageUrl);                        request.AllowReadStreamBuffering = true;                        var response = await request.GetResponseAsync();                        var stream = response.GetResponseStream();                        await Task.Delay(1000);                        //保存到本地                        StorageHelper.Instance.CreateFile(stream, fileName);                    }                    catch (Exception e)                    {                        //请求失败                        if (cachedImage.FaildImageUrl != null)                        {                            cachedImage.Image.Source = new BitmapImage(cachedImage.FaildImageUrl);                        }                        return;                    }                }                 //读取图片文件                var imageStream = StorageHelper.Instance.ReadFile(fileName);                var bitmapImage = new BitmapImage();                bitmapImage.SetSource(imageStream);                cachedImage.Image.Source = bitmapImage;            }            else            {                //本地图片                var bitmapImage = new BitmapImage(new Uri(imageUrl, UriKind.Relative));                cachedImage.Image.Source = bitmapImage;            }        }                public static readonly DependencyProperty ImageStreamProperty = DependencyProperty.Register(            "ImageStream", typeof (Stream), typeof (CacheableImage), new PropertyMetadata(default(Stream), ImageStreamPropertyChangedCallback));        private static void ImageStreamPropertyChangedCallback(DependencyObject dependencyObject,            DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)        {            var cachedImage = (CacheableImage) dependencyObject;            var imageStream = (Stream) dependencyPropertyChangedEventArgs.NewValue;            var bitmapImage = new BitmapImage();            bitmapImage.SetSource(imageStream);            cachedImage.Image.Source = bitmapImage;        }        /// <summary>        /// 支持直接传递流进来        /// </summary>        public Stream ImageStream        {            get { return (Stream) GetValue(ImageStreamProperty); }            set { SetValue(ImageStreamProperty, value); }        }    }}

 

为了保证线程安全,这里图片的保存没有用到异步,因为如果有很多图片进行请求的时候,可能会线程请求异常,像上面说的情况

上面的StorageHelper在读取和写入的时候都加了线程锁(其实不应该在读取文件的时候加锁的),是为了保证,在写入的过程中,读取文件出现无权访问的问题

  暂时没有找到方法支持线程安全,如果你有更好的方案,可以给我留言