这是我之前的问题的扩展,metro 应用程序应该如何缓存图像以进行墓碑(并且应该)?
我一直在寻找的解决方案是使用 HttpClient,但这会导致 Web 服务器上的每个文件不必要的第二次命中。
有没有办法从 Image/ImageSource 中保存现有的图像/流(使用 ImageOpened 事件来确保它可用)?
解决方案必须与当前的 RP API 配合使用,因为这是一个从 CP 进行大量更改的领域。
这是我之前的问题的扩展,metro 应用程序应该如何缓存图像以进行墓碑(并且应该)?
我一直在寻找的解决方案是使用 HttpClient,但这会导致 Web 服务器上的每个文件不必要的第二次命中。
有没有办法从 Image/ImageSource 中保存现有的图像/流(使用 ImageOpened 事件来确保它可用)?
解决方案必须与当前的 RP API 配合使用,因为这是一个从 CP 进行大量更改的领域。
首先请注意,在没有做任何自定义(即使用 URI 作为 Image.Source)的情况下,已经实现了某种缓存,就好像您在应用程序旁边运行了 Fiddler,您会看到发出请求一次而不是每次显示一个项目。
话虽这么说,如果您想要某种持久缓存,您可以创建一个转换器来检查所需的图像是否在应用程序的临时目录中,如果不是,则下载它。
这是一种方法:
public sealed class UriToCachedImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
Uri uri = value as Uri;
if (uri == null)
return null;
// Don't let user know those are images, yet keep a unique and consistent name.
string fileName = Path.GetFileName(uri.AbsolutePath);
StorageFile file = null;
try
{
Task<StorageFile> task = ApplicationData.Current.TemporaryFolder.GetFileAsync(fileName).AsTask<StorageFile>();
task.Wait();
file = task.Result;
}
catch (AggregateException ex)
{
if (ex.InnerException is FileNotFoundException)
{
// http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/1eb71a80-c59c-4146-aeb6-fefd69f4b4bb/
}
else
{
throw;
}
}
if (file == null)
{
// File isn't present in cache => download it.
// This creates a file in the app's INetCache
// but we can't retrieve its path or know its name for sure
// hence we're not using this file as our cache file.
Task<StorageFile> streamTask = StorageFile.CreateStreamedFileFromUriAsync(fileName, uri, RandomAccessStreamReference.CreateFromUri(uri)).AsTask<StorageFile>();
streamTask.Wait();
StorageFile storageFile = streamTask.Result;
// Copy file to our temporary directory (can't move as we don't have write access on the initial file).
Task<StorageFile> copyTask = storageFile.CopyAsync(ApplicationData.Current.TemporaryFolder, fileName, NameCollisionOption.ReplaceExisting).AsTask<StorageFile>();
copyTask.Wait();
file = copyTask.Result;
}
Task<IRandomAccessStreamWithContentType> openTask = file.OpenReadAsync().AsTask<IRandomAccessStreamWithContentType>();
openTask.Wait();
IRandomAccessStreamWithContentType randomAccessStream = openTask.Result;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(randomAccessStream);
return bitmapImage;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
注意:此代码片段还说明了如何从同步方法调用异步方法;)
我无法从 ImageOpened 事件中看到我需要的方法,但我找到了一个工作模型。
而不是将图像uri绑定到图像源并让metro下载图像,而是绑定到图像的标签,然后在数据绑定完成后循环遍历xaml中的每个图像并将图像下载为RandomAccessStream,保存流并将 Image Source 设置为从流中创建的 BitmapImage。
RandomAccessStreamReference rasf = RandomAccessStreamReference.CreateFromUri(new Uri(MyImage.Tag as string));
BitmapImage bitmapImage = new BitmapImage();
var ras = await rasf.OpenReadAsync();
bitmapImage.SetSource(ras);
MyImage.Source = bitmapImage;
我还通过将代码放入转换器和视图模型属性来研究自动化过程,但由于异步调用,这两者都不可能。
您还可以使用 FFImageLoading ( https://github.com/molinch/FFImageLoading/ ) 或查看其来源以了解其实现方式 ( https://github.com/molinch/FFImageLoading/tree/master/FFImageLoading.Windows )
这很简单:
<ff:FFImage Name="image"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LoadingPlaceholder="loading.png"
ErrorPlaceholder="error.png"
CacheDuration="30"
RetryCount="3"
RetryDelay="250"
DownsampleHeight="300"
Source="http://lorempixel.com/output/city-q-c-600-600-5.jpg">
</ff:FFImage>
此处的示例项目:https ://github.com/molinch/FFImageLoading/tree/master/samples/Simple.WinPhone.Sample