[译]:Xamarin.Android资源处理——提高加载大位图(Bitmap)的效率
博客分类: 官方教程
返回索引目录
原文链接:Load Large Bitmaps Efficiently
译文链接:Xamarin.Android资源处理——提高加载大位图(Bitmap)的效率
Load Large Bitmaps Efficiently
本文展示如何通过加载缩小版本的图像来将大的图片加载到内存,从而避免应用抛出内存溢出异常(OutOfMemoryException)。
示例代码下载:
操作步骤
图片有很多形状和大小。在很多情况下,它们会比一般应用界面所需的大小要大。例如,系统相册应用展示Android设备拍摄的照片,通常情况下,照片的分辨率要比设备的屏幕分辨率要大的多。
如果你的设备内存有限,你只需要在内存中加载较低分辨率的版本。而较低分辨率版本的应该与显示的用户界面组件大小匹配。高分辨率的图像并不能带来好处,而且还占用本就不多的内存,并且由于视图还要执行额外的缩放操作,这回导致额外的性能开销。
在本文中,我们涵盖了如何加载一个图片等比缩小的图片,那样它就可以有效的加载,并且对于Android设备内存影响也会较小。下面示例图中,展示了经过适当缩小后所看到的一张 4000x3000 像素的drawable资源:
读取位图尺寸和类型
BitmapFactory类提供了几种方法(如DecodeResourceAsync)来从各类来源创建Bitmap。这些方法为了构造位图会尝试为其分配内存,但这很容易导致OutOfMemoryException。每种类型的解码方法都有额外的签名,他们可以通过BitmapFactory.Options类来指定解码选项,如加载较小版本的位图。
将InJustDecodeBounds属性设置为true
可以避免解码时分配内存,同时bitmap对象返回null
,但是解码方法设置了OutWidth、OutHeight和OutMineType的值。此技术 允许你在位图构造(或分配内存)之前读取图像的尺寸和类型。
以下代码段显示了异步获取一个drawable资源的高度和宽度的方法:
async Task<BitmapFactory.Options> GetBitmapOptionsOfImageAsync()
{
BitmapFactory.Options options = new BitmapFactory.Options
{
InJustDecodeBounds = true
};
// The result will be null because InJustDecodeBounds == true.
Bitmap result= await BitmapFactory.DecodeResourceAsync(Resources, Resource.Drawable.samoyed, options);
int imageHeight = options.OutHeight;
int imageWidth = options.OutWidth;
_originalDimensions.Text = string.Format("Original Size= {0}x{1}", imageWidth, imageHeight);
return options;
}
将缩小的版本加载到内存中
经过上面的内存,至此,我们已经知道文件的尺寸,它们可以用于判断决定是加载完整图片到内存中,还是加载一个子样本版本。此处还有以下几点 需要考虑:
- 估计加载完整图片到内存中的内存使用情况
- 在满足你应用其他内存要求的情况下,你愿意为加载图片提供多少内存量
- 界面ImageView或其他组件的尺寸 —— 用于加载图片的控件
- 当前设备的屏幕尺寸和分辨率。
例如,要使用具有Argb8888配置的4000x3000像素的图片。如果将完整的图片加载到内存中大约需要46.8MB的RAM。显然,加载小版本的图像更好。要让解码器对图像进行采样来加载较小版本的图片到内存中,需要将InSampleSize设置为将用于缩小图像的值。例如,将InSampleSize设置为2,BitMapFactory会将图像按比例 缩小两倍。比例可以设置任何值,此处BitmapFactory使用最优的比例值2。
下面展示了一个根据目标宽度和高度来计算InSampleSize的值 —— 使用2的次方处理:
public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
float height = options.OutHeight;
float width = options.OutWidth;
double inSampleSize = 1D;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = (int)(height / 2);
int halfWidth = (int)(width / 2);
// Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
{
inSampleSize *= 2;
}
}
return (int)inSampleSize;
}
如果我们使用以上代码处理4000x3000的图像,然后要将其缩小后加载到150x150的ImageView中,此方法将会计算出InSampleSize值为16。这意味着,BitmapFactory将加载250x187的图像,它只需要183kb的RAM —— 节省了很多内存。
加载图片
以下将展示如何使用上述方法来加载缩小版本的位图。首先,在将位图加载到内存之前,我们使用GetBitmapOptionOfImageAsync来获得BitmapFactory.Options的值。然后通过Bitmap.Options来辅助计算给定图像的大小的最好的InSampleSize的 值。例如,下面代码片段展示了如何加载drawable资源,并转化为150x150的缩略图:
protected async override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
_imageView = FindViewById<ImageView>(Resource.Id.resized_imageview);
BitmapFactory.Options options = await GetBitmapOptionsOfImageAsync();
Bitmap bitmapToDisplay = await LoadScaledDownBitmapForDisplayAsync (Resources, options, 150, 150);
_imageView.SetImageBitmap(bitmapToDisplay);
}
注意,所有工作都是使用async/await来 异步执行,这防止位图处理阻塞主线程,并保持应用程序响应。
LoadScaledDownBitmapForDisplayAsync方法使用在下面代码片段中显示:
public async Task<Bitmap> LoadScaledDownBitmapForDisplayAsync(Resources res, BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return await BitmapFactory.DecodeResourceAsync(res, Resource.Drawable.samoyed, options);
}
你可以使用类似的过程来解码其他来源的位图,如SD卡上的一个文件 —— 依据需要选择适当的BitmapFactory.DecodeXXX方法。
译:奇葩史