高效加载大型位图
注意:有几个库遵循了加载图片的最佳做法。您可以在应用中使用这些库,从而以最优化的方式加载图片。我们建议您使用 Glide(https://github.com/bumptech/glide) 库,该库会尽可能快速、顺畅地加载和显示图片。其他常用的图片加载库包括 Square 的 Picasso、Instacart 的 Coil和 Facebook 的 Fresco。这些库简化了与位图和 Android 上的其他图片类型相关的大多数复杂任务。
图片有各种形状和大小。在很多情况下,它们的大小超过了典型应用界面的要求。例如,系统“图库”应用会显示使用 Android 设备的相机拍摄的照片,这些照片的分辨率通常远高于设备的屏幕密度(屏幕分辨率)。
鉴于您使用的应用内存有限,理想情况下您只希望在内存中加载较低分辨率的版本。分辨率较低的版本应与显示该版本的界面组件的大小相匹配。分辨率更高的图片不会带来任何明显的好处,但仍会占用宝贵的内存,并且会因为额外的动态缩放而产生额外的性能开销。
本节课向您介绍如何通过在内存中加载较小的下采样版本来解码大型位图Bitmap,从而不超出每个应用的内存限制(Android 不同厂商,不同版本的设备,应用使用内存大小限制不一样)。
读取位图尺寸和类型
BitmapFactory类提供了几种用于从各种来源创建 Bitmap 的解码方法(decodeByteArray()、decodeFile()、decodeResource()等)。根据您的图片数据源选择最合适的解码方法。这些方法尝试为构造的位图分配内存,因此很容易导致 OutOfMemory 异常
(OOM = OutOfMemoryError,这个异常可以捕获吗?请思考,见文末
)。每种类型的解码方法都有额外的签名,允许您通过 BitmapFactory.Options类指定解码选项。在解码时将 inJustDecodeBounds属性设置为 true 可避免内存分配,为位图对象返回 null,但设置了Bitmap的 outWidth、outHeight 和 outMimeType。此方法可让您在构造位图并为其分配内存之前读取图片数据的尺寸和类型。
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; // 图片类型
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(resources, R.id.myimage, options)
val imageHeight: Int = options.outHeight
val imageWidth: Int = options.outWidth
val imageType: String = options.outMimeType
为避免出现 java.lang.OutOfMemory 异常,请先检查位图的尺寸,然后再对其进行解码,除非您绝对信任该来源可为您提供大小可预测的图片数据,以轻松适应可用的内存。
将按比例缩小的版本加载到内存中
既然图片尺寸已知,便可用于确定
应将完整图片加载到内存中,还是应改为加载下采样版本。以下是需要考虑的一些因素:
- 在内存中加载完整图片的估计内存使用量。(怎么算图片内存大小?见文末)
- 根据应用的任何其他内存要求,您愿意分配用于加载此图片的内存量。
- 图片要载入到的目标 ImageView或界面组件的尺寸。
- 当前设备的屏幕大小和密度。
例如,如果 1024x768 像素的图片最终会在 ImageView中显示为 128x96 像素缩略图,则不值得将其加载到内存中。
要让解码器对图片进行下采样,以将较小版本加载到内存中,请在 BitmapFactory.Options 对象中将 inSampleSize 设置为 true。例如,分辨率为 2048x1536 且以 4 作为 inSampleSize 进行解码的图片会生成大约 512x384 的位图。将此图片加载到内存中需使用 0.75MB,而不是完整图片所需的 12MB(假设位图配置为 ARGB_8888)。下面的方法用于计算样本大小值,即基于目标宽度和高度的 2 的幂:
Java
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that
//is a power of 2 and keeps both
// height and width larger than the requested height
//and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
fun calculateInSampleSize(options: BitmapFactory.Options,
reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run {
outHeight to outWidth
}
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that
// is a power of 2 and keeps both
// height and width larger than the requested height
// and width.
while (halfHeight / inSampleSize >= reqHeight
&& halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
注意:根据 inSampleSize 文档,计算 2 的幂的原因是解码器使用的最终值将向下舍入为最接近的 2 的幂。
Note: the decoder uses a final value based on powers of 2,
any other value will be rounded down to the nearest power of 2.
要使用此方法,请先将 inJustDecodeBounds设为 true 进行解码,传递选项,然后使用新的 inSampleSize值并将 inJustDecodeBounds 设为 false 再次进行解码:
JAVA
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);
}
fun decodeSampledBitmapFromResource(
res: Resources,
resId: Int,
reqWidth: Int,
reqHeight: Int
): Bitmap {
// First decode with inJustDecodeBounds=true to
// check dimensions
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeResource(res, resId, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
BitmapFactory.decodeResource(res, resId, this)
}
}
采用此方法,您可以轻松地将任意大尺寸的位图加载到显示 100x100 像素缩略图的 ImageView中,如以下示例代码所示:
imageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)
);
imageView.setImageBitmap(
decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100))
您可以按照类似的流程来解码其他来源的位图,只需根据需要替换相应的 BitmapFactory.decode*方法即可。
1、关于异常的捕获 :
try{
}catch(Exception e){
}
try{
// 加载大图片 ,OutOfMemoryError
}catch(Error e){
// 加载图片失败
}
try{
}catch (Throwable throwable){
}
Exception 和 Error 都继承了 Throwable
catch 是能捕获 Throwable 和 exception 和 error
面试题: OutOfMemoryError能捕获吗 ? 上面的例子是可以的!
图片加载的时候避免OOM!
2、Bitmap 加载内存的大小计算
/* A bitmap configuration describes
how pixels are stored. This affects the quality (color depth) as
well as the ability to display transparent/translucent colors.
*/
Bitmap.config // 描述像素如何被存储,这影响质量(颜色深度)
//以及显示透明/半透明颜色的能力。
Bitmap.Config.ALPHA_8 :只有一个alpha通道,占用1Byte。
Bitmap.Config.RGB_565:每个像素占2Byte,其中红色占5bit,绿色占6bit,蓝色占5bit。
@Deprecated
Bitmap.Config.ARGB_4444:每个像素占2Byte,每个通道4bit,从API 13开始不建议使用。
Bitmap.Config.ARGB_8888:每个像素占4Byte,每个通道8bit。
这个格式最常用,通常是用于PNG的图片,支持透明度。
val bitmap = Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888)
图片像素的长 * 宽 * 存储字节 :
100 * 100 * 4 = 40000 byte = 39 Kb
From:https://developer.android.com/topic/performance/graphics/load-bitmap
网友评论