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

news2024/11/15 11:00:13

我们在编写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点击查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信号与系统复习笔记——信号与系统的时域和频域特性

信号与系统复习笔记——信号与系统的时域和频域特性 傅里叶变换的模和相位表示 一般来说&#xff0c;傅里叶变换的结果是复数&#xff0c;所以能够使用模和相位来表示&#xff0c;具体的有&#xff1a; X ( j ω ) ∣ X ( j ω ) ∣ e j ∡ X ( j ω ) X(j\omega) |X(j\ome…

浅尝Transformer和LLM

文章目录 TransformerTransformer的衍生BERTPre-trainingBERT与其他方法的关系怎么用BERT做生成式任务&#xff1f; GPTPre-trainingFine-Tuning Transformer工具开源库特点 LLM系列推理服务 大语言模型势不可挡啊。 哲学上来说&#xff0c;语言就是我们的一切&#xff0c;语言…

MySQL 高级(进阶) SQL 语句

创建两个表格 use awsl; create table location (Region char(20),Store_Name char(20)); insert into location values(East,Boston); insert into location values(East,New York); insert into location values(West,Los Angeles); insert into location values(West,Houst…

JMU20 软件工程经济学 复习总结

文章目录 碎碎念0. 基准收益率 i1. 现金流量图2. 净现值 NPV&#xff0c;内部收益率 IRR3. 单利&#xff0c;复利计算4. 等额年金NAV5. 动态回收期 P t ′ P_t Pt′​6. 固定资产折旧 [书P44]7. 增值税8. 软件行业增值税的即征即退9. 利息备付率 ICR&#xff0c;偿债备付率 DSC…

C语言之分支与循环

一、语句 什么是语句 C语言中&#xff0c;由一个分号&#xff08; &#xff1b;&#xff09;隔开的即为一条语句。 这些都是语句&#xff1a; &#xff08; 一行里只有 &#xff1b;的语句&#xff0c;我们称其为 “空语句” &#xff09; int main(void) {printf("hel…

UVM1.2究竟在UVM1.1上做了哪些升级

想必大家平时也没有很注意UVM1.1版本和UVM1.2版本的不同之处&#xff0c;只有在用一些以前UVM1.1能支持的功能&#xff0c;到了UVM1.2却出现编译报错&#xff0c;找不到对应的变量或者函数或者类的时候&#xff0c;才意识到这两个版本的差异。笔者也是遇到了1个打印问题&#x…

利用Django的视图类TemplateView将模板、视图与模板变量方便快速的整合在一起

TemplateView是Django提供的通用视图类之一&#xff0c;它允许您在不编写任何Python代码的情况下将模板与视图关联起来。下面是关于TemplateView类的一些介绍&#xff1a; 渲染模板&#xff1a;TemplateView负责渲染指定的模板并返回生成的HTML响应。您只需提供模板名称或路径即…

华为OD机试真题B卷 Java 实现【删除字符串中出现次数最少的字符】,附详细解题思路

一、题目描述 删除字符串中出现次数最少的字符&#xff0c;如果多个字符出现次数一样则都删除。 二、输入描述 一个字符串。 三、输出描述 删除字符串中出现次数最少的字符&#xff0c;如果多个字符出现次数一样则都删除&#xff0c;如果都被删除 则换为empty。 四、解题…

【安装lnmp实操】

文章目录 一、安装Nginx服务1.安装依赖包2、创建运行用户3、编译安装4、优化路径5、添加 Nginx 系统服务 二、【安装 MySQL 服务】1、安装Mysql环境依赖包2、创建运行用户3、编译安装4、修改mysql 配置文件5、更改mysql安装目录和配置文件的属主属组6、设置路径环境变量7、初始…

MySQL数据库高级SQL语句(图文详解!)

一、MySQL高级语句 1.SELECT 2.DISTINST 3.WHERE 4.AND|OR ​5.IN 6.BETWEEN 7.通配符 8.LIKE 9.ORDER BY 10.函数 &#xff08;1&#xff09;数学函数 &#xff08;2&#xff09;聚合函数 &#xff08;3&#xff09;字符串函数 11.GROUP BY 12.HAVING 13.别名 14.子查询 …

团体程序设计天梯赛-练习集L1篇③

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

插件 - 插件机制触手可及

文章目录 Pre方案流程图优点缺点Code小结Pre 插件 - 一份配置,离插件机制只有一步之遥 中是不是有依赖, 我不想依赖 ,肿么办? 方案 应用A定义服务接口,约定插件实现的功能规范。应用B,C,D等各自实现该接口,并打包成jar包,放置在应用A约定的读取目录下。应用A在运行时,读取…

MATLAB 动画的制作与保存

matlab有三种方法来创建动画&#xff1a; ①以质点运动轨迹的方式来创建动画 theta0:0.001:2*pi; %定义圆的半径 r10; %生成圆上各点的横纵坐标 xr*cos(theta); yr*sin(theta); comet(x,y); 生成的动画效果如下 &#xff08;其中的某一步&#xff09; 最终结果如下 comet(x,…

怎么将存入此电脑中的图片放入电脑D盘

先找到图片的目录&#xff0c; 目录为&#xff1a;xxx 然后 现在图片目录就移动到D盘了

算法设计 - KMP算法

字符串模式匹配问题 假设有两个字符串S&#xff0c;T&#xff0c;其中S是主串&#xff08;正文串&#xff09;&#xff0c;T为子串&#xff08;模式串&#xff09;&#xff0c; 我们需要在S中查找与T相匹配的子串&#xff0c;如果成功找到&#xff0c;则返回匹配的子串第一个…

华为OD机试真题 JavaScript 实现【太阳能板最大面积】【2022Q4 100分】,附详细解题思路

一、题目描述 给航天器一侧加装长方形或正方形的太阳能板&#xff08;图中的红色斜线区域&#xff09;&#xff0c;需要先安装两个支柱&#xff08;图中的黑色竖条&#xff09;&#xff0c;再在支柱的中间部分固定太阳能板。 但航天器不同位置的支柱长度不同&#xff0c;太阳…

logback日志框架基本知识

本文来说下logback日志框架基本知识 文章目录 概述logback简介SpringBoot对logback的支持SpringBoot的集成 概述 Spring Boot已经将logback做为默认集成的日志框架&#xff0c;全面了解学习是必然了。曾经log4j是流行的日志框架&#xff0c;现在已被它的继任者logback替代&…

第三节 折线图

文章目录 折线图1.1 numpy介绍1.2 预测趋势1.3 折线图流程工具 Pandas1.3.1 读取并生成 CSV1.3.2 输出列数据1.3.3 画折线图1.5 添加横纵坐标,标题 屏幕属性 1.4 画双折线图1.5 扩展:做三线图 折线图 1.1 numpy介绍 上节课我们学了柱状图, 通过柱状图可以了解, numpy主要是科…

部署lnmp框架nginx在上一章节

目录 一.安装mysql服务 1.下载mysql和模块boost并解压包到/opt目录下 2.创建运行用户 3.进入mysql包目录下面进行编译安装 4.创建普通用户管理mysql useradd -s /sbin/nologin mysqlchown -R mysql:mysql /usr/local/mysql/ 5.修改配置文件 6.设置环境变量&#xff0c;申…

等约束二次规划中的特征分解研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…