内存优化 | 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


相关文章
|
2月前
|
存储 缓存 安全
C语言中的内存管理与优化技巧
C语言中的内存管理与优化技巧
37 0
|
4月前
|
缓存 算法 Java
C语言中的内存优化及碎片优化
C语言中的内存优化及碎片优化
|
4月前
|
存储 程序员 C语言
【动态内存管理助力程序优化与性能飞升】(下)
【动态内存管理助力程序优化与性能飞升】
|
4月前
|
C语言
【动态内存管理助力程序优化与性能飞升】(中)
【动态内存管理助力程序优化与性能飞升】
|
4月前
|
缓存 监控 算法
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
73 0
|
5月前
|
存储 Java 测试技术
记一次堆内外内存问题的排查和优化
记一次堆内外内存问题的排查和优化
130 0
|
3天前
|
存储 开发者 Python
优化Python代码中的内存占用:实用技巧与最佳实践
本文将介绍如何优化Python代码中的内存占用,通过实用技巧和最佳实践,有效减少内存消耗,提升代码性能和可扩展性。
|
4月前
|
存储 缓存 Java
Java性能优化: 如何减少Java程序的内存占用?
Java性能优化: 如何减少Java程序的内存占用?
248 2
|
29天前
|
缓存 算法 Java
Java内存管理:优化性能和避免内存泄漏的关键技巧
综上所述,通过合适的数据结构选择、资源释放、对象复用、引用管理等技巧,可以优化Java程序的性能并避免内存泄漏问题。
28 5
|
2月前
|
存储 设计模式 缓存
C++享元模式探索:轻松优化内存使用和性能提升之道
C++享元模式探索:轻松优化内存使用和性能提升之道
41 0