内存优化 | Bitmap优化

简介: 在内存优化中,优化 Bitmap 占用的内存效果最为明显,在 Android 里面,大部分 OOM,都是 bitmap 占用资源过大导致的,那么问题来了如何防止 bitmap 占用资源过大导致 OOM?Android 系统何时会发生 OOM?怎样搭建线上线下一体化内存监控体系?Drump 文件过大,我们线上如何查看?线下监控那些工具你会用吗?关于 Native 层的内存泄漏该如何解决?图片监控你做过哪些努力?内存抖动为什么会引起 OOM?内存监控里面采集方式有哪些?看完本文,希望可以以本文为索引,然后依次排雷解决上述问题,这样你也是这个领域的专家了

改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注音视频和 APM ,涵盖各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!

在内存优化中,优化 Bitmap 占用的内存效果最为明显,在 Android 里面,大部分 OOM,都是 bitmap 占用资源过大导致的,那么问题来了

  • 如何防止 bitmap 占用资源过大导致 OOM?
  • Android 系统何时会发生 OOM?
  • 怎样搭建线上线下一体化内存监控体系?
  • Drump 文件过大,我们线上如何查看?
  • 线下监控那些工具你会用吗?
  • 关于 Native 层的内存泄漏该如何解决?
  • 图片监控你做过哪些努力?
  • 内存抖动为什么会引起 OOM?
  • 内存监控里面采集方式有哪些?

看完本文,希望可以以本文为索引,然后依次排雷解决上述问题,这样你也是这个领域的专家了

一. 如何得到 bitmap 对象?

Bitmap 是 Android 系统中的图像处理中最重要类之一。Bitmap 可以获取图像文件信息,对图像进行剪切、旋转、缩放,压缩等操作,并可以以指定格式保存图像文件。

如何创建 Bitmap 对象 创建 Bitmap 对象有两种方式,分别为:

Bitmap.createBitmap() 和 BitmapFactory 的 decode 系列静态方法创建 Bitmap 对象。

下面我们主要介绍 BitmapFactory 的 decode 方式创建 Bitmap 对象。

方式 1. decodeFile 从文件系统中加载
  • 通过 Intent 打开本地图片或照片
  • 根据 uri 获取图片的路径
  • 根据路径解析
 Bitmap bm = BitmapFactory.decodeFile(path);
方式 2. decodeResource 以 R.drawable.xxx 的形式从本地资源中加载
Bitmap bm = BitmapFactory.decodeResource(getResources(),
R.drawable.icon);
方式 3 decodeStream 从输入流加载
Bitmap bm = BitmapFactory.decodeStream(stream);
方式 4 decodeByteArray 从字节数组中加载
Bitmap bm = BitmapFactory.decodeByteArray
(myByte,0,myByte.length);

二. BitmapFactory.Options

合理的配置 BitmapFactory.Options 参数对防止内存溢出,节省内存开销,系统更流畅至关重要,下面我们就来聊聊 BitmapFactory.Options 参数介绍吧

2.1 BitmapFactory.Options 参数介绍

字段 参数名称 备注
inSampleSize 采样大小 用于将图片缩小加载出来的,以免占用太大内存,适合缩略图
inJustDecodeBounds 是否解析图片的原始宽高信息 当 inJustDecodeBounds 为 true 时,执行 decodexxx 方法时,
BitmapFactory 只会解析图片的原始宽高信息,并不会真正的加载图片
inPreferredConfig 配置图片解码方式 对应的类型 Bitmap.Config。如果非 null,
则会使用它来解码图片。默认值为是 Bitmap.Config.ARGB_8888
inBitmap 在图片加载的时候可以使用之前已经创建了的 Bitmap,以便节省内存 在 Android 3.0 开始引入了 inBitmap 设置,通过设置这个参数,在图片加载的时候可以使用之前已经创建了的 Bitmap,以便节省内存,避免再次创建一个 Bitmap。
在 Android4.4,新增了允许 inBitmap 设置的图片与需要加载的图片的大小不同的情况,只要 inBitmap 的图片比当前需要加载的图片大就好了。

2.2 如何进行图片尺寸压缩

通过 BitmapFactory.Options 的这些参数,我们就可以按一定的采样率来加载缩小后的图片,然后在 ImageView 中使用缩小的图片这样就会降低内存占用避免【OOM】,提高了 Bitamp 加载时的性能。

这其实就是我们常说的图片尺寸压缩。尺寸压缩是压缩图片的像素

1681570603992.png

1681570629984.png

2.3 一张图片所占内存大小的计算方式

一张图片所占内存大小的计算方式: 图片类型*宽*高,通过改变三个值减小图片所占的内存,防止 OOM,当然这种方式可能会使图片失真 。

三. 如何计算 Bitmap 所占内存大小

计算 Bitmap 所占内存大小 有两种,一种是 获取可用 bitmap 字节大小 bitmap.getByteCount(),另外一种是 获取系统分配的 Bitmap 内存大小 bitmap.getAllocationByteCount()

四. Bitmap 在内存中的缓存管理

4.1 构建单例的缓存工具类 ImageCache

public class ImageCache {
    private static ImageCache instance;
    /**
     * 单例类
     *
     * @return
     */
    public static ImageCache getInstance() {
        if (instance == null) {
            synchronized (ImageCache.class) {
                if (instance == null) {
                    instance = new ImageCache();
                }
            }
        }
        return instance;
     }
  }

4.2 利用软引用队列存储 Bitmap,磁盘缓存和内存缓存用的是最少使用算法

    private LruCache<String, Bitmap> lruCache;
    private Set<WeakReference<Bitmap>> reusablePool;
    private DiskLruCache diskLruCache;

4.3 LruCache 算法存储 Bitmap

首先我们需要计算我们 Android 手机可用内存大小,然后将内存分配给我们的 LruCache 最大缓存空间 LruCache 算法不同版本间做了一系列的变更

  • Android 3.0 bitmap 缓存在 native 层
  • Android 8.0 bitmap 缓存在 java 层
  • Android 8.0 bitmap 缓存在 native 层

我们统计当前 Bitmap 使用了多少内存大小是使用 sizeOf,将 getAllocationByteCount 返回给 lruCache 即可

1681570718205.png1681570740034.png

如果最少使用的缓存被清除时,我们要把可用缓存塞到我们的可重用缓存池

队列的获取是通过线程池来操作的,当前线程如果没有被打断要从软引用队列里面移除并且回收 Bitmap,这样能保证队列里面的 Bitmap 永远是最新的 Bitmap 对象

1681570784866.png

4.4 获取采样大小 inSampleSize

获取采样大小相对比较简单,我们只要迭代我们的可重用缓存

1681570814459.png

然后检查 Bitmap 是否满足以下条件

  • 3.0 之前不能复用
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
        }
  • 3.0-4.4 宽高一样,inSampleSize = 1
    if (inSampleSize > 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }
  • 4.4 只要小于等于就行了
int byteCount = w * h * getBytesPerPixel(bitmap.getConfig());
// 图片内存 系统分配内存
return byteCount <= bitmap.getAllocationByteCount();

4.5 内存缓存存放

    public void putBitmap2Memory(String key, Bitmap bitmap) {
        lruCache.put(key, bitmap);
    }

4.6 内存缓存获取

 public Bitmap getBitmapFromMemory(String key) {
        return lruCache.get(key);
    }

4.7 磁盘缓存存放

1681570950097.png

4.8 磁盘缓存获取

1681570982043.png

4.9 像素格式计算每一个像素占用多少字节

之前说过了 ARGB_8888 其实是占 4 个字节的,其余都是占 2 个字节,所以通过像素格式计算每一个像素占用多少字节是这么计算的

1681571017919.png

五. Bitmap 的缓存

5.1 LRUCache 算法
1681571062852.png


1681571095336.png

1681571146134.png

1681571178934.png

六. Bitmap 内存优化

7.1 磁盘缓存

概念梳理

缓存的内容直接写到磁盘中,存放的时间久,不会随着 APP 进程死亡而消失。如果磁盘缓存中没有,就会从网络中或者 SD 卡中获取资源了。同样使用的是 Lru 算法。

封装 DiskLruCache

因为 DiskLruCache 没有提供熟悉的 get、put 方法,所以我们对 DiskLruCache 进行二次封装,提供 get、put 方法给外界调用

1681571220402.png

1681571262394.png

1681571296096.png

7.2 长图加载

7.2.1 长图加载中需要注意的地方

长图加载,我们实现的是 GestureDetector.OnGestureListener 和 View.OnTouchListener 手势监听

然后将 局部 View 以 Rect 方式塞到我们的 BitmapRegionDecoder 参数里面

下面我们就实现一下我们的长图加载的自定义 View 吧

7.2.1 实现 GestureDetector.OnGestureListener, View.OnTouchListener 接口
7.2.2 onMeasure

在 onMeasure 通过缩放因子确定矩阵的宽高

1681571344406.png

7.2.3 onScroll

我们再手势滑动过程中,要不断的确定滑动区域的宽高,然后进行重新绘制工作

1681571378940.png

View 的 onTouch 直接交给 长按滑动事件 mGestureDetector 处理

1681571590972.png

7.2.4 onDraw

在 onDraw 方法里面,我们进行 inBitmap 初始化配置,再将 mRect 设置在 BitmapRegionDecoder 即可 ,然后使用矩阵进行缩放,最后调用 canvas.drawBitmap 进行 View 的绘制,这样就能完美实现局部刷新效果

1681571629101.png

7.2.5 computeScroll

完成滑动,我们就可以确定最终 React 矩阵参数了,然后进行相应重绘即可

1681571690843.png

八. Bitmap.Config 配置

1681571721147.png

九. Bitmap 大小缩放

不同的项目 Bitmap 缩放大小算法可能不太一样,这里我介绍一下通用的缩放算法,以便于对 Bitmap 知识点扫盲,大型项目一般不这样做,感兴趣可以看一下常用的图片压缩框架源码

  • 方式一
  • 1681571767548.png
  • 方式二
  • 1681571795947.png


相关文章
|
1月前
|
存储 缓存 监控
|
1月前
|
缓存 算法 Java
Java中的内存管理:理解与优化
【10月更文挑战第6天】 在Java编程中,内存管理是一个至关重要的主题。本文将深入探讨Java内存模型及其垃圾回收机制,并分享一些优化内存使用的策略和最佳实践。通过掌握这些知识,您可以提高Java应用的性能和稳定性。
46 4
|
11天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
33 6
|
11天前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能
|
11天前
|
存储 监控 Java
深入理解计算机内存管理:优化策略与实践
深入理解计算机内存管理:优化策略与实践
|
23天前
|
存储 JavaScript 前端开发
如何优化代码以避免闭包引起的内存泄露
本文介绍了闭包引起内存泄露的原因,并提供了几种优化代码的策略,帮助开发者有效避免内存泄露问题,提升应用性能。
|
24天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
数据处理 Python
如何优化Python读取大文件的内存占用与性能
如何优化Python读取大文件的内存占用与性能
117 0
|
2月前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
62 2