在我们的程序运行的时候,Android系统分配给我们程序的内存是有限的,当程序使用的内存过多,就会出现OOM(Out Of Memory),最常见出现OOM的地方就是加载图片,下面我就来介绍下如何高效的加载图片到我们的程序中。
图片的大小怎么计算?
在Android中,图片一共有4中配置,分别为
A:透明度 R:红色 G:绿 B:蓝
Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位
Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。
默认为Bitmap.Config ARGB_8888,也就是一个像素占用4个字节(4k)。一张1200*800像素的图片占用内存为 1200*800*4/1028/1024 = 3.6M。
1M = 1024 KB = 1024*1024 K = 1024*1024*8 位
处理的方法
对图片处理的方法有两种,第一种是降低图片质量。第二种是缩放图片尺寸,也可以把两种方法结合起来使用,避免OOM。
降低图片质量
一般情况下,用户不需要那么高精度的图片,比如显示的是一张略缩图,如果我们也是加载原图的话,而原图很大,那就很可能OOM了。
//加载低质量图片 BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img1, options);
在上面的代码中,我门使用了BitmapFactory.Options去设置加载的属性,让程序加载的图片格式为Bitmap.Config.RGB_565,从上面的分析可以看出来,这样图片占用的内存为默认配置的一半,而在测试过程中,发现和默认效果没有很大的区别。
减少图片尺寸
如果把一个大小为1024x768像素的图片显示到大小为128x96像素的ImageView上,显然没有必要把整张原图都加载到内存中,而是将原图缩放为128x96即可。
- 获取原图的尺寸
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;
设置 inJustDecodeBounds 属性为true可以在解码的时候避免内存的分配,它会返回一个null的Bitmap,但是可以获取到 outWidth, outHeight 与 outMimeType。该技术可以允许你在构造Bitmap之前优先读图片的尺寸与类型。(在加载图片之前获取图片的大小信息)
- 设置图片的缩放
图片的缩放主要用到了BitmapFactory.Options的inSampleSize属性,默认为1,当inSampleSize大于1的时候,比如为2,那么缩放后的图片宽高均为原图的1/2,占用内存为原图的1/4,inSampleSize必须大于1才有效果,另外,官方文档指出,inSampleSize的值应该总为2的指数,比如1、2、4、8等,如果外界传递给系统的值不为2的指数,那么系统会自己向下取整,并选择一个接近2的指数的数来代替。
/**
* 计算InSampleSize
* @param options 需要缩放图片的配置信息
* @param reqWidth 缩放后的宽
* @param reqHeight 缩放后的高
* @return 计算出的InSampleSize
*/
public 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 > reqWidth || width > reqHeight) {
float ratioWidth = (float) width / reqWidth;
float ratioHeight = (float) height / reqHeight;
inSampleSize = (int) Math.min(ratioHeight, ratioWidth);
}
//因为系统会自己向下取整为最接近的2的指数,所以我们不处理
inSampleSize = Math.max(1, inSampleSize);
return inSampleSize;
}
封装为一个函数就是:
/**
* 缩放从资源文件中加载的图片
* @param res 资源文件对象
* @param resId 文件id
* @param reqWidth 需要设置的宽
* @param reqHeight 需要设置的高
* @return 缩放后的Bitmap对象
*/
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 计算出图片的尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 计算出inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
测试代码
布局文件为3个ListView,用于显示原图,低质量图片,小尺寸图片。查看显示效果。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/img1"
android:layout_width="280dp"
android:layout_height="140dp" />
<ImageView
android:id="@+id/img2"
android:layout_width="280dp"
android:layout_height="140dp" />
<ImageView
android:id="@+id/img3"
android:layout_width="280dp"
android:layout_height="140dp" />
</LinearLayout>
主Activity代码
public class MainActivity extends AppCompatActivity {
private Bitmap mBitmap;
private static final String TAG = "MainActivity";
private ImageView mImg1;
private ImageView mImg2;
private ImageView mImg3;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImg1 = (ImageView) findViewById(R.id.img1);
mImg2 = (ImageView) findViewById(R.id.img2);
mImg3 = (ImageView) findViewById(R.id.img3);
//直接加载原图
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.img1);
mImg1.setImageBitmap(bitmap1);
//加载低质量图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.img1, options);
mImg2.setImageBitmap(bitmap2);
//加载小尺寸图片
//以为在onCreate里面无法获取ImageView的宽高,所以这里先强行测量大小
//布局文件中大小为280dp * 140dp
mImg3.measure(View.MeasureSpec.makeMeasureSpec(dp2px(280), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(dp2px(140), View.MeasureSpec.EXACTLY));
Bitmap bitmap3 = decodeSampledBitmapFromResource(getResources(),
R.drawable.img1, mImg3.getMeasuredWidth(), mImg3.getMeasuredHeight());
mImg3.setImageBitmap(bitmap3);
Log.i(TAG, "原图" + bitmap1.getByteCount());
Log.i(TAG, "低质量" + bitmap2.getByteCount());
Log.i(TAG, "小尺寸" + bitmap3.getByteCount());
}
/**
* dp转px
*
* @param dp 待转换的dp
* @return 打牌转px的结果
*/
int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
/**
* 计算InSampleSize
*
* @param options 需要缩放图片的配置信息
* @param reqWidth 缩放后的宽
* @param reqHeight 缩放后的高
* @return 计算出的InSampleSize
*/
public 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 > reqWidth || width > reqHeight) {
float ratioWidth = (float) width / reqWidth;
float ratioHeight = (float) height / reqHeight;
inSampleSize = (int) Math.min(ratioHeight, ratioWidth);
}
//因为系统会自己向下取整为最接近的2的指数,所以我们不处理
inSampleSize = Math.max(1, inSampleSize);
return inSampleSize;
}
/**
* 缩放从资源文件中加载的图片
*
* @param res 资源文件对象
* @param resId 文件id
* @param reqWidth 需要设置的宽
* @param reqHeight 需要设置的高
* @return 缩放后的Bitmap对象
*/
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 计算出图片的尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 计算出inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
结果对比
Log输出为
05-11 08:10:02.906 20605-20605/com.jay.testloadimage I/MainActivity: 原图16785408
05-11 08:10:02.906 20605-20605/com.jay.testloadimage I/MainActivity: 低质量8392704
05-11 08:10:02.906 20605-20605/com.jay.testloadimage I/MainActivity: 小尺寸4196352
效果图如下,可以看到,原图加载占用内存约为16M,低质量为8M,小尺寸为4M,如果两种方式结合起来大小应该为2M,可是显示效果区别不大,所以,加载大图时,在图片要求不大的情况下应该进行处理,不然容易OOM。

延伸阅读