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点击查看

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis入门(5)-set

Redis中set的元素具有无序性与不可重复性 1.sadd key member[member] 添加元素&#xff0c;若元素存在返回0若不存在则添加 sadd DB mysql oracle sadd DB mysql sadd DB db22.smembers key 查看set中所有元素 smembers DB3.sismember key member 判断元素在set中是否存…

GIS 功能模块实现

文章目录 1. GIS 模块流程图2. 网页端地图缓存的实现3. GIS 图形操作功能实现1 &#xff09;地图漫游2 &#xff09;对象删除3 &#xff09;选择复制属性查看 GIS 基本功能模块主要是在表现层开发的&#xff0c;是在OpenLayers 开发框架提供的接口上&#xff0c;通过Geo Server…

智芯MCU软件开发环境搭建

智芯MCU软件开发环境搭建 目录 智芯MCU软件开发环境搭建前言1 软件安装2 编译环境3 烧录环境4 新建工程结束语 前言 智芯科技的MCU主要应用于汽车行业&#xff0c;属于车规级的MCU&#xff0c;目前上市的MCU型号较少&#xff0c;相关资料也不多&#xff0c;所以这里出一期开发…

uniapp实现tab切换可以滚动的效果

实现效果 当 tab 切换的内容很多时&#xff0c;需要用到滚动&#xff0c;希望在点击 tab 的时候可以自动滑动到对应的tab下 知识点 scrollIntoView&#xff1a;该scrollIntoView()方法将调用它的元素滚动到浏览器窗口的可见区域。 语法 element.scrollIntoView&#xff08…

【kubernetes】部署controller-manager与kube-scheduler

前言:二进制部署kubernetes集群在企业应用中扮演着非常重要的角色。无论是集群升级,还是证书设置有效期都非常方便,也是从事云原生相关工作从入门到精通不得不迈过的坎。通过本系列文章,你将从虚拟机准备开始,到使用二进制方式从零到一搭建起安全稳定的高可用kubernetes集…

记录正式环境测试环境【RedHat7编译升级redis7.0.9】--有关报错及解决

记录正式环境&测试环境【RedHat7 编译升级redis7.0.9】--有关报错及解决 &#x1f53b; 一、报错详情1.1 ⛳ 写在前面1.2 ⛳ 报错11.3 ⛳ 报错21.4 ⛳ 安装redis1.5 ⛳ 版本检查 &#x1f53b; 二、⛳ 总结 &#x1f53b; 一、报错详情 1.1 ⛳ 写在前面 &#x1f341; 升级…

王道计算机网络学习笔记(3)——数据链路层

前言 文章中的内容来自B站王道考研计算机网络课程&#xff0c;想要完整学习的可以到B站官方看完整版。 三&#xff1a;数据链路层 3.1&#xff1a;数据链路层功能概述 结点&#xff1a;主机、路由器 链路&#xff1a;网络中两个结点之间的物理通道&#xff0c;链路的传输介…

【DeepLearning】Ubuntu中深度学习环境配置完整流程

Ubuntu中深度学习环境配置完整流程 1 显卡驱动2 cuda3 cuDNN4 torch5 torchvision 1 显卡驱动 支持 cuda 的所有显卡型号: Link 查询显卡型号 lspci -nn | grep VGA即 Vendor ID:Device ID 为 10de:21c4&#xff0c;在浏览器或者 Link 中搜索。 填写显卡信息: Link 选择要下载…

数据结构——快速排序的介绍

快速排序 快速排序是霍尔(Hoare)于1962年提出的一种二叉树结构的交换排序方法。快速排序是一种常用的排序算法&#xff0c;其基本思想是通过选择一个元素作为"基准值"&#xff0c;将待排序序列分割成两个子序列&#xff0c;其中一个子序列的元素都小于等于基准值&am…

SpringBoot集成WebSocket实现消息实时推送(提供Gitee源码)

前言&#xff1a;在最近的工作当中&#xff0c;客户反应需要实时接收消息提醒&#xff0c;这个功能虽然不大&#xff0c;但不过也用到了一些新的技术&#xff0c;于是我这边写一个关于我如何实现这个功能、编写、测试到部署服务器&#xff0c;归纳到这篇博客中进行总结。 目录 …

【计算机网络自顶向下】计算机网络期末自测题(一)

前言 “(学不懂一点) &#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#xff09;&#xff08;扭曲&#xff09;&#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#xff09;&#xff08;扭曲&#xff09;&#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#…

LeetCode·1262. 可被三整除的最大和·贪心

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/greatest-sum-divisible-by-three/solutions/2314049/tan-xin-zhu-shi-chao-ji-xiang-xi-by-xun-r0n76/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得…

vscode 调试

目录 准备 GDB 调试方法 问题 准备 然后点击 文件-打开文件夹&#xff0c;找到创建的代码路径&#xff0c;确定后&#xff0c;在左侧的资源管理器可以看到代码文件。 第一次运行需要安装 c 的扩展&#xff0c;在扩展页面中&#xff0c;安装 C/C 编译注意一定要加上 -g 指令…

Linux tar.xz 格式的文件正确的解压命令

Linux tar.xz 最近下载 Linux kernel&#xff0c;好像最近流行 tar.xz 格式的后缀 对于 xz 后缀的压缩文件&#xff0c;我之前的解压方式是分为两步&#xff1a; xz -d xxx.tar.xz 解压成 xxx.tar 格式文件&#xff0c;然后再 tar xf xxx.tar 解压文件。 这样的操作不仅比较的…

跳槽过去,刚工作三天就被裁是一种怎样的体验

前言 还有谁&#xff1f;刚上三天班就被公司公司的工作不适合我&#xff0c;叫我先提升一下。 后面我也向公司那边讨要了一个说法&#xff0c;我只能说他们那边的说辞让我有些不服气。 现在之所以把这件事在csdn上记录一下&#xff0c;一是记录一下自己的成长轨迹&#xff0…

使用STM32F103的串口实现IAP程序升级功能

使用STM32F103的串口实现IAP程序升级功能 &#x1f3ac;IAP程序烧录全过程演示&#xff1a; ✨这几天折腾IAP升级功能&#xff0c;狂补了很多相关BootLoader相关的知识。本来最想实现IAP升级程序的方式是&#xff0c;基于SPI通讯的SD卡&#xff0c;借助挂载的FatFS文件系统&am…

【计网】第一章 计算机网络概述

文章目录 计算机网络概述一、计算机网络在信息时代中的作用二、互联网概述2.1 互连网概念2.2 网络的网络2.3 互连网基础结构发展的三个阶段2.4 互连网的标准化工作 三、互联网的组成3.1 互联网的边缘部分3.2 互联网的核心部分3.2.1 基础概念3.2.2 电路交换3.2.3 报文交换3.2.4 …

Baumer工业相机堡盟工业相机如何使用新版本NEOAPI SDK控制相机数据流的开启和关闭(C++)

Baumer工业相机堡盟工业相机如何使用新版本NEOAPI SDK控制相机数据流的开启和关闭&#xff08;C&#xff09; Baumer工业相机Baumer工业相机NEOAPI SDK的技术背景Baumer工业相机使用NEOAPISDK控制相机数据流的方式1.引用合适的类文件2.使用NEOAPISDK控制相机数据流的方式2.使用…

macOS Monterey 12.6.7 (21G651) 正式版发布,ISO、IPSW、PKG 下载

macOS Monterey 12.6.7 (21G651) 正式版发布&#xff0c;ISO、IPSW、PKG 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持…

【发布】ChatGLM2-6B:性能大幅提升,8-32k上下文,推理提速42%

自3月14日发布以来&#xff0c; ChatGLM-6B 深受广大开发者喜爱&#xff0c;截至 6 月24日&#xff0c;来自 Huggingface 上的下载量已经超过 300w。 为了更进一步促进大模型开源社区的发展&#xff0c;我们再次升级 ChatGLM-6B&#xff0c;发布 ChatGLM2-6B 。 在主要评估LLM模…