内存优化 | 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月前
|
存储 缓存 监控
|
1月前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
2月前
|
缓存 算法 Java
Java中的内存管理:理解与优化
【10月更文挑战第6天】 在Java编程中,内存管理是一个至关重要的主题。本文将深入探讨Java内存模型及其垃圾回收机制,并分享一些优化内存使用的策略和最佳实践。通过掌握这些知识,您可以提高Java应用的性能和稳定性。
61 4
|
1月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
124 62
|
29天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
70 31
|
27天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
67 7
|
27天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
53 5
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
52 6
|
1月前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能

热门文章

最新文章