在做图片相关的应用的时候,经常需要用大图片的缓存,默认的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(, 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, , 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, , 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(); //保存到本地
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在读取和写入的时候都加了线程锁(其实不应该在读取文件的时候加锁的),是为了保证,在写入的过程中,读取文件出现无权访问的问题

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

  

最新文章

  1. 通过goto语句学习if...else、switch语句并简单优化
  2. 08void
  3. QML引擎的演进,第一部分
  4. 正则表达式统计java代码空白行,有效代码
  5. linux qq下载
  6. 定位表的数据块并且dump出来
  7. (转)UML常用图的几种关系的总结
  8. 小猪的Android入门之路 Day 3 - part 3
  9. (转载)让ie6也支持max-width,和max-height实现图片等比例缩放
  10. 实现在线阅读pdf功能--php
  11. 论文学习:Fully Convolutional Networks for Semantic Segmentation
  12. C语言 字符二维数组(多个字符串)探讨 求解
  13. java 命令行JDBC连接Mysql
  14. [hdu 6191] Query on A Tree
  15. epub格式的电纸书
  16. 使用HttpURLConnection时遇到的资源未释放的问题
  17. Densely Connected Convolutional Networks 论文阅读
  18. jquery 事件小事例
  19. boa.config
  20. ios真机连接不上记录,再次执行脚本说找不到真机的解决

热门文章

  1. css中span元素的width属性无效果原因及多种解决方案
  2. JAVA-JSP内置对象之pageContext对象取得不同范围属性
  3. DFI、DPI技术
  4. Android 底部按钮BottomNavigationView + Fragment 的使用(二)
  5. Android——计算器第一次完善
  6. Vagrant (2) —— 基本安装与配置(下)
  7. WCF 几种错误
  8. Intellij Idea搭建Spark开发环境
  9. 【oneday_onepage】——美国主食吃什么
  10. centos7配置kerberos服务,并使用JAAS登录