Android RecyclerView+Coil解码Bitmap设置进View,RenderThread上屏显示Graphics
┌─────────────────────────────────────────────────────────────────────┐
│ RecyclerView 列表页 │
└─────────────────────────────────────────────────────────────────────┘
数据源
│
│ (content:// / file / uri)
▼
┌──────────────────────┐
│ Coil ImageRequest │
│ - size(width,height)│
│ - allowHardware() │
│ - memoryCachePolicy │
│ - transformations │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Fetcher / Decoder │
│ 从磁盘取数据并解码 │
└──────────────────────┘
│
│ 产生像素数据
▼
┌─────────────────────────────────────────────┐
│ Bitmap / Drawable │
│ │
│ 可能落在: │
│ - Native Heap(软件Bitmap像素) │
│ - Hardware Bitmap(底层图形相关内存) │
│ - Java对象壳(Bitmap对象本身很小) │
└─────────────────────────────────────────────┘
│
│ setImageDrawable / setImageBitmap
▼
┌─────────────────────────────────────────────┐
│ RecyclerView.ItemView / ImageView │
│ │
│ - View 持有对 Drawable/Bitmap 的引用 │
│ - 当前可见item、预取item、复用item都会影响活跃图数量 │
└─────────────────────────────────────────────┘
│
│ UI线程 draw()
▼
┌─────────────────────────────────────────────┐
│ View 绘制命令录制 │
│ - Canvas.drawBitmap() / drawDrawable() │
│ - 不是直接"把像素写屏幕",而是记录绘制指令 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ RenderThread / HWUI │
│ │
│ - 处理硬件加速渲染 │
│ - 可能建立/复用纹理、图层、缓存 │
│ - bitmap参与GPU可用的图形资源 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Graphics 相关内存 │
│ │
│ 可能包括: │
│ - GPU纹理 / 纹理缓存 │
│ - Hardware Bitmap backing store │
│ - RenderNode / Layer / 离屏buffer │
│ - gralloc / dmabuf / GraphicBuffer │
│ - Window Surface buffer │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ SurfaceFlinger 合成显示 │
│ - App窗口buffer交给系统合成到屏幕 │
└─────────────────────────────────────────────┘
Coil 负责把图片"准备成可显示的 Bitmap/Drawable",RecyclerView + ImageView 负责把它接入 View 树,RenderThread/HWUI 负责把它变成图形系统可渲染的内容,最终这些渲染相关资源中的一部分体现在 Graphics。
分四层理解:
-
图片数据层:文件、content uri
-
解码层:Coil 把它变成 Bitmap
-
View 层:ImageView 引用并参与绘制
-
图形层:RenderThread / GPU / Surface buffer,最终表现为 Graphics
和 dumpsys meminfo 对照理解:
[磁盘文件 jpg/png/webp]
│
│ 不算进进程运行时内存
▼
[Coil 解码]
│
▼
┌──────────────────────────────┐
│ 1. Bitmap对象壳 │
│ -> 少量 Java Heap │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 2. Bitmap像素数据 │
│ -> 常见在 Native Heap │
│ -> 若是 HARDWARE,更偏底层图形内存 │
└──────────────────────────────┘
│
│ 被 ImageView 显示
▼
┌──────────────────────────────┐
│ 3. 硬件加速绘制链路 │
│ -> RenderThread / HWUI │
│ -> GPU纹理/缓存/图层 │
│ -> 这部分常表现为 Graphics │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 4. Window Surface Buffer │
│ -> Graphics │
│ -> 页面一显示就存在 │
└──────────────────────────────┘
这里最重要的是:
-
Bitmap像素内存 不等于 Graphics
-
但 Bitmap一旦显示 ,通常会牵动 Graphics
-
Graphics里除了图片,还包括 窗口 buffer
因为列表页内存问题,关键不是某一张图,而是 同时活跃了多少张图。
RecyclerView 当前页面
│
├─ 屏幕可见 ViewHolder 12 个
│ └─ 12 张图正在显示
│
├─ 即将进入屏幕的预取 ViewHolder 6 个
│ └─ 6 张图可能已加载好
│
├─ Recycler / Cache 中暂存的 item 若干
│ └─ 可能仍持有旧 drawable 引用
│
├─ Coil Memory Cache
│ └─ 若干 bitmap 可快速复用/命中
│
└─ 正在 crossfade / transition 的 item
└─ 旧图 + 新图同时参与绘制
所以 Graphics 上涨,往往是因为:
-
单张图大
-
同时活跃图多
-
图形缓存和窗口缓冲叠加
一张更细的"单张图片流转图"
[原始图片文件]
4000 x 3000 JPEG, 3MB
│
│ Coil 解码
▼
[Bitmap]
可能被解码成:
300 x 300 x 4 = 351KB (理想缩略图)
或
1200 x 1200 x 4 = 5.5MB (过大解码)
│
│ 设置给 ImageView
▼
[ImageView 持有 Drawable/Bitmap 引用]
│
│ RecyclerView 将 item 布局并绘制
▼
[UI线程记录 drawBitmap 指令]
│
▼
[RenderThread / HWUI]
- 将其纳入硬件加速渲染
- 可能建立纹理/缓存/图层
│
▼
[Graphics]
- 纹理资源
- 图形缓存
- Surface合成相关buffer
│
▼
[显示到屏幕]
同一张原图,真正决定 Graphics 压力的,不是磁盘大小 3MB,而是解码后的像素尺寸 和是否进入图形缓存路径。
一个"Graphics 增长来源拆解图"
打开一个 RecyclerView 图片列表页后,Graphics ≈
Graphics
├─ A. Window Surface Buffers
│ └─ 和屏幕分辨率、双/三缓冲有关
│
├─ B. 图片显示带来的图形资源
│ ├─ Bitmap被绘制后相关的纹理/缓存
│ ├─ Hardware Bitmap backing store
│ └─ RenderThread / HWUI 的缓存
│
├─ C. 特效额外开销
│ ├─ crossfade
│ ├─ alpha / shadow / rounded corners
│ ├─ 离屏渲染 layer
│ └─ 动画
│
└─ D. 滚动带来的短时峰值
├─ 预取图片
├─ 快速 bind / rebind
├─ 旧图新图交替
└─ 暂时未释放的图形资源
文件不占 Graphics,解码尺寸决定底盘,View 显示触发图形链路,RenderThread/HWUI 把图片变成 Graphics。
【RecyclerView + Coil 图片显示内存流转】
数据源(file/content/url)
│
▼
Coil Request
(size / allowHardware / transformations / cache)
│
▼
Decoder
(解码成 Bitmap/Drawable)
│
├──────────────► Java Heap
│ (Bitmap对象壳、少量引用)
│
└──────────────► Native / Hardware Bitmap
(像素数据本体)
│
▼
ImageView.setImage...
(View 持有图片引用)
│
▼
UI线程绘制
(drawBitmap / drawDrawable)
│
▼
RenderThread / HWUI
(硬件加速渲染、纹理/缓存/图层)
│
▼
Graphics
├─ 图片相关图形资源
├─ 硬件位图相关内存
├─ 渲染缓存/离屏buffer
└─ Window Surface buffers
│
▼
SurfaceFlinger
(系统合成后显示到屏幕)
附:Android HWUI是什么?
Android HWUI(Hardware Accelerated Rendering Engine for UI)是Android系统中用于处理UI渲染的硬件加速引擎。它的主要作用是利用GPU(图形处理单元)来加速UI的渲染过程,从而提高渲染效率和流畅度。以下是Android HWUI工作的主要方式和步骤:
一、基本工作原理
传统软件的UI绘制是依靠CPU来完成的,硬件加速就是将绘制任务交由GPU来执行。
HWUI基于GPU加速,通过Skia Backend与GPU进行交互,将UI绘制任务从CPU转移到GPU上执行。这种方式可以显著提高绘制性能,特别是在高分辨率屏幕和复杂UI场景下。
- GPU相比CPU更加适合完成光栅化、动画变换等耗时任务,在移动设备上比起使用CPU来完成这些任务,GPU会更加省电,带来的用户体验也会更佳。
- 现代移动GPU支持可编程管线,可以高效地开发应用界面的一些特效(吸入、渐变、模糊、阴影)
Skia Backend有两种:SkiaGl和SkiaVk,分别对应后端OpenGLES和Vulkan
二、核心组件
-
Skia图形库:
-
Skia是HWUI的核心图形库,提供了基础的绘制功能,如图形、文本、位图等。
-
它支持硬件加速渲染,能够充分利用GPU进行并发计算,加快UI界面的渲染速度。
-
代码示例
// 创建画布 Canvas canvas = new Canvas(bitmap); // 绘制矩形 Paint paint = new Paint(); final int color = Color.BLUE; paint.setColor(color); RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()); canvas.drawRoundRect(rect, 32, 32, paint);
-
-
OpenGL ES/Vulkan:
- OpenGL ES/Vulkan是HWUI与GPU之间的桥梁,负责将Skia生成的绘制命令转化为GPU能够执行的指令序列。
- OpenGL ES 3.0是HWUI的默认版本,具有更好的功能和性能表现。未来HWUI的Backend默认会切换到Vulkan。
-
DisplayList:
-
DisplayList是HWUI的渲染列表,用于记录绘制操作以及它们的位置、大小等信息。
-
这些操作被显式地缓存起来,以便后续可以更快速地进行处理,并允许视图树中相同的部分在多个帧之间重复使用,从而节约内存和带宽。
-
代码示例
// 创建DisplayList DisplayList displayList = new DisplayList("MyList"); displayList.start(512, 512); //设置宽高 // 添加绘制操作 Paint paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); displayList.drawRect(64, 64, 256, 256, paint); // 结束DisplayList displayList.end();
-
-
RenderNode:
-
RenderNode是HWUI的渲染节点,对应于视图层次结构中的一个节点。
-
每个View持有一个RenderNode,这些RenderNode组成树形结构,用于管理和组织渲染任务。
-
RenderNode负责保存和管理View绘制过程中使用的各种属性,如透明度和边框等。
-
代码示例

// 创建RenderNode RenderNode renderNode = new RenderNode("MyNode"); // 更新RenderNode属性 View view = findViewById(R.id.myView); renderNode.setPosition(view.getLeft(), view.getTop()); renderNode.setElevation(view.getElevation()); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon); renderNode.setBitmapCache(bitmap); // 添加子节点 RenderNode child = new RenderNode("MyChild"); //更新child属性 renderNode.addChild(child);
-
-
Layers
-
Layer是HWUI的另一个重要概念,用于实现UI的专业效果和动画效果。
-
在Android 10及以前的系统中,所有视图都隶属于共享的系统组层级。但是在Android 11及以后的系统中,每个窗口/活动/碎片都将拥有自己的独立图层级别,从而增强了性能和隐私管理。
-
代码示例
// 创建Layer Layer layer = mSurface.getLayer(); layer.setX(100); layer.setY(100); layer.setAlpha(0.5f); // 在Layer上绘制图像和文字 Canvas canvas = layer.lockCanvas(); canvas.drawBitmap(bitmap, 0, 0, paint); canvas.drawText(text, 30, 100, paint); layer.unlockCanvasAndPost(canvas);
-
-
HardwareComposer
-
HardwareComposer是Android系统中的硬件合成器,作为HWUI的主要接口之一。
-
它的作用是在GPU输出图像到屏幕之前,对多个应用程序的渲染结果进行混合和组合,从而形成最终的显示内容。
-
在硬件合成情况下,不需要将应用程序的UI渲染成一个帧缓存(FrameBuffer)并上传到系统RAM,从而减少内存和总线带宽的负担。
-
代码示例
RoutingTable routingTable = hwcomposer.createRoutingTable(); routingTable.setOutputConfig(new OutputConfiguration(mDisplayToken, Display.DEFAULT_DISPLAY)); Destination destination = routingTable.createDestination(); destination.setColorMode(ColorMode.SRGB); destination.setBufferStream(bufferStream); surfaceFlinger.setTransactionState(routingTable.getTransaction()); surfaceFlinger.applyTransaction();
-
-
Threaded Rendering(多线程渲染):
- 为了最大化利用CPU性能,HWUI采用了多线程渲染模式。
- 通常包括UI线程和RenderThread(RT线程)。UI线程负责View的绘制逻辑和将绘制命令打包成Skia的绘制命令存储到DisplayList;RT线程则负责取出这些绘制命令并执行实际的渲染操作。
三、工作流程
- UI线程绘制:
- 当View需要被绘制时,UI线程会调用相应的绘制方法(如onDraw())。
- 这些绘制方法通过Canvas类提供的接口进行绘制操作,Canvas的绘制命令会被转化为Skia的绘制命令并存储到DisplayList中。
- RenderThread渲染:
- RenderThread从DisplayList中取出绘制命令,并通过OpenGL ES接口将这些命令发送给GPU执行。
- GPU完成绘制后,将渲染结果输出到屏幕上。
- 性能优化:
- HWUI还采用了多种性能优化技术,如延时渲染列表(Deferred Display List)、绘制命令合并(Draw Op Batching)等。
- 这些技术通过减少GPU的调用次数、优化渲染状态切换等方式来提高渲染效率。