我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android大图加载优化方案,避免程序OOM

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android大图加载优化方案,避免程序OOM

我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小。比如微博长图,海报等等。所以我们就要对图片进行局部显示。

大图加载基本需求和原理分析

在这里插入图片描述
基本需求:当我们有一张绿色大小的大图,我们需要让其展示成蓝色部分的大小,一般在我们滑动的过程中我们就只能看到蓝色部分的图片,蓝色部分的下面部分通过向下滑动才能看到。
原理分析:这里涉及到区域加载,由于我们人眼就只能看到占满手机屏幕大小的图片,蓝色部分下面部分是看不到的,这就意味着我们每次加载图片只需要加载到我们能看到的区域即可,看不到的区域就不加载。
在这里插入图片描述

假设我们的图片高度是手机的5倍,那我们首次加载其实就是图片的1/5,而我们不管继续往下滑,每次都加载图片的1/5,那么我们就能节省4/5的内存。
那么问题来了?我们如何做到区域加载和内存复用
在这里插入图片描述

比如我们讲图片分成了5份,我们每次都加载这1/5的内存,为了确保每次都加载1/5的内存,假设我们滑到了第二块区域,依然也是用我们加载第一块区域时的内存,不然的话就相当于我们把5份的内存都加载进去了,可能会造成OOM。

大图加载基础api解析

        //设置一个矩形区域(可以理解为矩形区域框定)      Rect  mRect = new Rect();        //用于内存复用(Google提供的对内存复用设置一些参数,比如设置编码格式)      BitmapFactory.Options  mOptions = new BitmapFactory.Options();        //手势支持      GestureDetector  mGestureDetector = new GestureDetector(context, this);        //滚动类      Scroller  mScroller = new Scroller(context);       //触摸时触发事件,比如触碰就停止屏幕滚动      setOnTouchListener(this);

在这里插入图片描述
我们要将绿色大小的原图转换成手机屏幕大小的蓝图就需要对图片进行缩放,就需要获取图片大小等相关信息。但问题有来了,我们在获取图片宽高信息的时候不能把整个图片加载进来,不然我们内存复用就没意义,这个时候就用到了mOptions。

//inJustDecodeBounds方法,只加载边缘区域来获取图片宽高        mOptions.inJustDecodeBounds=true;            //将is传进去解码就能获取到图片的宽和高        BitmapFactory.decodeStream(is,null,mOptions);          //拿到宽和高        mImageWidth = mOptions.outWidth;        mImageHeight = mOptions.outHeight;          //开启内存复用        mOptions.inMutable=true;        //设置图片格式:rgb565        mOptions.inPreferredConfig= Bitmap.Config.RGB_565;         //用完需要关闭        mOptions.inJustDecodeBounds=false;

通过这种方式就能获取到图片的宽和高,并且没有将整张图片加载进内存

图片编码格式与占用内存之间关系

在这里插入图片描述

比如Glide使用的是RGB_565,Picasso使用的是ARGB_8888
当我们对一张图片进行无限的放大,你会发现它是由n个像素点组成,每个像素点都有自己的颜色,比如下面的图片就是有黑色,黄色和浅黄色
在这里插入图片描述
而当缩回去的时候就会发现是一张正常的图片,可以发现图片由像素点组成
在这里插入图片描述
像素点由RGB组成,三元色(红绿蓝)
而ARGB_8888表示图片中的像素有A,R,G,B四种颜色通道,每个通道占用内存为8位,共32位,相当于4个字节,也就是说每个像素点占用4个字节。
RGB_565相比于ARGB_8888少了A透明通道,表示的是R通道占5位,G通道占6位,B通道占5位,共16位,相当于2个字节,也就是说每个像素点占用2个字节。这样的话内存就相比于上面减少可一半。

大图加载之图片初始化展示实现

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        BigView bigView= findViewById(R.id.bigView);        InputStream is=null;        try {            is= getResources().getAssets().open("test.jpg");            bigView.setImage(is);        } catch (IOException e) {            e.printStackTrace();        }    }}
public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {    private Rect mRect;    private BitmapFactory.Options mOptions;    private GestureDetector mGestureDetector;    private Scroller mScroller;    private int mImageWidth;    private int mImageHeight;    private BitmapRegionDecoder mDecoder;    private int mViewWidth;    private int mViewHeight;    private Bitmap mBitmap;    private float mScaleX;    private float mScaleY;    public BigView(Context context) {        this(context,null);    }    public BigView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr,0);    }    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        //第一步 设置BigView需要的成员变量        //设置一个矩形区域(矩形区域框定)        mRect = new Rect();        //用于内存复用(设置编码格式)        mOptions = new BitmapFactory.Options();        //手势支持        mGestureDetector = new GestureDetector(context, this);        //滚动类        mScroller = new Scroller(context);        //触摸时触发事件        setOnTouchListener(this);    }    //第二步设置图片    public void setImage(InputStream is){        //获取图片的宽和高        //此时不能将整张图片加载进来,这样内存复用无意义,需要使用inJustDecodeBounds方法,只加载部分区域来获取图片宽高        mOptions.inJustDecodeBounds=true;        //将is传进去解码就能获取到图片的宽和高        BitmapFactory.decodeStream(is,null,mOptions);        mImageWidth = mOptions.outWidth;        mImageHeight = mOptions.outHeight;        //开启内存复用        mOptions.inMutable=true;        //设置图片格式:rgb565        mOptions.inPreferredConfig= Bitmap.Config.RGB_565;        //用完需要关闭        mOptions.inJustDecodeBounds=false;        //区域解码器        try {            mDecoder = BitmapRegionDecoder.newInstance(is, false);        } catch (IOException e) {            e.printStackTrace();        }        //去调用onMeasure方法        requestLayout();    }    //第三步 加载图片    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        mViewWidth = getMeasuredWidth();        mViewHeight = getMeasuredHeight();        //绑定图片加载区域        //上边界为0        mRect.top=0;        //左边界为0        mRect.left=0;        //右边界为图片的宽度        mRect.right=mImageWidth;        //下边界为view的高度,在这里相当于手机的高度        mRect.bottom=mViewHeight;    }    //第四步 画图    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if(mDecoder==null){            return;        }        //内存复用        //复用inBitmap这块的内存(每次滚动重新绘制都会复用这块内存,达到内存复用)        mOptions.inBitmap=mBitmap;               mBitmap=mDecoder.decodeRegion(mRect,mOptions);             //计算缩放因子        mScaleX = mViewWidth / (float) mImageWidth;           mScaleY = mViewHeight / (float) mImageHeight;              //得到矩阵缩放        Matrix matrix = new Matrix();        matrix.setScale(mScaleX, mScaleX);//如果matrix.setScale(mScaleX, mScaleY)则图片会在充满在当前的view的x和y轴        canvas.drawBitmap(mBitmap,matrix,null);    }    //第五步 处理点击事件    @Override    public boolean onTouch(View v, MotionEvent event) {        //将Touch事件传递给手势        return true;    }      @Override    public void onShowPress(MotionEvent e) {    }    @Override    public boolean onSingleTapUp(MotionEvent e) {        return false;    }    @Override    public void onLongPress(MotionEvent e) {    }}

大图加载之图片滚动功能实现

  //第五步 处理点击事件    @Override    public boolean onTouch(View v, MotionEvent event) {        //将Touch事件传递给手势        return mGestureDetector.onTouchEvent(event);    }    //第六步 处理手势按下事件    @Override    public boolean onDown(MotionEvent e) {        //如果滑动没有停止就 强制停止        if(!mScroller.isFinished()){            mScroller.forceFinished(true);        }        //将事件进行传递,接收后续事件        //因为在GestureDetector中,onDown方法是用于监听手指按下事件的,如果不返回true消费该事件,        // GestureDetector就不会将后续的事件传递给其他的方法进行处理,        // 包括滑动事件。因此,如果要实现按下手指后进行滑动图片的效果,需要在onDown方法中返回true进行消费。        return true;    }    //第七步 处理滑动事件(手势)指手势的拖动    //e1 开始事件    //e2 即时事件也就是滑动时    @Override    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {        //上下滑动时,直接改变Rect的显示区域        mRect.offset(0,(int) distanceY);//上下滑动只需要改变Y轴        //判断到顶和到底的情况        if(mRect.bottom>mImageHeight){//滑到最底            mRect.bottom=mImageHeight;            mRect.top=mImageHeight-mViewHeight;        }        if(mRect.top<0){//滑到最顶            mRect.top=0;            mRect.bottom=mViewHeight;        }        invalidate();        return true;    }

大图加载之图片惯性滚动功能实现

    //第八步 处理惯性问题(手势)指手势的滑动    @Override    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {        //velocityY表示Y轴的惯性值,startX和startY为滑动的开始位置,minY和maxY为滑动距离的最小值和最大值        mScroller.fling(0,mRect.top,0,(int) -velocityY,0,0,0,mImageHeight-mViewHeight);        return false;    }    //该方法可以获取当前的滚动值    @Override    public void computeScroll() {        super.computeScroll();        //如果没有滚动,直接返回即可        if(mScroller.isFinished()){            return;        }        //如果已经滚动到新位置返回true        if(mScroller.computeScrollOffset()){            mRect.top=mScroller.getCurrY();            mRect.bottom=mRect.top+mViewHeight;//底部边框等于更新的top位置加上        }        invalidate();    }

请添加图片描述

项目地址

github点击查看

来源地址:https://blog.csdn.net/ChenYiRan123456/article/details/131310826

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android大图加载优化方案,避免程序OOM

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android高效加载大图、多图解决方案 有效避免程序OOM

本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文。 http://developer.android.com/training/displaying-bitmaps/index.html高
2022-06-06

Android 加载大图及多图避免程序出现OOM(OutOfMemory)异常

Android 加载大图及多图避免程序出现OOM(OutOfMemory)异常 1、高效加载大图片 我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,这些图片都会大于我们程序所需
2022-06-06

详解Android_性能优化之ViewPager加载成百上千高清大图oom解决方案

一、背景最近做项目需要用到选择图片上传,类似于微信、微博那样的图片选择器,ContentResolver读取本地图片资源并用RecyclerView+Glide加载图片显示就搞定列表的显示,这个没什么大问题,重点是,点击图片进入大图浏览,比
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录