一为什么 Bitmap 需要高效加载?
现在的高清大图, 动辄就要好几 M, 而 Android 对单个应用所施加的内存限制, 只有小几十 M, 如 16M, 这导致加载 Bitmap 的时候很容易出现内存溢出如下异常信息, 便是在开发中经常需要的:
java.lang.OutofMemoryError:bitmap size exceeds VM budget
为了解决这个问题, 就出现了 Bitmap 的高效加载策略其实核心思想很简单假设通过 ImageView 来显示图片, 很多时候 ImageView 并没有原始图片的尺寸那么大, 这个时候把整个图片加载进来后再设置给 ImageView, 显然是没有必要的, 因为 ImageView 根本没办法显示原始图片这时候就可以按一定的采样率来将图片缩小后再加载进来, 这样图片既能在 ImageView 显示出来, 又能降低内存占用从而在一定程度上避免 OOM, 提高了 Bitmap 加载时的性能
二 Bitmap 高效加载的具体方式
1. 加载 Bitmap 的方式
Bitmap 在 Android 中指的是一张图片通过 BitmapFactory 类提供的四类方法: decodeFile,decodeResource,decodeStream 和 decodeByteArray, 分别从文件系统, 资源, 输入流和字节数组中加载出一个 Bitmap 对象, 其中 decodeFile,decodeResource 又间接调用了 decodeStream 方法, 这四类方法最终是在 Android 的底层实现的, 对应着 BitmapFactory 类的几个 native 方法
2.BitmapFactory.Options 的参数
inSampleSize 参数
上述四类方法都支持 BitmapFactory.Options 参数, 而 Bitmap 的按一定采样率进行缩放就是通过 BitmapFactory.Options 参数实现的, 主要用到了 inSampleSize 参数, 即采样率通过对 inSampleSize 的设置, 对图片的像素的高和宽进行缩放
当 inSampleSize=1, 即采样后的图片大小为图片的原始大小小于 1, 也按照 1 来计算 当 inSampleSize>1, 即采样后的图片将会缩小, 缩放比例为 1/(inSampleSize 的二次方)
例如: 一张 1024 ×1024 像素的图片, 采用 ARGB8888 格式存储, 那么内存大小 1024×1024×4=4M 如果 inSampleSize=2, 那么采样后的图片内存大小: 512×512×4=1M
注意: 官方文档支出, inSampleSize 的取值应该总是 2 的指数, 如 1,2,4,8 等如果外界传入的 inSampleSize 的值不为 2 的指数, 那么系统会向下取整并选择一个最接近 2 的指数来代替比如 3, 系统会选择 2 来代替当时经验证明并非在所有 Android 版本上都成立
关于 inSampleSize 取值的注意事项: 通常是根据图片宽高实际的大小 / 需要的宽高大小, 分别计算出宽和高的缩放比但应该取其中最小的缩放比, 避免缩放图片太小, 到达指定控件中不能铺满, 需要拉伸从而导致模糊
例如: ImageView 的大小是 100×100 像素, 而图片的原始大小为 200×300, 那么宽的缩放比是 2, 高的缩放比是 3 如果最终 inSampleSize=2, 那么缩放后的图片大小 100×150, 仍然合适 ImageView 如果 inSampleSize=3, 那么缩放后的图片大小小于 ImageView 所期望的大小, 这样图片就会被拉伸而导致模糊
inJustDecodeBounds 参数
我们需要获取加载的图片的宽高信息, 然后交给 inSampleSize 参数选择缩放比缩放那么如何能先不加载图片却能获得图片的宽高信息, 通过 inJustDecodeBounds=true, 然后加载图片就可以实现只解析图片的宽高信息, 并不会真正的加载图片, 所以这个操作是轻量级的当获取了宽高信息, 计算出缩放比后, 然后在将 inJustDecodeBounds=false, 再重新加载图片, 就可以加载缩放后的图片
注意: BitmapFactory 获取的图片宽高信息和图片的位置以及程序运行的设备有关, 比如同一张图片放在不同的 drawable 目录下或者程序运行在不同屏幕密度的设备上, 都可能导致 BitmapFactory 获取到不同的结果, 和 Android 的资源加载机制有关
3. 高效加载 Bitmap 的流程
将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true 并加载图片
从 BitmapFactory.Options 中取出图片的原始宽高信息, 它们对应于 outWidth 和 outHeight 参数
根据采样率的规则并结合目标 View 的所需大小计算出采样率 inSampleSize
将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 false, 然后重新加载图片
三 Bitmap 高效加载的代码实现
- public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- // 加载图片
- BitmapFactory.decodeResource(res,resId,options);
- // 计算缩放比
- options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
- // 重新加载图片
- options.inJustDecodeBounds =false;
- return BitmapFactory.decodeResource(res,resId,options);
- }
- private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
- int height = options.outHeight;
- int width = options.outWidth;
- int inSampleSize = 1;
- if(height>reqHeight||width>reqWidth){
- int halfHeight = height/2;
- int halfWidth = width/2;
- // 计算缩放比, 是 2 的指数
- while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
- inSampleSize*=2;
- }
- }
- return inSampleSize;
- }
这个时候就可以通过如下方式高效加载图片:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
除了 BitmapFactory 的 decodeResource 方法, 其他方法也可以类似实现
四参考文章
https://github.com/LRH1993/android_interview/blob/master/android/basis/bitmap.md
来源: http://www.bubuko.com/infodetail-2503830.html