Android显示系统RenderThread绘制HARDWARE/普通格式Bitmap与GPU与CPU处理机制

Android显示系统RenderThread绘制HARDWARE/普通格式Bitmap与GPU与CPU处理机制

在 Android 里,Bitmap.Config.HARDWARE 的 Bitmap 和普通格式 Bitmap(如 ARGB_8888、RGB_565)最大的区别,不只是"格式不同",而是存放位置、可操作性、适用场景都不一样。普通 Bitmap:像素主要在 CPU 可访问内存 中,真正用于 GPU 渲染时,通常需要经过一次"上传到 GPU 纹理/图像资源"的过程。

Bitmap.Config.HARDWARE Bitmap:像素直接放在 硬件可直接使用的 buffer 里,绘制时通常不需要再走普通 Bitmap 那种 CPU->GPU 纹理上传,因此更适合"只显示不处理"的场景。

可以把它理解成:

  • 普通 Bitmap:主要给 CPU 处理,适合读写像素、做图片加工
  • HARDWARE Bitmap:主要给 GPU 显示,适合直接渲染到屏幕

1. 存储位置不同

普通 Bitmap

通常像素数据在 应用可访问的内存 中,CPU 能直接访问。这类 Bitmap 的像素数据一般在:

native/系统内存中

CPU 可访问

但如果最终要显示到屏幕,而 View 是硬件加速渲染的,那么 GPU 不能直接拿 Java/CPU 这边的像素内存来画,通常要先把这份像素数据变成 GPU 纹理资源。

HARDWARE Bitmap

像素数据通常在 GPU/GraphicBuffer 相关的硬件内存 中,主要为了高效显示。这会直接导致后面的"能做什么、不能做什么"差异。这种 Bitmap 的像素数据在:

GPU/图形系统可直接使用的硬件 buffer 中

应用侧不能像普通 Bitmap 那样随便读写。

2. 是否可变:HARDWARE 一定不可变

普通 Bitmap

可以是:

  • mutable(可修改)
  • immutable(不可修改)

比如可以创建一个可变 Bitmap,然后拿 Canvas 在上面画。

HARDWARE Bitmap

永远是不可变的(immutable)

也就是说不能:

  • 往它上面画
  • 修改其中某个像素
  • 用它做"画布底图"进行编辑

3. 像素读写能力不同

这是最核心的区别。

普通 Bitmap

可以直接做很多 CPU 侧处理,比如:

  • getPixel()
  • setPixel()
  • getPixels()
  • copyPixelsToBuffer()
  • copyPixelsFromBuffer()
  • 图像滤镜
  • 高斯模糊
  • 像素遍历分析
  • OpenCV / 自定义算法处理

HARDWARE Bitmap

这些操作通常受限或不支持,因为像素不在普通内存里,CPU 不能像普通 Bitmap 一样直接拿到整块像素数据。

所以如果要做:

  • 二维码识别
  • 图片压缩前处理
  • 调色
  • 马赛克
  • 像素级特效
  • 转 byte array / buffer 分析

HARDWARE Bitmap 不适合

4. 绘制方式不同

普通 Bitmap

可以:

  • 绘制到屏幕
  • 绘制到另一个 Bitmap
  • 作为 Canvas(Bitmap) 的目标

HARDWARE Bitmap

它更适合:

  • 直接显示到 View / ImageView
  • 配合硬件加速渲染

它的优势是:

  • 显示时更高效
  • 减少从 CPU 内存上传纹理到 GPU 的开销
  • 更适合"只展示,不处理"的图片

但缺点是:

  • 不适合软件 Canvas
  • 某些场景下会报错,比如试图在软件渲染路径中使用它

5. 使用场景不同

适合 HARDWARE Bitmap 的场景

如果图片只是:

  • 下载后展示
  • 列表滚动显示
  • 大图预览
  • 不做任何像素处理

那么 HARDWARE 很合适,性能通常更好,内存使用策略也更偏向渲染优化。比如很多图片加载库在合适情况下会优先使用它来提升显示效率。

不适合 HARDWARE Bitmap 的场景

如果要:

  • 对图片做变换/滤镜
  • 读取像素
  • 截图后继续处理
  • 合成图片
  • 生成缩略图并编辑
  • 用 Palette 提取颜色
  • 传给依赖 CPU 像素访问的库

那应使用普通 Bitmap,通常是:

  • ARGB_8888

6. 创建和解码上的差异

普通 Bitmap

常见解码方式:

val Bitmap = BitmapFactory.decodeFile(path)

默认一般是普通配置(常见为 ARGB_8888)。

HARDWARE Bitmap

Android 8.0(API 26)开始支持:

Kotlin

val options = BitmapFactory.Options().apply {

inPreferredConfig = Bitmap.Config.HARDWARE

}

val Bitmap = BitmapFactory.decodeFile(path, options)

但即使这样指定,系统也会根据条件决定是否真的返回 HARDWARE Bitmap。

7. 判断是不是 HARDWARE Bitmap

可以这样:

if (Bitmap.config == Bitmap.Config.HARDWARE) {

// 是硬件位图

}

8. 如果拿到 HARDWARE Bitmap 但又要处理怎么办?

常见做法是先拷贝成普通 Bitmap

val softwareBitmap = Bitmap.copy(Bitmap.Config.ARGB_8888, true)

这样就能继续:

  • 读写像素
  • 用 Canvas 编辑
  • 做滤镜和图像处理

注意:

  • 这会产生一次额外内存分配
  • 大图时要注意 OOM 和性能成本

9. 一句话总结两者处理方式的本质区别

普通 Bitmap

面向"处理"

  • CPU 可访问
  • 可读像素、可编辑
  • 适合图像算法和二次加工

HARDWARE Bitmap

面向"显示"

  • GPU 友好
  • 不可修改
  • 不适合像素级处理
  • 更适合直接渲染到屏幕

10. 实际开发建议

如果需求是:

只显示图片

优先考虑 HARDWARE,可能更省事、显示更高效。

还要处理图片

不要用 HARDWARE,直接用:

Bitmap.Config.ARGB_8888

不确定后续会不会处理

也建议先别用 HARDWARE,否则后面还得 copy,一来一回反而增加开销。

11. 简单对照表

对比项 普通 Bitmap HARDWARE Bitmap
存储位置 应用可访问内存 硬件/GPU相关内存
是否可变 可变或不可变 永远不可变
像素访问 支持 一般不适合/受限
Canvas 编辑 支持 不支持直接编辑
显示性能 普通 更适合硬件渲染
图像处理 很适合 不适合
推荐场景 编辑、算法、合成 纯展示

12. 普通 Bitmap 显示到屏幕,是否需要 RenderThread 上传?

通常需要,而且一般就发生在硬件加速渲染链路中。

但更准确地说,不是"每次显示都重新上传",而是:

  • 第一次参与 GPU 绘制时,往往需要上传成纹理
  • 后续如果纹理缓存还在,通常可以复用
  • 如果缓存失效、纹理被回收,可能又会重新上传

更准确的流程

以一个普通 ARGB_8888 Bitmap 被 ImageView 显示为例,在硬件加速开启时,大致会经历:

  1. UI 线程记录绘制命令
  2. RenderThread / HWUI 消费这些绘制命令
  3. 当发现要绘制一个普通 Bitmap 时
    • 如果 GPU 侧还没有对应纹理
    • 就把 Bitmap 像素从 CPU 可访问内存上传到 GPU
  4. GPU 用这个纹理参与真正的渲染
  5. 最终交给 SurfaceFlinger 做合成并显示

13. HARDWARE Bitmap 是否就是避免了这一环节?

基本是的,但要加一个限定:它避免的是"普通 Bitmap 在首次 GPU 绘制时那种 CPU 内存 -> GPU 纹理资源的上传路径"。

因为 HARDWARE Bitmap 在创建/解码时,像素就已经放到 GPU/图形系统可直接使用的硬件资源里了。

所以在绘制时:

  • 不需要把一份 CPU 侧 Bitmap 像素再额外上传成纹理
  • 往往可以直接被渲染管线引用

这正是它性能上的重要价值之一。

14. 普通Bitmap一定Render Thread每帧上次吗?

真实实现里,不能简单理解成:

  • 普通 Bitmap = 一定每帧上传
  • HARDWARE Bitmap = 完全零成本直接显示

这两个说法都不够准确。

对普通 Bitmap 来说

它通常不是"每一帧都上传"。

更常见的是:

  • 首次绘制时上传
  • GPU 侧做缓存
  • 后续重复绘制直接用缓存纹理

只有在这些情况下可能重新上传:

  • Bitmap 变了
  • 纹理缓存被驱逐
  • 上下文丢失
  • 内存紧张导致资源回收

所以普通 Bitmap 的问题主要是:

  • 首次显示可能有上传开销
  • CPU 和 GPU 侧可能各保留一份数据,造成更高内存占用

对 HARDWARE Bitmap 来说

它也不是"完全没有任何准备成本"。

因为在:

  • 图片解码
  • buffer 分配
  • 图形资源创建

这些阶段也会有成本。

它只是把代价更早地转移到了"解码/创建资源"阶段,并且避免了后续普通 Bitmap 那种再上传一遍的路径。

15. RenderThread 在这里扮演什么角色?

在 Android 硬件加速渲染里:

  • UI Thread:负责 View measure/layout/draw 命令录制
  • RenderThread:负责执行这些渲染命令,和 GPU 交互

所以当在 View 树里画一个普通 Bitmap 时,真正跟 GPU 纹理创建、上传、绘制打交道的,通常是在 RenderThread/HWUI 这一侧。

因此可以理解为:

  • 普通 Bitmap:RenderThread 常常要处理"纹理化"
  • HARDWARE Bitmap:RenderThread 更多是"直接引用现成硬件资源"去画

16. 小结

可以把它理解成两种送货方式:

普通 Bitmap

货物存在 CPU 仓库里。

GPU 要用的时候,要先把这箱货搬运到 GPU 仓库

第一次搬运有成本,之后 GPU 仓库里有库存就能直接用了。

HARDWARE Bitmap

货物一开始就存放在 GPU/图形系统能直接使用的仓库里。

所以显示的时候,不需要再从 CPU 仓库搬一次。

这就是它能减少"上传环节"的原因。

17. 这带来的实际差异是什么?

普通 Bitmap

优点:

  • 可读写
  • 可做图像处理
  • 适合编辑、滤镜、像素分析

代价:

  • 用于 GPU 显示时通常要经历纹理上传
  • 可能有 CPU + GPU 双份内存占用

HARDWARE Bitmap

优点:

  • 更适合直接显示
  • 避免普通 Bitmap 的首次纹理上传路径
  • 常常能减少峰值内存和卡顿风险

代价:

  • 不可变
  • 不能方便地读写像素
  • 某些 API 不支持
  • 软件绘制链路不友好

18. 总结

普通格式 Bitmap 的像素数据通常位于 CPU 可访问内存中,在 Android 硬件加速渲染时,首次参与 GPU 绘制往往需要通过 RenderThread/HWUI 上传为 GPU 纹理;而 Bitmap.Config.HARDWARE 的 Bitmap 像素数据直接存储在图形系统可直接使用的硬件 buffer 中,绘制时通常可以避免这一步额外的纹理上传,从而更适合纯显示场景。

19.必须是硬件加速渲染场景

如果不是硬件加速,比如:

  • 软件 Canvas
  • 某些 CPU-only 处理链路

那么:

  • 普通 Bitmap 仍然能正常工作
  • HARDWARE Bitmap 反而不适合,甚至不能直接用

所以 HARDWARE Bitmap 的优势,主要体现在:

  • ImageView
  • RecyclerView 列表图片
  • 大图浏览
  • 纯展示型 UI

这些硬件加速显示场景里。

20. 误区

普通 Bitmap 的像素数据通常在 CPU 可访问内存中,在硬件加速渲染到屏幕时,首次绘制一般需要经过 RenderThread/HWUI 上传到 GPU 纹理;而 HARDWARE Bitmap 的像素数据直接位于硬件图形 buffer 中,因此通常可以避免这类额外上传过程。

很多人会误以为:

"HARDWARE Bitmap 就一定比普通 Bitmap 快"

其实不绝对。如果场景是:

  • 要做图像处理
  • 要频繁读取像素
  • 要做 Bitmap 合成
  • 要作为中间态反复加工

那 HARDWARE Bitmap 反而更麻烦,因为你最后又得 copy 回 ARGB_8888,得不偿失。

所以:

  • 纯展示:HARDWARE 常常更好
  • 要处理:普通 Bitmap 更合适

21.Coil/Glide等图片加载库为什么允许开发者禁用 Bitmap.Config.HARDWARE?

核心原因就四类:

  1. 这张图后面还要"处理",而 hardware Bitmap 不适合处理
  2. 这张图要走的软件绘制链路不支持
  3. 某些系统/设备上有兼容性或稳定性问题
  4. 一些中间态、动画帧、后处理流程必须用普通 Bitmap

1. 要做图片处理时,会禁用 hardware Bitmap

这是最常见的原因。

hardware Bitmap 的特点是:

  • 不可变
  • CPU 不方便直接访问像素
  • 不能拿来随便做 Canvas(bitmap) 绘制
  • 很多像素级操作不支持

所以只要发现这张图不是"纯展示",而是还要继续处理,就通常会禁用。

常见会触发禁用的操作

比如:

  • 圆角
  • 模糊
  • 裁剪
  • BitmapTransformation
  • 颜色矩阵
  • 像素读取
  • Palette 取色
  • OCR / ML / 二维码识别
  • 自定义后处理器
  • 多张图合成

例子

如果在 Glide 里写了:

Kotlin

Glide.with(imageView)

.asBitmap()

.load(url)

.transform(RoundedCorners(10))

.into(imageView)

这类 transform 往往就要求普通 Bitmap,因为它需要在 CPU/Canvas 上处理像素。

2. 软件绘制链路不支持 hardware Bitmap

这是另一个非常关键的原因。

如果某张图最终不是走"标准硬件加速 View 显示",而是要进入一些软件 Canvas 或特殊目标,hardware Bitmap 就容易出问题,甚至直接抛异常。

典型报错类似:

Software rendering doesn't support hardware bitmaps

常见不适合的场景

比如:

  • Canvas(bitmap) 离屏绘制
  • 软件渲染的 View
  • setLayerType(View.LAYER_TYPE_SOFTWARE, ...)
  • 某些通知 / RemoteViews / AppWidget
  • 打印、截图、导出
  • 某些自定义 View 内部会把 Drawable 画到 software canvas
  • 需要把图片再转成普通 Bitmap 做二次绘制

所以图片库如果发现 target 或后续链路可能是软件渲染,就会保守地禁用 hardware。

3. 某些 Android 版本 / 机型上有兼容性问题

这也是 Glide/Coil很在意的一点:
性能优化不能换来线上 crash。

hardware Bitmap 从 Android 8.0(API 26)开始支持,但早期尤其是:

  • Android 8.0 / 8.1
  • 某些厂商 ROM
  • 某些 GPU 驱动实现

存在一些兼容性坑。

常见问题包括

3.1 文件描述符(FD)占用问题

hardware Bitmap 底层通常关联 GraphicBuffer / HardwareBuffer 一类资源,可能会占用额外系统资源,包含文件描述符。

如果短时间内加载很多图,尤其在列表、大图流、复杂页面里,可能导致:

  • FD 数量逼近上限
  • 打开资源失败
  • 渲染异常
  • crash
    三方库会根据系统版本、设备情况、FD 风险,决定是否允许 hardware config。

3.2 厂商实现 bug

有些设备上可能会出现:

  • 绘制异常
  • 共享元素过渡问题
  • 某些场景下黑图
  • 回收时机异常
  • 特定系统 API 配合不稳定

所以库作者通常会维护一套"黑名单 / 兼容策略",发现不稳就禁用。

22. 动图、后处理、中间帧经常不能用 hardware Bitmap

如果是这些类型的资源,通常也更倾向禁用:

  • GIF
  • Animated WebP
  • 动画帧序列
  • 视频帧抽取后的继续处理
  • 需要 postprocessor 的图片

原因很简单:

这些流程里,图片往往不是"解完就显示",而是:

  • 要解多帧
  • 要频繁变更
  • 要合成
  • 要后处理
  • 要生成中间结果

而 hardware Bitmap 不适合当这种"处理中间态"。

23. 三个库分别为什么会这么做

Glide 为什么有时禁用

Glide 对 hardware Bitmap 的策略一直比较谨慎,原因主要有:

1 有 Transformation 时通常会禁用

因为 Glide 的很多变换本质上都需要普通 Bitmap。

比如:

  • centerCrop
  • circleCrop
  • RoundedCorners
  • 自定义 BitmapTransformation

这些都可能要求软件可处理的像素数据。

2 某些设备 / 系统版本有黑名单或保护逻辑

Glide 会考虑:

  • Android 版本
  • 厂商机型
  • FD 使用风险

避免为了性能优化引入 crash。

3 某些目标对象不适合 hardware

如果最终拿到图的人不是单纯 ImageView 显示,而是还要继续处理,Glide 会倾向禁用。

可以主动关闭

Glide.with(context)

.load(url)

.disallowHardwareConfig()

.into(imageView)

24. Coil 为什么有时禁用

Coil 的设计也类似,核心原则是:

如果图片只是展示,可以用 hardware;
如果要处理,就回退软件 Bitmap。

1 有 Transformation 时通常会禁用

Coil 的 transformation 也需要对 Bitmap 做实际处理,所以一般不适合 hardware。

2 某些 Target / 使用场景不兼容

比如:

  • 你要拿结果继续处理
  • 自定义 Target 里要访问 Bitmap
  • 目标链路不是单纯硬件加速绘制

Coil 通常也会选择禁用。

3 允许手动指定

ImageRequest.Builder(context)

.data(url)

.allowHardware(false)

.build()

如果后面要做调色、识别、处理,最好直接关掉。

有后处理器(Postprocessor)时

如果要做后处理,通常要用普通可处理 Bitmap 或 native buffer,而不是 hardware Bitmap。

动图 / 多帧资源

这类资源的内部管线更复杂,通常也不适合直接走 hardware Bitmap。

从根上说:图片库要的是"通用正确",不是"局部最快"

这是最本质的一点。

如果只看一个简单场景:

  • 静态 JPEG
  • 直接显示到 ImageView
  • 不做任何处理

那 hardware Bitmap 很棒,确实能减少普通 Bitmap 的那种 CPU->GPU 上传路径。

但是图片库面对的真实业务是:

  • 同一套 API 既要支持展示,也要支持变换
  • 既要支持新系统,也要支持老设备
  • 既要快,也不能崩
  • 既要支持标准 View,也要支持各种奇怪 Target

所以它们的策略通常是:

只有在"确定安全且有收益"的场景下才启用 hardware Bitmap。
否则就回退到普通 Bitmap。

25. 可以把它理解成一个决策规则

大概就是这样:

会倾向开启 hardware Bitmap

  • Android 8.0+
  • 静态图片
  • 只显示到硬件加速的 ImageView
  • 不做任何 Bitmap 变换/像素处理
  • 设备兼容性良好

会倾向禁用 hardware Bitmap

  • 有 transform / postprocessor
  • 需要读写像素
  • 需要软件 Canvas
  • 要调色、识别、导出、合成
  • 动图/多帧
  • 老系统或问题机型
  • 资源/FD 风险较高

26. 建议

如果图片只是用来显示:

  • 可以让库默认策略决定
  • 一般它会在合适时启用 hardware

如果你的图片后面还要处理:

  • Coil:allowHardware(false)

这是最稳的。

27. 简洁总结

Glide/Coil有时禁用 hardware Bitmap,不是因为它"不好",而是因为它**太偏"显示优化"**了。

而图片库的职责远比"显示"更广,所以一旦涉及:

  • 图像处理
  • 软件绘制
  • 动画帧
  • 兼容性
  • 稳定性

它们就会主动退回普通 Bitmap。

相关推荐
美狐美颜SDK开放平台1 小时前
什么是美颜SDK?高并发场景下的企业级美颜SDK如何开发?
android·人工智能·ios·美颜sdk·第三方美颜sdk·视频美颜sdk
YF02112 小时前
Protobuf与 gRPC 的关系:从理论到 Android + Go 实战通信全解析
android·后端·grpc
YF02112 小时前
Android 卡顿性能优化专项治理:从 ANR 根源到系统性重构实践
android·app
蒙奇·D·路飞-2 小时前
Kotlin安卓app版本自动升级设计实现
android
博客zhu虎康2 小时前
小程序按钮实现先表单校验再走手机号获取功能
android·javascript·小程序
码途漫谈2 小时前
Easy-Vibe高级开发篇阅读笔记(十三)——多平台开发之Android App 原生开发
android·人工智能·笔记·ai·开源·ai编程
街灯L2 小时前
【ADB】使用ADB工具箱卸载安卓系统软件
android·adb
赏金术士2 小时前
Kotlin 从入门到进阶 之泛型 模块(七)
android·开发语言·kotlin
恋猫de小郭3 小时前
经典,Flutter iOS 又修复了一个构建问题,还是很抽象
android·前端·flutter