【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在读取和写入的时候都加了线程锁(其实不应该在读取文件的时候加锁的),是为了保证,在写入的过程中,读取文件出现无权访问的问题

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

 

 

 

  

posted @ 2014-07-22 20:26  bomo  阅读(1135)  评论(1编辑  收藏  举报