当前位置: 首页 > news >正文

Android Bitmap屏显后Native堆占用以及GPU GraphicBuffer分布统计量

Android Bitmap屏显后Native堆占用以及GPU GraphicBuffer分布统计量

摘要:图片显示后内存占用主要由解码后的像素尺寸决定,而非原始文件大小。以400x400 ARGB_8888格式为例,内存分布为:1)Native堆存储像素数据约0.64MB;2)GPU纹理内存约0.64MB;3)少量Java对象和元数据。稳定显示后总增量约1.28MB。硬件加速下,CPU和GPU各存一份数据,其中Native堆归入内存统计,GPU内存计入Graphics/GL跟踪项。关键点:解码时应直接缩小尺寸,避免先解码大图再缩放;ImageView的缩放不会减少实际内存占用;使用HARDWARE配置可减少CPU副本但限制操作。

URI 原始文件大小 ≠ Bitmap 内存大小 Bitmap 内存大小 ≈ 宽 × 高 × 每像素字节数 ImageView.setImageBitmap() 本身通常不复制 Bitmap 像素 真正显示到屏幕时,硬件加速渲染会把 Bitmap 上传成 GPU texture 所以稳定显示后,通常会同时存在: 1 份 CPU/native 侧 Bitmap 像素内存 1 份 GPU 侧纹理内存 若干 Java/native 小对象和渲染缓存

1. 先算 Bitmap 本体大小

假设原图是:

3000 x 4000

通过BitmapFactory按照:

400 x 400

解码后形成 Bitmap。

如果最终 Bitmap 是常见的:

Bitmap.Config.ARGB_8888

那么每个像素 4 字节:

400 × 400 × 4 = 640,000 bytes

换算:

约 625 KiB 约 0.61 MiB 约 0.64 MB

所以这个 400x400 的 ARGB_8888 Bitmap 本体不是 3MB,而是约:

0.64 MB

如果是其他格式:

Bitmap Config

每像素字节数

400x400 Bitmap 大小

ARGB_8888

4 bytes

640,000 bytes,约 0.61 MiB

RGB_565

2 bytes

320,000 bytes,约 0.31 MiB

ALPHA_8

1 byte

160,000 bytes,约 0.15 MiB

RGBA_F16

8 bytes

1,280,000 bytes,约 1.22 MiB

下面默认按最常见的:

ARGB_8888

来分析。

2. 稳定显示后,总体大概占多少?

在正常 Android 硬件加速渲染场景下:

imageView.setImageBitmap(bitmap)

然后这个 ImageView 被绘制到屏幕上,通常会有:

CPU/native 侧 Bitmap 像素内存:约 0.64 MB GPU 侧 texture 纹理内存:约 0.64 MB Java 对象/Drawable/View 引用:很小,通常 KB 级 RenderNode/DisplayList/Skia/HWUI 元数据:小量 native

所以这个单张 400x400 ARGB_8888 图片的稳定态增量内存大致是:

约 0.64 MB native Bitmap + 约 0.64 MB GPU texture + 少量 Java/native 元数据 ≈ 1.28 MB 左右

更准确一点:

约 1.2 ~ 1.5 MB

如果考虑 GPU 对齐、纹理缓存、allocator 开销,可能略高。

3. 分布拆解

3.1 URI 原始媒体文件

原始文件是:

3000 x 4000

但如果它是 JPEG/PNG/WebP 文件,磁盘上的文件大小是压缩后的大小。

例如:

3000x4000 JPEG 可能是 1MB、3MB、8MB

这不等于 Bitmap 内存。URI 指向的文件通常在:

/data/media/... MediaStore ContentProvider

解码时会通过:

ContentResolver.openInputStream(uri) BitmapFactory.decodeStream(...)

读取数据。这部分内存包括:

InputStream buffer Binder/ContentProvider 相关小对象 Skia 解码临时 buffer 系统 page cache

其中:

文件 page cache 通常不算作 App 私有 Java/Native Heap

如果 App 没有 mmap 整个文件,而只是流式读取,那么原始压缩文件本身通常不会以完整大小记到 App PSS 里。

3.2 BitmapFactory 解码阶段

BitmapFactory底层主要走 Skia 解码。

解码过程中可能有:

Java 层 Bitmap 对象 native 层 SkBitmap / PixelRef / pixel buffer decoder 临时 buffer InputStream buffer 颜色空间 / ICC profile / row buffer 等临时 native 内存

最终产物是一个 Bitmap。在现代 Android 上,Bitmap 的像素内存主要在:

native heap

而不是 Java heap。

Java 层的Bitmap对象只是一个 wrapper,比较小。

最终 Bitmap 像素大小:

rowBytes × height

通常近似:

width × height × bytesPerPixel

例子:

400 × 400 × 4 = 640,000 bytes

所以 native 侧稳定保留:

约 0.64 MB

3.3 ImageView.setImageBitmap 阶段

调用:

imageView.setImageBitmap(bitmap)

内部大致是:

ImageView 持有这个 Bitmap 一般会包装成 BitmapDrawable 触发 invalidate 等待下一帧绘制

重要点:

setImageBitmap()本身通常不会复制一份 Bitmap 像素数据。

所以在这个阶段,主要增加的是:

ImageView 对 Bitmap 的引用 BitmapDrawable 对象 少量 Java/native 状态

这部分一般是:

KB 级别

不是 MB 级。

3.4 真正绘制到屏幕阶段

当 ViewRoot 开始 draw,硬件加速开启时:

ImageView.draw() BitmapDrawable.draw() Canvas.drawBitmap() HWUI / RenderThread / Skia GPU backend

此时系统会把 CPU/native 侧 Bitmap 上传到 GPU,形成 GPU texture。

对于 400x400 ARGB_8888:

GPU texture 大小 ≈ 400 × 400 × 4 = 640,000 bytes

也就是:

约 0.64 MB

实际可能因为:

纹理行对齐 tile 对齐 GPU allocator 对齐 驱动内部 metadata 是否生成 mipmap 纹理缓存策略

略微高一点。

但一般可粗略按:

GPU texture ≈ Bitmap 像素大小

来估算。

4. 所以最终这条链路的内存分布大概是?

以:

400x400 ARGB_8888

为例,稳定显示后:

部分

大小估算

归属

Java

Bitmap

对象

很小,几十到几百 bytes 级

Java Heap

ImageView

/

BitmapDrawable

/ 引用

KB 级

Java Heap

Bitmap 像素内存

约 0.64 MB

Native Heap

Skia / decoder / RenderNode 元数据

KB 到几十 KB

Native Heap / Private Other

GPU texture

约 0.64 MB

Graphics / GL mtrack / GPU memory

Window Surface buffer

不是这张图片独占,通常已存在

Graphics

原始 URI 文件 page cache

通常不算 App 私有 heap

System page cache / file cache

所以单张图片的增量稳定态可以粗略写成:

Native Heap: Bitmap pixels ≈ 0.64 MB GPU / Graphics: Texture ≈ 0.64 MB Java Heap: 很小,通常 KB 级 Total incremental: ≈ 1.28 MB + 少量对象/对齐/缓存开销

5. 为什么会有 native 一份,又有 GPU 一份?

因为 CPU 和 GPU 使用的内存空间不同。

5.1 Bitmap 解码后的内存

BitmapFactory解码出来的是 CPU 可访问的像素数据。

App 可能会对它做:

bitmap.getPixel() bitmap.copy() canvas.drawBitmap()

这类操作需要 CPU 可读的像素内存。这部分在现代 Android 上通常位于:

native heap

5.2 屏幕显示需要 GPU texture

Android 正常 UI 渲染是硬件加速的。

ImageView 里的 Bitmap 最终需要被 GPU 采样、缩放、混合、合成。

GPU 不能直接高效使用普通 native heap 中的像素内存,通常需要上传成:

GPU texture

所以会形成第二份:

GPU 侧纹理内存

于是稳定显示时常见就是:

CPU/native Bitmap 一份 GPU texture 一份

这就是为什么在手机屏幕上肉眼看到一个图片,实际内存往往接近:

Bitmap size × 2

甚至更多。

6. ImageView 缩放会不会改变 Bitmap 内存?

一般不会。

例如:

Bitmap 是 400x400 ImageView 显示成 100x100

Bitmap native 内存仍然是:

400 × 400 × 4

GPU texture 通常也仍然是:

400 × 400 × 4

ImageView 的scaleType只是改变绘制矩阵,不会自动减少 Bitmap 内存。

如果希望减少内存,需要在解码阶段就降低尺寸,例如:

decode 到 100x100

而不是:

decode 到 400x400,然后显示成 100x100

7. 原图 3000x4000 对最终内存有什么影响?

如果确实最终只解码成:

400x400

那么最终稳定态主要由:

400x400

决定,而不是:

3000x4000

但是有几个例外。

7.1 如果中间曾经解码过原图

如果代码先解码完整原图:

3000 × 4000 × 4 = 48,000,000 bytes

约:

45.8 MiB

然后再 scale 到 400x400,那么短时间内可能存在:

原图 Bitmap 约 45.8 MiB + 缩略图 Bitmap 约 0.64 MB + GPU texture 约 0.64 MB

这会非常浪费。

所以图库里必须避免:

先 decode full size,再 scale

而应该用:

inSampleSize ImageDecoder targetSize 缩略图 API

7.2 BitmapFactory 不一定能直接任意解码成 400x400

传统BitmapFactory.Options.inSampleSize通常按采样倍数缩小,尤其历史上常见是 2 的幂采样。

例如原图:

3000x4000

inSampleSize=8可能得到大约:

375x500

如果最终需要 400x400,可能还会有 crop/scale。

如果内部流程是:

decode sampled bitmap 375x500 再 createScaledBitmap/crop 到 400x400

那么峰值内存可能是:

sampled bitmap + final bitmap + GPU texture

但稳定态如果只保留 final 400x400,则还是前面的估算。

8. OS 会把这些内存统计到哪里?

这部分要看使用什么工具。

下面以常见的:

adb shell dumpsys meminfo <package>

和系统 PSS 统计为准。

8.1 Java Heap

归属内容:

Bitmap Java wrapper ImageView BitmapDrawable Drawable state Uri/String/Options 等 Java 对象 临时 byte[] buffer,如果你自己分配了

这部分很小。

通常不会看到:

Java Heap 增加 0.64 MB

因为 Bitmap 像素不在 Java byte[] 里。

8.2 Native Heap

归属内容:

Bitmap 像素内存 Skia native 对象 BitmapFactory decoder 临时 native buffer malloc 分配

对于 400x400 ARGB_8888,稳定态最主要是:

Bitmap pixels ≈ 0.64 MB

dumpsys meminfo里通常会进入:

Native Heap

或者厂商/Android 版本会在 Bitmap 相关统计里显示 Bitmap 占用。

所以可能看到:

Native Heap PSS / Private Dirty 上升 Bitmap memory 上升

8.3 Graphics / GPU

归属内容:

GPU texture HWUI texture cache RenderThread/HWUI caches EGL/GL mtrack GraphicBuffer / Surface buffer

这部分在dumpsys meminfo里通常体现为:

Graphics EGL mtrack GL mtrack Gfx dev

不同平台名字会略有差异。 400x400 ARGB_8888 Bitmap 显示后,可能让 Graphics 增加约:

0.64 MB

但实际可能更高,因为:

纹理对齐 HWUI 缓存 Surface buffer Render target buffer 厂商 GPU 驱动统计方式

8.4 Surface / Window Buffer

这个要特别说明。

一个 App 窗口本身通常有 Surface buffer,例如:

1080 × 2400 × 4 bytes ≈ 9.9 MB

如果双缓冲/三缓冲:

约 20 MB ~ 30 MB

这部分是整个窗口的显示缓冲,不是某一张 ImageView 图片独占。

如果你的 App 已经在前台显示,这些 Surface buffer 本来就存在。
此时新增一张图片,主要增量是:

GPU texture

不是重新增加整个窗口 buffer。

但如果这是 App 第一次启动、第一次创建窗口,那么你会同时看到:

Surface buffer / Graphics 明显增加

这部分不能全部归因于这张 Bitmap。

8.5 PSS 统计

PSS 的统计会分散在:

Java Heap PSS Native Heap PSS Graphics / GL / EGL mtrack Private Other System

需要注意:

GPU 内存不一定都能在/proc/<pid>/smaps的 PSS 里完整体现,很多时候依赖 memtrack HAL,以GraphicsGL mtrackEGL mtrack等形式展示。

所以在dumpsys meminfo里看到的总 PSS,可能和真实 GPU 物理占用并不完全一一对应。

9. 一张图总结

场景:

URI 原图:3000x4000 解码结果:400x400 ARGB_8888 Bitmap ImageView.setImageBitmap() 最终显示

稳定态大致是:

Java Heap: Bitmap wrapper + ImageView/Drawable ≈ KB 级 Native Heap: Bitmap pixel buffer 400 × 400 × 4 ≈ 0.64 MB GPU / Graphics: GPU texture 400 × 400 × 4 ≈ 0.64 MB Total incremental: ≈ 1.28 MB + 少量额外开销

如果用公式:

CPU Bitmap = width × height × bytesPerPixel GPU Texture ≈ width × height × bytesPerPixel Total ≈ 2 × width × height × bytesPerPixel

对于 400x400 ARGB_8888:

Total ≈ 2 × 400 × 400 × 4 ≈ 1,280,000 bytes ≈ 1.22 MiB

10. 特殊情况:Bitmap.Config.HARDWARE

如果使用的是:

Bitmap.Config.HARDWARE

或者某些图片库使用了 hardware bitmap,那么情况会变。

Hardware Bitmap 的像素数据更偏向 GPU/GraphicBuffer 侧,CPU 通常不可直接访问。

特点:

Native Heap 中可能不会有完整 CPU 像素副本 Graphics / GPU / dmabuf / GraphicBuffer 占用会上升 不能直接修改像素 部分 Canvas 操作受限

这种情况下可能更接近:

GPU/GraphicBuffer 一份 Native/Java wrapper 少量

而不是普通 software Bitmap 的:

native 一份 + GPU 一份

但普通BitmapFactory.decodeStream()默认更常见还是 software Bitmap,尤其没有明确设置Bitmap.Config.HARDWARE时。

12. 对内存问题的实际意义

如果上层应用同时保留很多 Bitmap,例如:

120 张首批缩略图 每张 400x400 ARGB_8888

粗略估算:

单张 native bitmap ≈ 0.64 MB 单张 GPU texture ≈ 0.64 MB 单张合计 ≈ 1.28 MB 120 张 ≈ 153.6 MB

当然实际图库缩略图可能小于 400x400,图片库也有缓存淘汰,但这个量级解释了为什么:

Native Heap Graphics PSS

会随着图片展示明显上涨。

总结:

在现代 Android 上,一个400x400 ARGB_8888Bitmap 通过ImageView.setImageBitmap()显示后,稳定态通常大约占0.64MB Native Bitmap + 0.64MB GPU Texture,合计约1.28MB,再加少量 Java/native 元数据。Native Bitmap 通常归到Native Heap/Bitmap,GPU texture 和 Surface/GraphicBuffer 归到Graphics / GL mtrack / EGL mtrack / Gfx dev等统计中。原始 3000x4000 文件大小本身不决定最终 Bitmap 内存,真正决定的是解码后的像素尺寸和像素格式。

AI智能网站

http://www.cnnetsun.cn/news/3293286.html

相关文章:

  • openeuler/sra_tensorflow_adapter完全指南:从架构到部署的5个关键步骤
  • 一个程序员副业新方向:用AI做漫剧,3亿播放量,单人变现70万
  • Cesium 智慧城市着色器教程
  • 项目开发计划 V1.0 编制实战:3个核心模板与5步滚动式迭代法
  • AI个性化学习系统架构与实现:从知识图谱到推荐算法
  • LR(0) 分析表构造实战:Python 实现 5 步自动生成算法(附完整代码)
  • STM32数字控制DC-DC升压转换系统设计与优化
  • Neo4j AuraDB 与 Docker 社区版部署:3 种方案成本与性能实测
  • 如何高效使用openeuler/integration-test?10分钟上手核心测试用例
  • KASandbox核心组件深度解析:从CRI shim到编排服务的完整架构
  • 现代文件上传系统架构深度解析:WebUploader技术实现与性能优化
  • CDMA 直接序列扩频实验箱实战:m序列与Gold序列扩频增益32对比实测
  • CentOS7 容器化安装 zabbix7.0服务
  • 小说下载器终极指南:200+网站一键离线阅读完整方案
  • Go Modules 使用指南:从入门到精通
  • 终极图像标注工具选择指南:从LabelImg到现代标注平台的完整迁移路径
  • FGO自动化革命:从零到精通的Fate/Grand Automata实战指南
  • 音乐解密工具终极方案:一键解锁加密音频实现跨设备播放
  • PCIe 3.0/4.0/5.0 Block Alignment 三阶段详解:从 EIEOS 检测到 Locked 状态转换
  • ASMR资源自动下载器:3步构建你的专属音频收藏库
  • 终极指南:使用KMS_VL_ALL_AIO智能激活脚本免费激活Windows和Office
  • 复盘 SFLSOJ 20260707 coach 原题 CSU1913 一条龙送礼物
  • RUFI洗衣精华怎么选
  • 电力通信现场作业 4 类安全措施实操指南:基于《安规》的 20+ 关键条款解读
  • 工业负载控制:智能高侧开关与微控制器的实战应用
  • 3人团队如何跑赢AI算力战?Cloud架构选型踩坑实录
  • 如何用嘎嘎降AI处理生物医学论文:生物医学毕业论文降AI4.8元知网达标完整教程
  • 心理学论文降AI工具免费推荐:2026年心理学毕业论文降AI免费4.8元亲测完整指南
  • 3步极速部署:TradingAgents-CN智能投资分析平台的完整实战指南
  • 【软考设计模式】策略模式:算法族的封装与动态切换精讲