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


相关文章
|
4月前
|
机器学习/深度学习 算法 PyTorch
125_训练加速:FlashAttention集成 - 推导注意力优化的独特内存节省
2025年,大型语言模型的训练面临着前所未有的挑战。随着模型参数量和序列长度的不断增加,传统注意力机制的内存瓶颈问题日益突出。FlashAttention作为一种突破性的注意力算法,通过创新的内存访问模式和计算优化,显著提升了训练效率和内存利用。
|
4月前
|
存储 机器学习/深度学习 PyTorch
119_LLM训练的高效内存管理与优化技术:从ZeRO到Flash Attention
大型语言模型(LLM)的训练面临着前所未有的计算和内存挑战。随着模型规模达到数百亿甚至数千亿参数,高效的内存管理成为训练成功的关键因素之一。2025年,LLM训练的内存优化技术已经取得了显著进展,从ZeRO优化器到Flash Attention等创新技术,为训练超大规模模型提供了可能。
|
7月前
|
缓存 固态存储 Windows
如何让内存发挥到最大效能?全面优化指南,提升电脑运行体验
电脑内存使用不合理会导致卡顿,本文教你如何优化内存性能。检查内存容量与主板支持上限,考虑升级或调整配置;关闭后台程序、管理浏览器标签、结束异常进程以释放内存;设置虚拟内存、调整视觉效果、定期重启提升效率;必要时增加内存条、选择高频内存、更换固态硬盘。避免盲目清理内存和依赖大内存忽视其他硬件瓶颈。只需合理设置,无需额外花钱,就能显著提升电脑速度。
|
10月前
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
1920 0
|
7月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
436 4
AI代理内存消耗过大?9种优化策略对比分析
|
7月前
|
存储 人工智能 API
AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
在AI代理系统开发中,上下文工程成为提升系统性能的关键技术。本文探讨了从提示工程到上下文工程的转变,强调其通过为AI系统提供背景信息和工具支持,显著提升智能化程度和实用价值。文章系统分析了上下文工程的理论基础、核心策略(如写入、选择、压缩和隔离),并结合LangChain和LangGraph工具,展示了如何实现上下文工程技术以优化AI代理性能。通过Scratchpad机制、内存管理、RAG系统集成、多代理架构及沙盒环境等技术手段,开发者可以更高效地构建高性能、可扩展的AI系统。
852 0
AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
|
8月前
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
432 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
|
8月前
|
存储 自然语言处理 算法
基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案
本文探讨了在构建对话系统时如何通过一种内存高效算法降低大语言模型(LLM)的Token消耗和运营成本。传统方法中,随着对话深度增加,Token消耗呈指数级增长,导致成本上升。
700 7
基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案
|
6月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
241 0
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
1174 166