[译]:Xamarin.Android资源处理——提高加载大位图(Bitmap)的效率

标签: Xamarin.Android, 官方教程, 中文翻译

博客分类: 官方教程

返回索引目录
原文链接: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,但是解码方法设置了OutWidthOutHeightOutMineType的值。此技术 允许你在位图构造(或分配内存)之前读取图像的尺寸和类型。

以下代码段显示了异步获取一个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方法。


译:奇葩史

没有评论