Android加载大长图
一、为什么Android加载图片要进行压缩?
Android系统为app分配的内存是有限的,在Android开发中如果不对图片加载进行处理,可能会导致OOM(Out of Memory),所以图片的压缩不得不作为Android开发中比用的一项技能点。
二、Android开发中图片的大小如何计算?
图片大小 = 图片的width乘以图片的height然后再乘以每一个像素所占用的字节数
这个字节数需要根据图片解码模式来获得,Android中提供了6种方案,常用的只有三个,如下所示。
Bitmap.Config.ARGB_8888:由4个8位组成,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位(4字节)
Bitmap.Config.ARGB_4444:由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 (2字节)
Bitmap.Config.RGB_565:没有透明度,R=5,G=6,B=5,,那么一个像素点占5+6+5=16位(2字节)
色彩模式ARGB分别是什么含义
A:透明度(Alpha)
R:红色(Red)
G:绿(Green)
B:蓝(Blue)
需要了解知识:
8bit(位)=1byte(字节)
1024byte=1KB
1024kb=1MB
1024mb=1GB
三、图片加载优化
通常为了避免内存泄漏在加载图片时往往进行压缩或进行内存复用
Bitmap内存复用
内存复用,通俗来说就是我已经创建了一个Bitmap对象了,那么我接着还想用一个bitmap对象,那么就可以复用上一个Bitmap对象。
(1)内存复用缺点
这样做有两个缺点,第一就是会将之前的图片覆盖掉,第二就是后边加载的图片必须小于或者等于之前的图片大小,否则就不行。
(2)内存复用优点
bitmap复用内存块,不需要再重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
四、自定义view实现使用内存复用加载长图
(1)背景介绍
实现目标:加载显示大长图
理论:只加载需要显示的部分其他部分不加载
(2)实现步骤
主要分为三大步 :
- 画出图形
- 手指移动滑动图片
- 加载assets目录下长图
具体一步步实现代码 :
第一步,在自定义view构造器中设置BigView所需要的一些成员变量
mRect = new Rect(); //矩形区域
mOptions = new BitmapFactory.Options(); //内存复用
mGestureDetector = new GestureDetector(context,this); //手势识别
mScroller = new Scroller(context); //滚动类
setOnTouchListener(this); //设置监听
第二步,设置图片得到图片信息(注意获取图片宽和高不能将图片整个加载进内存)
public void setImage(InputStream is){
mOptions.inJustDecodeBounds = true; //设置仅加载图片的宽高信息不将图片加载的内存中
BitmapFactory.decodeStream(is,null,mOptions);
mImageWith = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
mOptions.inMutable = true; //开启复用
mOptions.inPreferredConfig = Bitmap.Config.RGB_565; //设置解码模式(质量压缩),设置格式为RGB565(相比ARGB_8888去掉了透明通道,消耗内存少,但是图片质量会降低一些)
mOptions.inJustDecodeBounds = false; //消除仅加载图片的宽高
try {
mDecoder = BitmapRegionDecoder.newInstance(is,false);//区域解码器
} catch (IOException e) {
e.printStackTrace();
}
requestLayout(); //刷新
}
第三步,开始测量,得到view的宽高保存在Rect中,测量加载的图片缩放成什么样子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//得到view的宽高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
//确定加载图片的区域
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWith;
//计算缩放因子
mScale = mViewWidth/(float)mImageWith;
mRect.bottom = (int) (mViewHeight/mScale);
}
第四步,画出具体的内容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//判断解码器是不是为null,如果解码器没有拿到,表示没有设置图片
if(mDecoder == null){
return;
}
//真正内存复用 注意复用的bitmap必须要跟即将解码的bitmap尺寸一样
mOptions.inBitmap = mBitmap; //绑定一个已经加载的Bitmap对象
//指定解码区域
mBitmap = mDecoder.decodeRegion(mRect,mOptions);
//得到一个矩阵进行缩放,相当于得到的view的大小
Matrix matrix = new Matrix();
matrix.setScale(mScale,mScale);
canvas.drawBitmap(mBitmap,matrix,null);
}
第五步,处理点击事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//直接将事件交给手势事件处理
return mGestureDetector.onTouchEvent(event);
}
第六步,处理手按下去
@Override
public boolean onDown(MotionEvent e) {
//如果移动没有停止,强行停止
if(!mScroller.isFinished()){
mScroller.forceFinished(true);
}
//继续接收后续事件
return true;
}
第七步,处理滑动事件
//e1:开始事件,手按下去,开始获取坐标
//e2:获取当前事件坐标
//xy:xy轴移动的距离
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,mRect需要改变显示的区域
mRect.offset(0, (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);
}
invalidate();//重绘
return false;
}
第八步,处理惯性问题
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//注意:手势滑动方法onFling中的velocityY参数与Scroller.fling方法的参数velocityY方向是反的
mScroller.fling(0,mRect.top,0,(int)-velocityY,0,0,0,
mImageHeight-(int)(mViewHeight/mScale));
return false;
}
第九步,处理计算结果
@Override
public void computeScroll() {
if(mScroller.isFinished()){
return;
}
//滚动还没结束,加载
if(mScroller.computeScrollOffset()){
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top+(int)(mViewHeight/mScale);
invalidate();
}
}
第十步,在加载长图的布局文件中使用BigView布局,然后在对应activity中获取BigView并加载assets目录下图片
BigView bigView = findViewById(R.id.bigView);
InputStream is = null;
try {
is = getAssets().open("long.jpg");
} catch (IOException e) {
e.printStackTrace();
}
bigView.setImage(is);