在Android开发中常常遇到加载大图的需求,而手机分配的内存有限,所以如果直接加载到手机经常会oom,因此我们需要解决这类问题。
一 加载缩略图
简单介绍一下BitmapFactory,它是android加载图片的工厂,里面有一个常用的内部类options,这个类很重要,一般对Bitmap压缩就是使用这个类,这个类可以认为是对图片解码时做的一些设置,具体请看官方文档。这里简单的介绍一些常用的属性。
1.inJustDecodeBounds,这个是压缩常用字段,设置为true的时候可以查询bitmap的信息却不占用内存,返回的bitmap为Null,在设置完相对参数后需要设置为false才能得到bitmap对象。
2.inSampleSize这个是压缩的倍数,1相当于和原来一样,2是压缩原来的二分之一,是宽高都变为原来的二分之一。则图像的压缩倍数则是长和宽的倍数的乘积,也就是4。这个最小值为1,小于1的话则会设置成1。设置值如果不是2的倍数则会向下取2的倍数(5取4,3取2,2到1之间的话就不往下取直接为2),不过这是区分不同Android版本的。
以下则用代码来了解以下
public class BitmapUtil {
private static final String TAG = "lmf";
public static Bitmap creatImage(Context context,int res){
BitmapFactory.Options options = new BitmapFactory.Options();
//设置为true获取原图大小而且不加载到内存
options.inJustDecodeBounds = true;
//这时候加载图片并不占用内存,只是获得原图信息而已;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),res,options);
if (bitmap != null){
Log.d("lmf", "creatImage: " +bitmap.getByteCount());
}else {
Log.d(TAG, "creatImage: bitmap == null");
}
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeResource(context.getResources(),res,options);
Log.d(TAG, "creatImage: options.inJustDecodeBounds = false;"+bitmap.getByteCount());
return bitmap;
}
}
而调用地方的代码则
public class MainActivity extends AppCompatActivity {
private static final String TAG = "lmf";
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.daily_pre);
Log.d(TAG, "onCreate: 未压缩的bitmap:"+bitmap.getByteCount());
bitmap = BitmapUtil.creatImage(this,R.drawable.daily_pre);
imageView.setImageBitmap(bitmap);
}
}
打印结果则为:
07-01 15:31:15.895 5465-5465/com.example.limaofang.bitmapdemo D/lmf: onCreate: 未压缩的bitmap:595980
07-01 15:31:15.896 5465-5465/com.example.limaofang.bitmapdemo D/lmf: creatImage: bitmap == null
07-01 15:31:15.899 5465-5465/com.example.limaofang.bitmapdemo D/lmf: creatImage: options.inJustDecodeBounds = false;149152
为什么会打印出bitmap == null这个结果呢?
因为inJustDecodeBounds这个参数设置为true的话,它只会解析图片的信息而不会把bitmap加载到内存,所以会打印出bitmap==null,从上述打印结果来看,当inSampleSize设置2时候,内存的确为原来的4分之一。
二加载高清原图(不允许压缩)
但如果我们遇到需要展示很大的图片又不允许压缩,那该怎么办呢?这时候我们应该要用到一个类BitmapRegionDecoder。
BitmapRegionDecoder主要用于显示指定图片的某一块矩形区域,如果你需要指定显示某一块区域,这个类将非常适合。
BitmapRegionDecoder
提供了一系列的newInstance
方法来构造对象,支持传入文件路径,文件描述符,文件的inputstrem
等。
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
- 上述解决了传入我们需要处理的图片,那么接下来就是显示指定的区域。
bitmapRegionDecoder.decodeRegion(rect, options);
参数一很明显是一个rect,参数二是BitmapFactory.Options,你可以控制图片的
inSampleSize
,inPreferredConfig
等。下面是一个简单的核心代码:
上述代码显示区域为中间400*400的区域。InputStream is = getAssets().open("big_pic.jpg"); //获得图片的宽、高 BitmapFactory.Options tmpOptions = new BitmapFactory.Options(); tmpOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(inputStream, null, tmpOptions); int width = tmpOptions.outWidth; int height = tmpOptions.outHeight; //设置显示图片的中心区域 BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 200, height / 2 - 200, width / 2 + 200, height / 2 + 200), options); mImageView.setImageBitmap(bitmap);
自定义大图显示控件
根据上述分析,自定义大图控件思路就十分清晰了。
- 提供一个设置图片的入口
- 重写 onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数
- 每次更新区域参数后,调用 invalidate,onDraw 里面去regionDecoder.decodeRegion 拿到 bitmap,去 draw