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

这篇具有很好参考价值的文章主要介绍了Android大图加载优化方案,避免程序OOM。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

比如我们讲图片分成了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);

android 大图加载,Android项目总结,android
我们要将绿色大小的原图转换成手机屏幕大小的蓝图就需要对图片进行缩放,就需要获取图片大小等相关信息。但问题有来了,我们在获取图片宽高信息的时候不能把整个图片加载进来,不然我们内存复用就没意义,这个时候就用到了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;

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

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

android 大图加载,Android项目总结,android

比如Glide使用的是RGB_565,Picasso使用的是ARGB_8888
当我们对一张图片进行无限的放大,你会发现它是由n个像素点组成,每个像素点都有自己的颜色,比如下面的图片就是有黑色,黄色和浅黄色
android 大图加载,Android项目总结,android
而当缩回去的时候就会发现是一张正常的图片,可以发现图片由像素点组成
android 大图加载,Android项目总结,android
像素点由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();
    }

android 大图加载,Android项目总结,android

项目地址

github点击查看文章来源地址https://www.toymoban.com/news/detail-722673.html

到了这里,关于Android大图加载优化方案,避免程序OOM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 优化广告图加载

    作用是:下载完成后,把图片显示在你广告图上,由于glide有三级缓存机制,因此,最好是提前在接口返回的时候,进行预加载,然后进入到广告启动的流程中的时候就可以直接复用上一次的bitmap,不会浪费相应的时间。因此思路如下: 接口数据---开始预加载广告图---》loa

    2024年01月20日
    浏览(32)
  • Android启动页的加载优化

    现在市面上的app都有个启动页广告或者闪屏广告的过渡页,启动页的作用无非就是2个: 启动页的作用主要有以下几个方面: 提示应用正在加载 :启动页可以作为一个视觉指示,告诉用户应用正在启动和加载。这样用户在等待应用启动的过程中可以得到一个明确的反馈,避免

    2024年02月12日
    浏览(30)
  • 【Angular性能优化】项目8版本加载速度缓慢、白屏时间、首页渲染性能优化方案

    随着业务的代码一点点增加,加上Angular的项目本身就比 vue、react 的重一些,随之而来的启动速度,更改文件后编译速度,以及打包速度也会变慢,于是乎想着优化下我们的项目。 本文章主要说的是 : 打包Angular项目的一些配置,性能优化方面的方案 打包后,用户进入页面的

    2024年04月10日
    浏览(32)
  • Android进阶:ListView性能优化异步加载图片 使滑动效果流畅

    ListView  是一种可以显示一系列项目并能进行滚动显示的 View,每一行的Item可能包含复杂的结构,可能会从网络上获取icon等的一些图标信息,就现在的网络速度要想保持ListView运行的很好滚动流畅是做不到的 所以这里就需要把这些信息利用多线程实现异步加载 实现这样功能的

    2024年02月16日
    浏览(38)
  • Vue首屏加载过慢出现白屏的六种优化方案

    公司业务展示官网开发,构建版本后在测试环境下,发下首屏加载损耗高达几十秒(服务器在国外,所以也导致加载时间变长),于是采用了以下方法来达到提速目的。  路由懒加载和组件懒加载:const One = ()=import(\\\"./one\\\"); 图片懒加载:使用vue-lazyload插件 gizp压缩是一种http请

    2023年04月10日
    浏览(26)
  • Android 屏幕刷新机制与优化方案~

    作者:阿健君 基本概念 刷新率 :屏幕每秒刷新的次数,单位是 Hz,例如 60Hz,刷新率取决于硬件的固定参数。 帧率 :GPU 在一秒内绘制操作的帧数,单位是 fps。Android 采用的是 60fps,即每秒 GPU 最多绘制 60 帧画面,帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作

    2024年02月07日
    浏览(27)
  • 图片模块封装:Glide高级使用+使用设计模式图片框架封装+Bitmap尺寸压缩和质量压缩+Bitmap加载大图长图

    框架设计过程中对于对于架构要求高内聚低耦合,图片加载框架中引 入三方框架提示开发效率,对于技术选型后的方案可能后面需求的变更原三方 sdk无法满足当前业务需求,故而需要更换原有sdk,为了将更改降到最低,所 有前面设计图片加载框架时要考虑当前这个风险点

    2024年02月06日
    浏览(37)
  • uni-app小程序加载图片优化

    场景: 较大的图片加载很慢,会出现较长时间的白屏,体验感差。   解决方案 :一进入页面,图片未加载成功前,进行loading…;图片加载完成后会触发@load事件;

    2024年02月11日
    浏览(42)
  • 小程序getStorageSync、setStorageSync数据缓存,优化页面加载

    1、将数据缓存到本地 : 同步缓存:wx.setStorageSync() 异步缓存:wx.setStorage() 2、从本地缓存获取数据: 同步:wx.getStorageSync() 异步:wx.getStorage()      

    2024年02月12日
    浏览(50)
  • 记录--Loading 用户体验 - 加载时避免闪烁

    在切换详情页中有这么一个场景,点击上一条,会显示上一条的详情页,同理,点击下一条,会显示下一条的详情页。 伪代码如下所示: 我们定义了一个  switcher  模版, 用户点击上一条、下一条时调用  goToPreOrNext  方法。该页面通过  loadingDone  状态判断是否展示加载效

    2024年02月13日
    浏览(23)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包