一.问题描述
当我们决定是把原图加载进内存还是压缩图加载进内存的时候,有几点是需要考虑的:
- 大概的估算一下这张图片占用多少内存。
- 展示图片的控件的实际像素大小。
-
考虑一下当前设备的屏幕尺寸和屏幕分辨率
假设使用ImageView进行加载图片,很多时候ImageView没有图片的尺寸那么大,这个时候你把原始图片加载进来再设置给ImageView,是很浪费内存的,而且没必要,因为ImageView没有办法加载出原始的图片。
第三点和资源的加载机制有关,比如同一张图片放在不同的drawable目录下,通过BitmapFactory获取的宽、高都不尽相同
二.BitmapFactory
它提供了4类方法,分别是:
- decodeResource:从资源加载出Bitmap对象。
- decodeStream:从字节数组加载出Bitmap对象。
- decodeFile: 从文件加载出Bitmap对象。
-
decodeByteArray: 从字节数组加载出Bitmap对象。
这些方法都会为Bitmap分配内存,那就有可能发生OOM。
三.BitmapFactory.Options
那有什么办法可以避免呢。这时候BitmapFactory.Options就要上场了,将它的属性inJustDecodeBounds设置为true就可以让解析方法不给Bitmap分配内存,也就能防止OOM,返回值也不是实际的bitmap,而是null,但是我们还是可以查询图片的相关信息比如宽、高。
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
// 值设为true那么将不返回实际的bitmap,也不给其分配内存空间这样就避免内存溢出了。但是允许我们查询图片的信息这其中就包括图片大小信息
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
四.区域解码(分块加载)
在这里,简单说一下区域解码。假设我们有一张非常大的照片,例如它的分辨率是4000 * 3000。那么,在常规的的手机屏幕(1080 * 1920)上,如果不做压缩处理,我们的图片很明显是显示不开的。因此,我们想到了这样的方案:让图片支持滑动,滑动到哪里,加载哪一部分。如下图片,我们要在手机上按照1:1的比例高清展示出来,那么1080*1920的区域,大概只能让他显示黑框中的区域。那其他的区域就需要我们滑动去加载。滑动到哪里,就展示哪一块区域。
五.BitmapRegionDecoder
1、使用
(1)创建BitmapRegionDecoder
使用区域解码,那么我们首先需要创建一个BitmapRegionDecoder对象。只需要调用newInstance方法,传入一个InputStream和一个boolean值。如下所示:
mDecoder = BitmapRegionDecoder.newInstance(is, false);
2)解码Bitmap
调用decodeRegion方法解码Bitmap,需要传入一块区域,以及参数,代码如下:
Bitmap bitmap = mDecoder.decodeRegion(mRect, mDecodeOptions);
六.自定义大图加载View
package com.luisliuyi.demo.optimize.bitmap03;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
public class LYBigView extends View implements View.OnTouchListener, GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener {
//图片缩放因子
private float mScale;
//需要显示的区域
private Rect mRect;
//区域解码器
private BitmapRegionDecoder mDecode;
private BitmapFactory.Options mOptions;
//图片的宽度
private int mImageWidth;
//图片的高度
private int mImageHeight;
//控件的宽度
private int mViewWidth;
//控件的高度
private int mViewHeight;
private GestureDetector mGestureDetector;
//缩放功能
ScaleGestureDetector mScaleGestureDetector;
private float originalScale;
//需要展示的图片,是被复用的
private Bitmap mBitmap;
//滑动帮助类
private Scroller mScroller;
private Matrix matrix = new Matrix();
public LYBigView(Context context) {
super(context);
}
public LYBigView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mRect = new Rect();
mOptions = new BitmapFactory.Options();
mGestureDetector = new GestureDetector(context,this);
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGesture());
mScroller = new Scroller(context);
setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent motionEvent) {
mGestureDetector.onTouchEvent(motionEvent);
mScaleGestureDetector.onTouchEvent(motionEvent);
return true;
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mRect.offset((int)distanceX,(int)distanceY);
if (mRect.bottom > mImageHeight) {
mRect.bottom = mImageHeight;
mRect.top = mImageHeight-(int)(mViewHeight/mScale);
}
if(mRect.top < 0){
mRect.top = 0;
mRect.bottom = (int)(mViewHeight/mScale);
}
if(mRect.left < 0) {
mRect.left = 0;
mRect.right = (int)(mViewWidth/mScale);
}
if(mRect.right > mImageWidth) {
mRect.right = mImageWidth;
mRect.left = mImageWidth-(int)(mViewWidth/mScale);
}
invalidate();
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(mRect.left,mRect.top,(int)velocityX,-(int)velocityY,0,
mImageWidth-(int)(mViewWidth/mScale),0,mImageHeight-(int)(mViewHeight/mScale));
return false;
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.isFinished()){
return;
}
if (mScroller.computeScrollOffset()) {
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top+(int)(mViewHeight/mScale);
invalidate();
}
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
// 处理缩放的回调事件
class ScaleGesture extends ScaleGestureDetector.SimpleOnScaleGestureListener{
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = mScale;
scale += detector.getScaleFactor()-1;
if(scale <= originalScale){
scale = originalScale;
}else if(scale > originalScale*2){
scale = originalScale*2;
}
mRect.right = mRect.left + (int)(mViewWidth/scale);
mRect.bottom = mRect.top+(int)(mViewHeight/scale);
mScale = scale;
invalidate();
return super.onScale(detector);
}
}
public void setImage(InputStream is) {
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
//开启复用内存
mOptions.inMutable = true;
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
//第二次配置才会加载像素
mOptions.inJustDecodeBounds = false;
try {
mDecode = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
//刷新
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
mRect.left = 0;
mRect.top = 0;
mRect.right = Math.min(mImageWidth, mViewWidth);
mRect.bottom = Math.min(mImageHeight,mViewHeight);
// 再定义一个缩放因子
originalScale = mViewWidth/(float)mImageWidth;
mScale = originalScale;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mDecode) {
return;
}
mOptions.inBitmap = mBitmap;
matrix.setScale(mScale, mScale);
mBitmap = mDecode.decodeRegion(mRect, mOptions);
canvas.drawBitmap(mBitmap, matrix, null);
}
}
七.代码地址
学习方案:
https://gitee.com/luisliuyi/android-optimize-bitmap03.git
成熟方案:
https://gitee.com/luisliuyi/android-optimize-bitmap04.git
网友评论