【Google官方教程】第一课:高效地加载大Bitmap(位图)

原创
2012/11/09 22:51
阅读数 1W

声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-) 

http://my.oschina.net/ryanhoo/blog/88242

译者:Ryan Hoo

来源:https://developer.android.com/develop/index.html

译者按: 在Google最新的文档中,提供了一系列含金量相当高的教程。因为种种原因而鲜为人知,真是可惜!Ryan将会细心整理,将之翻译成中文,希望对开发者有所帮助。

        本系列是Google关于展示大Bitmap(位图)的官方演示,可以有效的解决内存限制,更加有效的加载并显示图片,同时避免让人头疼的OOM(Out Of Memory)。

-------------------------------------------------------------------------------------

译文:

         图像可以有各种各样的形状和大小。在很多情况下,它们往往会比典型的应用UI界面所需要的更大。例如,系统的Gallery程序展示使用Android设备的摄像头拍摄的照片的分辨率往往要远高于设备的屏幕密度。

        考虑到你所使用的内存有限,理想的情况是你只会想加载一个分辨率相对较低的图片到内存中来。低分辨率版本的图片与相应UI组件的尺寸应该是相匹配的。一张高分辨率的图片并不能带给你任何可见的好处,却要占据着宝贵的内存,以及间接导致由于动态缩放引起额外的性能开销。

        这节课将向你演示如何解码大图片,通过加载较小的图片样本以避免超出应用的内存限制。


读取Bitmap(位图)的尺寸和类型

        BitmapFactory提供了几种解码方式(decodeByteArray(), decodeFile(), decodeResource()等等),以便从多种资源中创建一个Bitmap(位图)对象。可以根据你的图片数据来源选择最合适的解码方式。这些方法视图为构造Bitmap对象分配内存,因此很容易导致OutOfMemory(OOM)异常。每一种解码方式都有额外的特征,你可以通过BitmapFactory.Options类指定解码方法。在解码图片的时候设置inJustDecodeBounds属性为true,可以避免内存分配,返回的bitmap对象为null却可以设置outWidth, outHeight和outMimeType。这项技术允许你在创建Bitmap(并分配内存)之前读取图片的尺寸和类型。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
        为了避免java.lang.OutOfMemeory异常,在解码图片之前就要检查图片的尺寸,除非你十分确信图片资源的尺寸是可预见的并且有着充裕的可用内存。

将缩小版的图片加载到内存中

        现在图片的尺寸已经知道了,这些信息可以用来决定是将一个完整尺寸的图片加载到内存中,还是应该用一个图片的子样本来取代它。这里有一些可供考虑的因素:
  • 估计加载全尺寸的图片所要消耗的内存
  • 在考虑应用中其他内存需求的情况下,你愿意给加载这个图片分配的内存空间。
  • 准备加载该图像的目标ImageView或者UI组件的尺寸
  • 当前设备的屏幕的尺寸和密度

        例如,如果最终只是要在ImageView中显示一张128*96px大小的缩略图,直接加载1024*768px的图片是非常不值得的。

        为了告诉解码器如何对图像进行采样,加载更小版本的图片,需要在BitmapFactory.Options对象中将inSampleSize设置为true。例如,一张分辨率为2048*1536px的图像使用inSampleSize值为4的设置来解码,产生的Bitmap大小约为512*384px。相较于完整图片占用12M的内存,这种方式只需0.75M内存(假设Bitmap配置为ARGB_8888)。这里有一个方法用来计算基于目标高宽的sample size的值:

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        if (width > height) {
            inSampleSize = Math.round((float)height / (float)reqHeight);
        } else {
            inSampleSize = Math.round((float)width / (float)reqWidth);
        }
    }
    return inSampleSize;
}

        提示:使用2的次幂来设置inSampleSize值可以使解码器执行地更加迅速、更加高效。但是,如果你想在内存或者硬盘上缓存一个调整过大小的图片,通常还是解码到合适的图片尺寸更加节省空间。

        要使用这个方法,首先要使用inJustDecodeBoundstrue来解码尺寸信息,将options传递过去使用新的inSampleSize值再次解码并且要将inJustDecodeBounds值设置为false

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}
        这个方法使得加载任意大小的Bitmap到展示100*100px缩略图的ImageView中更加简单,如下代码所示:
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
        你可以根据需要,按照类似的解码过程,采用适当的BitmapFactory.decode*方法从其他资源中解码Bitmap。

展开阅读全文
打赏
7
186 收藏
分享
加载中
QLi

引用来自“庄与邻”的评论

引用来自“如幻随影”的评论

经过测试,这种方法读取本地图片还是很消耗内存。我尝试网上另外的方法:
/*
* 以最省内存的方式读取本地资源的图片
*/
public static Bitmap readBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
内存检测,几乎没怎么变化。
如果没有尺寸要求,个人推荐这种。

图片一般从外部读取,你这种要资源ID的根本没法用,除非你把图片打包到APK了。。。那有什么意义。。。

openRawResource.. 我也想这么说 现在一般是从网上获取图片数据
2013/08/04 16:36
回复
举报
您好。我采用这个方法,从相册获取照片显示到imageview,但是总是会报错。。。
2013/03/12 15:36
回复
举报

引用来自“RyanHoo”的评论

引用来自“zerocom520”的评论

引用来自“RyanHoo”的评论

引用来自“zerocom520”的评论

楼主好帖子,支持一下,就是不明白如下逻辑是为神马?
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}

将现有图片的宽高与预期宽高进行比较,计算出合适的sample size值。比如原图:1024 * 768,预期宽高为128 * 88,那么根据宽度来计算(宽度更大,根据它进行计算才能将图片缩小到合适的尺寸),sample size值为 1024/128 = 8,sample size即为8。

你的举例有问题,跟代码逻辑想违背。代码中显示如果width>height,即原图宽大于原图高,就根据高计算sample值,而不是你说的根据宽来计算。如果是你的那样的逻辑我倒理解了···

Sorry,抱歉现在才回复。确实是我犯了个小错误。不过我跟google是两种处理,都没错,但是产生的结果有很大区别。google的方法是以图片质量为先,而我是以图片最小的合适尺寸来算的。可以说是各有利弊。既然是高宽为计算标准,那么举个例子,一个吉昌或者极宽的图片比较有说服力。要求尺寸是100,100。已经有一个长的图片,120,200。如果按照google的方法,sampleSize值为1,根本没压缩。而我以长度(200>100)为基准,sampleSize值为2,但是你看到宽度就变成60,与要求的100还是有差距的,这样拉伸开来,质量不是很理想。但是好处是起到了节省内存的作用。如果图片和要求尺寸比例接近,这两种方法区别不大的。你可以根据自己的项目需求选取不同的方案(width > height...或者相反)。再次致歉!

多谢指教,互相学习学习!
2012/12/03 15:44
回复
举报
RyanHoo博主

引用来自“zerocom520”的评论

引用来自“RyanHoo”的评论

引用来自“zerocom520”的评论

楼主好帖子,支持一下,就是不明白如下逻辑是为神马?
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}

将现有图片的宽高与预期宽高进行比较,计算出合适的sample size值。比如原图:1024 * 768,预期宽高为128 * 88,那么根据宽度来计算(宽度更大,根据它进行计算才能将图片缩小到合适的尺寸),sample size值为 1024/128 = 8,sample size即为8。

你的举例有问题,跟代码逻辑想违背。代码中显示如果width>height,即原图宽大于原图高,就根据高计算sample值,而不是你说的根据宽来计算。如果是你的那样的逻辑我倒理解了···

Sorry,抱歉现在才回复。确实是我犯了个小错误。不过我跟google是两种处理,都没错,但是产生的结果有很大区别。google的方法是以图片质量为先,而我是以图片最小的合适尺寸来算的。可以说是各有利弊。既然是高宽为计算标准,那么举个例子,一个吉昌或者极宽的图片比较有说服力。要求尺寸是100,100。已经有一个长的图片,120,200。如果按照google的方法,sampleSize值为1,根本没压缩。而我以长度(200>100)为基准,sampleSize值为2,但是你看到宽度就变成60,与要求的100还是有差距的,这样拉伸开来,质量不是很理想。但是好处是起到了节省内存的作用。如果图片和要求尺寸比例接近,这两种方法区别不大的。你可以根据自己的项目需求选取不同的方案(width > height...或者相反)。再次致歉!
2012/11/24 21:47
回复
举报

引用来自“RyanHoo”的评论

引用来自“zerocom520”的评论

楼主好帖子,支持一下,就是不明白如下逻辑是为神马?
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}

将现有图片的宽高与预期宽高进行比较,计算出合适的sample size值。比如原图:1024 * 768,预期宽高为128 * 88,那么根据宽度来计算(宽度更大,根据它进行计算才能将图片缩小到合适的尺寸),sample size值为 1024/128 = 8,sample size即为8。

你的举例有问题,跟代码逻辑想违背。代码中显示如果width>height,即原图宽大于原图高,就根据高计算sample值,而不是你说的根据宽来计算。如果是你的那样的逻辑我倒理解了···
2012/11/13 10:25
回复
举报
RyanHoo博主

引用来自“zerocom520”的评论

楼主好帖子,支持一下,就是不明白如下逻辑是为神马?
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}

将现有图片的宽高与预期宽高进行比较,计算出合适的sample size值。比如原图:1024 * 768,预期宽高为128 * 88,那么根据宽度来计算(宽度更大,根据它进行计算才能将图片缩小到合适的尺寸),sample size值为 1024/128 = 8,sample size即为8。
2012/11/12 16:48
回复
举报
楼主好帖子,支持一下,就是不明白如下逻辑是为神马?
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
2012/11/12 16:38
回复
举报

引用来自“庄与邻”的评论

https://developer.android.com/training/displaying-bitmaps/index.html

准确的来源是这个吧,估计现在很多人都不知道training 里面有一堆好文章。。。。

现在很少人会看官方文档,要么百度,要么问群和论坛。
2012/11/12 08:59
回复
举报
非常受用,项目中刚好用到这个技术
2012/11/11 09:45
回复
举报
用过的,有bug的,在《2.1的时候,应该先把bitmap先下载,再decode。
2012/11/10 20:00
回复
举报
更多评论
打赏
30 评论
186 收藏
7
分享
返回顶部
顶部