ArrayDeque 和 java.util.Stack 和 MutableList 实现与扩容策略

ArrayDequejava.util.StackMutableList 的区别是什么

ArrayDequejava.util.StackMutableList(如 ArrayList)是实现栈的不同方式,它们有一些区别:

  1. 实现接口:ArrayDeque 实现了 Deque 接口,而 java.util.Stack 继承自 Vector 类,MutableList 则是 Kotlin 标准库中的可变列表接口。
  2. 内部数据结构:ArrayDeque 使用循环数组(circular array)作为内部数据结构,而 java.util.Stack 内部使用数组(Array),MutableList 则可以使用数组、链表或其他数据结构。
  3. 动态扩容:ArrayDequeMutableList 都支持动态扩容,可以根据需要自动调整容量,而 java.util.Stack 不支持动态扩容,需要手动调整容量。
  4. 性能:ArrayDeque 在大多数操作上具有更好的性能,因为它使用了循环数组和更优化的实现。而 java.util.Stack 是一个遗留类,性能可能相对较差。MutableList 的性能取决于具体实现。

综上所述,如果你想要使用性能较好且功能丰富的栈实现,推荐使用 ArrayDeque。如果你需要与现有的代码兼容或使用特定的接口,可以选择 java.util.StackMutableList

ArrayDeque 怎么用循环数组实现的

ArrayDeque 使用循环数组(circular array)实现,它在内部维护了一个固定大小的数组来存储元素。下面是简单介绍 ArrayDeque 如何使用循环数组实现的一些关键点:

  1. 内部数组:ArrayDeque 内部有一个对象数组来存储元素,通常使用 elementsarray 字段表示。
  2. 头部和尾部指针:ArrayDeque 有两个指针,分别指向头部和尾部。通常使用 headtail 字段表示。
  3. 循环数组特性:ArrayDeque 的关键之处在于利用循环数组的性质。当头部或尾部指针到达数组的边界时,它们会回绕到数组的另一侧,形成循环效果。
  4. 扩容和缩容:当元素数量达到数组容量时,ArrayDeque 会自动扩容。在扩容时,会创建一个更大的数组,并将原始数组中的元素按照循环顺序复制到新数组中。类似地,当元素数量较少时,ArrayDeque 也会自动缩容。
  5. 添加和移除元素:添加元素时,头部指针向前移动一个位置,并将元素放入头部位置。移除元素时,头部指针向后移动一个位置,并返回头部位置的元素。

通过这种方式,ArrayDeque 可以在不断添加和移除元素的过程中高效地利用数组空间,并保持循环数组的特性。这使得 ArrayDeque 在栈和队列等数据结构的实现中具有较高的性能和灵活性。

ArrayList 和 ArrayDeque 的区别

两种实现之间的主要区别在于调整大小的策略。

ArrayList 的调整大小策略是将其调整为 oldCapacity + (oldCapacity >> 1) 的新大小,增加了约 50%。默认容量为 10,在调整大小后的容量为 15、22、33、49、73、109、163、244、366…

ArrayDeque 总是调整为 2 的幂次方。在调整大小时,容量会加倍。从默认值 16 开始,调整大小后的容量为 32、64、128、256…

因此,ArrayDeque 达到了更高的容量,而调整大小的操作更少,这是因为数组的复制操作很耗费资源。例如,要在默认大小的 ArrayList 中存储 256,需要进行 9 次调整大小操作,而 ArrayDeque 只需要 4 次。数组的复制操作可能很快,但也可能需要 GC 来释放一些空间用于新的数据集,此外还需要进行内存复制操作(ArrayDeque 可能在这方面表现更好,因为它对齐到 2 的幂次方)。

这两种数据结构的最佳情况复杂度为 O(1)。ArrayList 的 push 和 pop 操作通过直接访问头部和尾部(ArrayDeque)来实现,而 add 和 removeLast 操作(ArrayList)则是通过直接访问大小来实现。

线程安全的单例

这是为你整理的最终技术文档。文档聚焦于 Kotlin 顶级函数实现 DCL 单例的四种演进方案,重点标注了面试中必须指出的致命错误。


Kotlin 单例模式(DCL)最终技术文档

核心背景

在禁用 objectcompanion object 的情况下,必须使用 Top-level(顶级) 变量与函数。此时,变量编译后等同于 Java 的 static 成员。


情况一:❌ 致命错误版(最常见的挂点)

1
2
3
4
5
6
7
8
9
10
11
12
13
private var instance: Singleton? = null // 错误 1:缺失 @Volatile
private val lock = Any()

fun getInstance(): Singleton {
if (instance == null) { // 第一次检查
synchronized(Any()) { // 错误 2:锁不唯一,每次都 new 一个新锁
if (instance == null) {
instance = Singleton()
}
}
}
return instance!!
}

致命缺陷指出:

  1. 指令重排风险:缺少 @Volatile,导致线程 A 可能先赋值引用,后执行构造函数。线程 B 在锁外检查到 instance 非空,拿走一个未初始化的“半成品”对象,引发崩溃。
  2. 同步失效synchronized(Any()) 导致每个线程都在拿不同的钥匙,无法形成互斥,会创建出多个单例。

情况二:❌ 性能低效版(判空仅在锁内)

1
2
3
4
5
6
7
8
9
10
11
12
@Volatile
private var instance: Singleton? = null
private val lock = Any()

fun getInstance(): Singleton {
synchronized(lock) { // 错误 3:缺失外部判断
if (instance == null) {
instance = Singleton()
}
}
return instance!!
}

缺陷指出:

  • 性能瓶颈:虽然线程安全,但由于没有外部判空,每一次调用该方法都要排队竞争锁。在高并发场景(如 Android 列表滑动、数据库频繁读写)下会造成严重卡顿。

情况三:❌ 锁范围错误版(成员变量锁)

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton private constructor() {
private val lock = Any() // 错误 4:锁定义在类内部
}

@Volatile
private var instance: Singleton? = null

fun getInstance(): Singleton {
return instance ?: synchronized(lock) { // 无法访问,且锁不唯一
instance ?: Singleton().also { instance = it }
}
}

缺陷指出:

  • 作用域冲突:顶级函数(静态上下文)无法访问类内部的成员变量 lock
  • 逻辑悖论:单例创建前实例并不存在,实例不存在则 lock 不存在。必须使用静态锁才能保证创建过程的唯一性。

情况四:✅ 最终正确版(标准 DCL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Volatile // 必须:禁止指令重排序,保证锁外线程安全
private var instance: Singleton? = null

private val lock = Any() // 必须:全局唯一静态锁

class Singleton private constructor()

fun getInstance(): Singleton {
// 第一次判空:为了性能,避免不必要的锁竞争
return instance ?: synchronized(lock) {
// 第二次判空:为了安全,防止排队线程重复创建
instance ?: Singleton().also { instance = it }
}
}

💡 面试核心知识点总结

关键组件 核心作用
@Volatile 保护锁外线程。防止其读取到“已赋值地址但未初始化完成”的对象(解决指令重排)。
静态唯一锁 保证互斥性。确保所有线程竞争同一把钥匙,防止创建多个实例。
外层判空 性能优化。单例一旦创建,后续调用实现“零开销”返回。
内层判空 原子性保障。拦截那些在锁外排队的线程,确保只执行一次构造函数。

Java Stack 过时了吗

正文

在 Java 中,Stack 类并没有过时,仍然可以使用。然而,根据 Java 官方文档的建议,推荐使用 Deque 接口的实现类 ArrayDeque 来代替 Stack

Stack 类是基于向量(Vector)实现的,而 Vector 是线程安全的、同步的,因此在性能上可能会有一些开销。而 ArrayDeque 是基于数组实现的双端队列,性能更高,并且在大多数情况下能够提供与 Stack 类相同的功能。

此外,Deque 接口提供了更丰富的操作方法,例如在队首或队尾插入、删除元素等,使得其更灵活且更符合现代编程的需求。

因此,尽管 Stack 类仍然可用,但在新的代码中,推荐使用 ArrayDeque 或其他实现了 Deque 接口的类来代替 Stack

java.util.Stack 类为什么是线程安全的

正文

在Java中,java.util.Stack 类是线程安全的,这是因为它是通过在每个方法上添加synchronized关键字来实现同步的。
synchronized关键字可以确保在同一时间只有一个线程可以访问被标记为synchronized的方法或代码块。
当一个线程执行一个被synchronized关键字修饰的方法时,它将获得一个锁,其他线程必须等待该锁释放才能访问相同的方法。
这样可以确保在任何给定的时刻只有一个线程可以修改Stack的状态,从而防止并发访问导致的数据不一致或竞态条件。
然而,需要注意的是,虽然Stack类本身是线程安全的,但在某些情况下,使用synchronized关键字可能会降低性能,特别是在高并发环境下。
因此,在Java中,推荐使用java.util.concurrent包提供的并发集合类,如java.util.concurrent.ArrayDequejava.util.concurrent.LinkedBlockingDeque,它们提供了更高效和灵活的线程安全操作。

Android 帧绘制耗时全链路溯源

本档旨在从底层 AOSP 源码视角,拆解 JankStats 捕获的每一项耗时指标的物理来源,并精确到具体的类、函数及源码路径。


全链路耗时概览图

一帧的产生经历了:Vsync 信号(Choreographer) -> UI 线程处理(MainThread) -> Sync 同步 -> RenderThread 处理 -> GPU 渲染 -> Buffer 交换

一帧的完整渲染流程

mermaid 代码

sequenceDiagram
    participant C as Choreographer
    participant M as MainThread
    participant R as RenderThread
    participant G as Graphics

    Note over C, M: 1 VSync (INTENDED_VSYNC / VSYNC_TIMESTAMP)
    C->>M: VSync 信号触发
    
    rect rgb(240, 240, 240)
        Note right of M: UNKNOWN_DELAY_DURATION
        M->>M: 2 do other work (Handler/Broadcast)
    end

    rect rgb(230, 255, 230)
        M->>M: 3 handle input (INPUT_HANDLING_DURATION)
        M->>M: 4 play animation (ANIMATION_DURATION)
        M->>M: 5 measure & layout (LAYOUT_MEASURE_DURATION)
        M->>M: 6 draw (DRAW_DURATION)
    end

    M->>R: 7 sync and upload (SYNC_DURATION)
    
    rect rgb(230, 230, 255)
        R->>G: 8 dequeue buffer (阻塞点)
        R->>R: 9 execute
        R->>G: 10 flush command (COMMAND_ISSUE_DURATION)
        R->>G: 11 swap buffer (SWAP_BUFFERS_DURATION)
    end

    G->>G: 12 compose (系统合成)

    Note over G: GPU_DURATION (GPU 异步执行耗时)
    Note over C, G: TOTAL_DURATION (1-11 阶段总和)

流程详细说明与 FrameMetrics 参数对应

流程编号 流程名称 详细说明 对应 FrameMetrics 参数
1 VSync Choreographer 接收到硬件 VSync 信号,触发主线程。 INTENDED_VSYNC_TIMESTAMP (预期) / VSYNC_TIMESTAMP (实际)
2 do other work 主线程在处理 UI 前执行的其他任务(如 Handler 消息、广播)。 UNKNOWN_DELAY_DURATION
3 handle input 处理触摸、按键等输入事件。 INPUT_HANDLING_DURATION
4 play animation 执行属性动画、帧动画等。 ANIMATION_DURATION
5 measure & layout 对 View 树进行测量和布局,确定每个控件的大小和位置。 LAYOUT_MEASURE_DURATION
6 draw 录制绘制指令 (DisplayList),将 Canvas 绘制转化为 Native 指令。 DRAW_DURATION
7 sync and upload 将主线程录制的绘制信息和资源(如 Bitmap)同步到渲染线程。 SYNC_DURATION
8 dequeue buffer 渲染线程从 BufferQueue 申请缓冲区。此处是著名的阻塞点。 (包含在 TOTAL_DURATION 中,或由 DequeueBufferDuration 统计)
9-10 execute & flush 渲染线程回放绘制指令,调用 GPU 渲染并刷新指令队列。 COMMAND_ISSUE_DURATION
11 swap buffer 将渲染完成的 Buffer 提交给 SurfaceFlinger SWAP_BUFFERS_DURATION
12 compose SurfaceFlinger 进行画面合成并显示到屏幕。 (属于系统层,不计入应用内耗时)

FrameMetrics 详细指标参考表

FrameMetrics 的常量值 含义 可能导致耗时多久的情况
UNKNOWN_DELAY_DURATION UI 线程延迟处理绘制任务的耗时 主线程消息队列中正在执行的非绘制任务耗时太久,导致开始执行绘制任务太晚
INPUT_HANDLING_DURATION 输入事件处理函数的耗时 点击事件耗时太久
ANIMATION_DURATION 动画回调函数的耗时 动画太多或者回调函数耗时太久
LAYOUT_MEASURE_DURATION 整个 View 树的测量和布局的耗时 布局层级复杂,频繁改变尺寸或位置(比如复杂的属性动画)
DRAW_DURATION 布局绘制函数的耗时 draw/onDraw 里执行了耗时操作
SYNC_DURATION 主线程同步 DisplayList 到 RenderThread 的耗时 过度绘制,导致要更新的 DisplayList 过多
COMMAND_ISSUE_DURATION RenderThread 发送绘制命令到 GPU 的耗时 绘制内容复杂
SWAP_BUFFERS_DURATION 将绘制的 buffer 交换到前台显示的耗时
TOTAL_DURATION 绘制一帧的总耗时 前面任意一个阶段耗时,最终这个值都会变大
FIRST_DRAW_FRAME 这帧是否是当前窗口的第一帧
INTENDED_VSYNC_TIMESTAMP 预期接收到 VSync 信号(也就是这帧开始执行)的时间戳
VSYNC_TIMESTAMP 真正接收到 VSync 信号的时间 主线程的耗时任务太多,会导致主线程响应 VSync 信号过慢
GPU_DURATION GPU 完成这帧绘制的耗时

其他核心参数解析

  • TOTAL_DURATION: 从预期 VSync 信号发出到帧绘制完成(Swap Buffer 结束)的总时间。即 1-11 阶段的总和。
  • GPU_DURATION: GPU 执行渲染指令的实际耗时(从指令提交完成到 GPU 硬件执行完毕)。反映了 GPU 的真实压力。
  • DEADLINE: 系统为该帧设定的截止时间。如果 TOTAL_DURATION > DEADLINE,通常意味着发生了卡顿。
  • FIRST_DRAW_FRAME: 标记该帧是否为窗口可见后的第一帧。由于需要初始化资源,首帧耗时通常较高。

Android 帧绘制耗时全链路溯源

第一阶段:起跑线前的等待 (Schedule & Input)

核心指标:UNKNOWN_DELAY_DURATION(未知延迟时长)

  • 物理意义:从系统产生 Vsync 信号到 UI 线程真正开始执行 doFrame 任务的时间差。
  • 精确路径
    1. 起点DisplayEventReceiver 收到底层信号,通过 JNI 回调发送消息到 Looper。
      • 源码:frameworks/base/core/java/android/view/DisplayEventReceiver.java
    2. 关键函数Choreographer$FrameDisplayEventReceiver.run() 被 Looper 唤醒,调用 doFrame
      • 源码:frameworks/base/core/java/android/view/Choreographer.java
    3. 终点Choreographer.doFrame() 开始执行。
  • 计算逻辑
    UNKNOWN_DELAY = FrameInfo[HANDLE_INPUT_START] - FrameInfo[INTENDED_VSYNC]
  • 解析
    • INTENDED_VSYNC:理想的帧开始时间戳(Vsync 信号发生的时刻)。
    • HANDLE_INPUT_START:UI 线程 Looper 实际分发到该渲染消息并开始处理的时刻。
  • 参数价值:反映 UI 线程的消息队列积压情况。如果此值大,说明 UI 线程正在执行非 UI 的耗时任务(如长耗时的 Handler 消息或过重的业务逻辑)。

第二阶段:UI 线程的奔跑 (Logic & Construction)

核心指标:INPUT_HANDLING(输入处理) + ANIMATION(动画) + LAYOUT_MEASURE(布局测量) + DRAW(绘制)

  • 物理意义:UI 线程执行业务逻辑并构建绘制指令的过程。
  • 精确路径
    1. Input HandlingChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT) -> InputEventReceiver.consumeEvents()
      • 源码:frameworks/base/core/java/android/view/InputEventReceiver.java
    2. AnimationsChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION) -> 执行所有已注册 of the ValueAnimator 等回调。
    3. Layout & MeasureChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL) -> ViewRootImpl.performTraversals()
      • performTraversals 核心步骤解析
        • relayoutWindow:通过 IPC 与 WindowManagerService 通信,获取或调整 Surface,确定窗口的物理大小(Frame Size)。
        • **performMeasure()**:从顶层 DecorView 开始深度优先遍历 View 树。每个 View 根据父容器的 MeasureSpec 计算自己的 MeasuredWidthMeasuredHeight
        • **performLayout()**:根据测量结果确定 View 在窗口中的最终位置坐标(Left, Top, Right, Bottom),并触发 View 的 onLayout()
        • **performDraw()**:启动绘制阶段,将 View 树的视觉信息转化为渲染指令。
      • 源码:frameworks/base/core/java/android/view/ViewRootImpl.java
    4. **Draw (Recording)**:ViewRootImpl.performDraw() -> ThreadedRenderer.draw() -> View.updateDisplayListIfDirty()
      • 源码:frameworks/base/core/java/android/view/ThreadedRenderer.java
      • 注意:此处是在录制指令到 DisplayList,而非真正渲染。
  • 参数价值:反映 布局复杂度 (Over-nesting)主线程计算压力

第三阶段:交接棒 (Handoff / Sync)

核心指标:SYNC_DURATION(同步时长)

  • 物理意义:UI 线程阻塞等待,将绘制指令和 Bitmap 资源同步给 RenderThread。
  • 精确路径
    1. Java 端入口ThreadedRenderer.nSyncAndDrawFrame() (JNI 调用)。
      • 源码:frameworks/base/core/java/android/view/ThreadedRenderer.java
    2. Native 端核心CanvasContext::prepareTree()
      • 源码:frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
    3. 资源上传:在此阶段,新创建的 Bitmap 会被上传到 GPU 纹理缓存。
  • 计算逻辑:记录从 UI 线程发起同步请求到 RenderThread 成功接收并完成资源拷贝,且 UI 线程被唤醒的时间。
  • 参数价值Bitmap 优化的核心指标。如果 SYNC 耗时高,通常是由于该帧上传了过大的 Bitmap 或触发了大量的资源回收与重新分配。

第四阶段:渲染线程的冲刺 (Recording & Issue)

核心指标:COMMAND_ISSUE_DURATION(指令发布时长)

  • 物理意义:RenderThread 将 DisplayList 转换为真正的 GPU 指令(OpenGL/Vulkan)并提交给驱动。
  • 精确路径
    1. RenderThread 循环RenderThread::threadLoop()
      • 源码:frameworks/base/libs/hwui/renderthread/RenderThread.cpp
    2. 核心执行CanvasContext::draw() 调用渲染管线。
    3. 渲染管线SkiaPipeline::renderFrame() (Android 8.0+ 默认使用 Skia)。
      • 源码:frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp
  • 参数价值:反映 绘制指令的复杂程度。DrawCalls 越多、Path 路径越复杂、或者使用了复杂的 Shader,此项耗时越高。

第五阶段:终点线 (GPU & Swap)

核心指标:SWAP_BUFFERS_DURATION(缓冲区交换时长)

  • 物理意义:RenderThread 调用 eglSwapBuffers 后,等待 GPU 渲染完成并交换缓冲区的阻塞时间。
  • 精确路径
    1. 核心函数CanvasContext::swapBuffers()
    2. 底层调用EglManager::swapBuffers() -> eglSwapBuffers()
      • 源码:frameworks/base/libs/hwui/renderthread/EglManager.cpp
    3. 系统反馈:底层 SurfaceFlinger 消费 Buffer 的速度(涉及 BufferQueue)。
  • 解析:如果 GPU 还没算完,或者系统显示队列已满(存在背压),此调用会阻塞 RenderThread,直到有可用的 Buffer。
  • 面试价值:反映 GPU 渲染压力 (Overdraw)系统三重缓冲 (Triple Buffering) 的饱和度。也是衡量 SurfaceFlinger 消费压力的关键点。

第六阶段:最终审判 (Judgment)

核心指标:TOTAL_DURATION(总耗时) & frameOverrunNanos(帧超时时间) (API 31+)

  • 物理意义:一帧从计划开始到最终出屏全链路总耗时及其与截止日期的偏离量。
  • 精确路径
    • Java 层android.view.FrameMetrics
      • 源码:frameworks/base/core/java/android/view/FrameMetrics.java
    • Native 层google::hwui::FrameInfo
      • 源码:frameworks/base/libs/hwui/FrameInfo.cpp
  • 卡顿判定准则
    1. 经典准则TOTAL_DURATION > 16.6ms (针对 60Hz)。
    2. **JankStats 准则 (API 31+)**:frameOverrunNanos > 0。即该帧物理上错过了 VSync 的 Deadline(最准确的掉帧判定)。

Q&A

  • Q:如果 UI 线程只用了 2ms,为什么还是掉帧了?
    • A:通过 FrameMetrics 分析,可能是 UNKNOWN_DELAY 极大(消息队列排队严重)或 SWAP_BUFFERS 阻塞严重(GPU 瓶颈或渲染队列满了)。
  • Q:SYNC_DURATION(同步时长)过高怎么排查?
    • A:重点检查该帧是否有大图加载、ImageView 设置了巨大的 Bitmap,或者是否在 onDraw 之外频繁触发了硬件资源更新。
  • Q:为什么硬件加速下,RenderThread 卡顿会影响 UI 线程?
    • A:因为 SYNC 阶段是同步的。UI 线程必须在 CanvasContext::prepareTree 期间等待 RenderThread 接收数据,只有等 RenderThread “接手”成功后,UI 线程才能继续处理下一个 Vsync。

Android 帧绘制耗时全链路源码溯源

本文档详细记录了从 Java 层到 Native 层的 Android 帧刷新全链路核心函数(Android 15),并对源码关键逻辑进行了中文注释。


Java 层:UI 线程任务调度

frameworks/base/core/java/android/view/

帧回调起点:Choreographer.doFrame()

这是主线程响应 VSYNC 信号的总入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
boolean resynced = false;
try {
// 1. 更新当前帧的时间线数据(VsyncId、预期呈现时间、截止时间)
FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);

if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
}

synchronized (mLock) {
if (!mFrameScheduled) {
return; // 如果当前没有安排帧,说明是过期的信号,直接返回
}

// 2. 抖动计算 (Jitter):判断主线程是否存在耗时操作导致响应延迟
long intendedFrameTimeNanos = frameTimeNanos; // 信号发出的预期时间
startNanos = System.nanoTime(); // 实际开始处理的时间
final long jitterNanos = startNanos - frameTimeNanos;

if (jitterNanos >= frameIntervalNanos) {
// 如果延迟超过一个周期,说明主线程发生了卡顿
long lastFrameOffset = jitterNanos % frameIntervalNanos;
// 修正 frameTime 为最近的一个模拟 VSYNC 点,保证动画平滑
frameTimeNanos = startNanos - lastFrameOffset;
final long skippedFrames = jitterNanos / frameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! 应用程序可能在主线程做了太多工作。");
}
// 掉帧后重新同步时间线
timeline = mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
resynced = true;
}

// 3. 记录帧统计信息,供后续 JankTracker 统计卡顿
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
vsyncEventData.preferredFrameTimeline().vsyncId,
vsyncEventData.preferredFrameTimeline().deadline, startNanos,
vsyncEventData.frameInterval);
mFrameScheduled = false; // 重置安排标记
mLastFrameTimeNanos = frameTimeNanos;
}

// 4. 锁定动画时钟,确保本帧内所有动画基于相同的时间基准
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS,
timeline.mExpectedPresentationTimeNanos);

// 5. 调用 doCallbacks 依次按顺序执行回调:输入 -> 动画 -> 缩进动画 -> 遍历(Layout/Draw) -> 提交
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameIntervalNanos);

mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameIntervalNanos);

mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameIntervalNanos);

doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

补充 VsyncEventData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#pragma once

#include <android/gui/FrameTimelineInfo.h>

#include <array>

namespace android::gui {

/**
* VsyncEventData 是一个简单的 POD(Plain Old Data)结构,用于存储 Vsync 相关数据。
* 例如,它可以方便地在 DisplayEventReceiver::Event 的联合体(union)中使用。
*/
struct VsyncEventData {
// 帧时间轴的最大容量,设置为 7 是为了在灵活性和内存开销之间取得平衡。
static constexpr int64_t kFrameTimelinesCapacity = 7;

// 当前帧被调度时的帧间隔时间(纳秒)。反映了当前的屏幕刷新率。
int64_t frameInterval;

// 系统推荐的首选帧时间轴(frameTimelines 数组中的索引)。
uint32_t preferredFrameTimelineIndex;

// 平台当前提供的有效帧时间轴数量;最大值为 kFrameTimelinesCapacity。
uint32_t frameTimelinesLength;

/**
* FrameTimeline 定义了一个特定的渲染预期。
* 现代 Android 系统提供多个候选时间轴,允许应用根据其渲染能力选择最合适的呈现时刻。
*/
struct alignas(8) FrameTimeline {
// 与此 Vsync 事件对应的唯一 ID。
// 此 ID 将用于填充 ISurfaceComposer::setFrameTimelineVsync 等接口,
// 以便系统能够追踪特定帧的流水线状态。
int64_t vsyncId;

// 应用必须在该时刻之前完成其帧渲染任务(包括 CPU 和 GPU 上的所有工作)。
// 使用 CLOCK_MONOTONIC 单调时钟,单位为纳秒。
int64_t deadlineTimestamp;

// 预期该帧画面在物理屏幕上真正显示的时刻(纳秒)。
int64_t expectedPresentationTime;
} frameTimelines[kFrameTimelinesCapacity]; // 按时间顺序排序的候选帧时间轴。

// 获取首选帧时间轴的 Vsync ID。
int64_t preferredVsyncId() const;

// 获取首选帧时间轴的截止时间戳(Deadline)。
int64_t preferredDeadlineTimestamp() const;

// 获取首选帧时间轴的预期呈现时间戳。
int64_t preferredExpectedPresentationTime() const;
};

/**
* ParcelableVsyncEventData 是 VsyncEventData 的包装类,
* 继承自 Parcelable,使其能够通过 Binder 在进程间进行序列化传输。
*/
struct ParcelableVsyncEventData : public Parcelable {
VsyncEventData vsync;

status_t readFromParcel(const Parcel*) override;
status_t writeToParcel(Parcel*) const override;
};
} // namespace android::gui

接过来自底层 DisplayEventReceiver.cpp 传上来的“发令枪响时间” (frameTimeNanos)。
记录:把主线程此时此刻的“起跑时间” (startNanos) 记录下来。
UNKNOWN_DELAY_DURATION = startNanos - frameTimeNanos

任务分发:Choreographer.doCallbacks()

这是 Choreographer 内部用于依次执行各阶段任务(输入、动画、遍历、提交)的核心逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 执行指定类型的回调任务。
*
* @param callbackType 回调类型(INPUT, ANIMATION, INSETS_ANIMATION, TRAVERSAL, COMMIT)
* @param frameIntervalNanos 当前设备的帧间隔(例如 60Hz 约为 16.6ms)
*/
void doCallbacks(int callbackType, long frameIntervalNanos) {
CallbackRecord callbacks;
long frameTimeNanos = mFrameData.mFrameTimeNanos;

synchronized (mLock) {
final long now = System.nanoTime();

// 提取所有“到期”的回调
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);

if (callbacks == null) {
return;
}
mCallbacksRunning = true;

// 特殊处理:提交阶段 (CALLBACK_COMMIT),修正帧时间基准以应对严重掉帧
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
if (frameIntervalNanos > 0 && jitterNanos >= 2 * frameIntervalNanos) {
final long lastFrameOffset = jitterNanos % frameIntervalNanos + frameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
}
}
}

try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 执行回调。对于 CALLBACK_TRAVERSAL 类型,会进入 ViewRootImpl.performTraversals()
c.run(mFrameData);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
修正帧时间基准以应对严重掉帧
1
2
3
4
5
6
7
8
9
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
if (frameIntervalNanos > 0 && jitterNanos >= 2 * frameIntervalNanos) {
final long lastFrameOffset = jitterNanos % frameIntervalNanos + frameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
}
}

这在 Choreographer 的底层逻辑中是一个非常重要的保护机制,用于解决因严重的掉帧或主线程卡顿导致的 “逻辑时间偏差”


核心意图:为什么要进行这一步计算?

在 Android 的动画(Animation)和图形渲染中,所有计算都是基于 frameTimeNanos 这个基准的。计算逻辑通常如下:

progress = (currentTime - startTime) / duration

如果系统发生了严重的卡顿(例如主线程阻塞了 500ms),frameTimeNanos 依然停留在 500ms 之前。如果不进行校正,当代码恢复执行时,程序会使用这个“过时”的 frameTimeNanos。这会导致:

  • 动画“跳变”:动画会瞬间计算出很大的 delta,导致元素直接从起点“瞬移”到终点,产生视觉上的闪烁。
  • 物理引擎异常:如果你的代码里有基于物理模拟的逻辑,巨大的时间差会导致计算出的速度、位移爆炸。
代码逻辑拆解

这段代码在 CALLBACK_COMMIT 阶段触发,其逻辑是在检测到极度严重掉帧时,强行将“当前的逻辑帧时间”重置到最近的一个有效时间点。

  • **jitterNanos (抖动/延迟量)**:

    1
    jitterNanos = now - frameTimeNanos

    这是当前物理时间(now)距离该帧预期执行时间(frameTimeNanos)的偏移量。如果该值很大,说明我们在这一帧上滞后了很多。

  • **阈值判断 (jitterNanos >= 2 \* frameIntervalNanos)**:

    这是为了避免误判。只有当延迟超过 2 个帧周期(在 60Hz 下约 > 33.3ms)时,才会介入。这说明系统已经错过了至少一个 VSYNC 信号,进入了严重的掉帧状态。

  • **重新对齐 frameTimeNanos**:

    1
    2
    final long lastFrameOffset = jitterNanos % frameIntervalNanos + frameIntervalNanos;
    frameTimeNanos = now - lastFrameOffset;

    这里将 frameTimeNanos 设置为 now 减去一个偏移量。这个偏移量被强制限制在 frameIntervalNanos2 * frameIntervalNanos 之间。

    效果:它将 frameTimeNanos “强制拉回”到距离当前 now 最近的、符合渲染周期节奏的时间点。


可视化理解

上图展示了当发生卡顿时,虚拟时间轴如何被校准以平滑后续动画。

状态 逻辑时间点 结果
正常情况 frameTimeNanos 是旧的信号时间 动画平滑,delta 很小。
严重卡顿 frameTimeNanos 滞后 > 33ms 如果不修正,动画下一帧会因为 currentTime - frameTimeNanos 过大而发生“ teleport” (瞬移)。
修正后 frameTimeNanos 被重置到 now - interval 动画认为上一帧是 16.6ms 前,从而继续平滑过渡,掩盖了卡顿期间的时间断层。
与 View 相关的耗时统计
核心分类逻辑:通过 callbackType 实现“打标签”

Choreographer 内部维护了一个数组:

1
2
3
4
5
6
7
/**
* 必须与下面的 CALLBACK_* 整数保持同步,用于索引此数组。
* @hide
*/
private static final String[] CALLBACK_TRACE_TITLES = {
"input", "animation", "insets_animation", "traversal", "commit"
};

doCallbacks(int callbackType, ...) 被调用时,代码执行的是: Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);

  • 对于 Input:传入 callbackType = 0,字符串就是 "input"
  • 对于 Animation:传入 callbackType = 1,字符串就是 "animation"
核心对应关系表
CALLBACK_TRACE_TITLES (Trace 标签) FrameMetrics 对应指标 (Duration) 核心职能
“input” INPUT_HANDLING_DURATION 处理 MotionEvent、按键事件等。
“animation” ANIMATION_DURATION 处理 ValueAnimator 等动画逻辑。
“insets_animation” INSETS_ANIMATION_DURATION 处理窗口插屏(如键盘弹出)动画。
“traversal” LAYOUT_MEASURE_DURATION 执行 performTraversals,即 Measure + Layout。
“commit” SYNC_DURATION 同步 RenderNode 数据到渲染线程 (RenderThread)。
核心负载:Choreographer.FrameData

封装了 VSYNC 信号的时间戳和多组呈现时间线(Frame Timeline),这是现代 Android 系统平滑显示的核心数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* {@link VsyncCallback} 的负载,包括帧信息(例如开始渲染帧的时间)
* 以及多个可能的帧时间轴及其信息(包括截止时间和预期呈现时间)。
*/
public static class FrameData {
// 当前帧的时间戳(纳秒),用于动画计算的基准
private long mFrameTimeNanos;
// 包含多个候选的帧时间轴。每个时间轴对应不同的预期呈现时间
private FrameTimeline[] mFrameTimelines;
// 系统推荐的时间轴索引。如果应用无法在 16.6ms 内完成,可能会调整此索引
private int mPreferredFrameTimelineIndex;
// 标记当前是否处于 vsync 回调处理流程中,防止非法访问
private boolean mInCallback = false;

FrameData() {
// 初始化时间轴数组,容量通常由 DisplayEventReceiver 决定
allocateFrameTimelines(DisplayEventReceiver.VsyncEventData.FRAME_TIMELINES_CAPACITY);
}

/** 开始渲染帧的时间(以纳秒为单位)。 */
public long getFrameTimeNanos() {
checkInCallback();
return mFrameTimeNanos;
}

/** 可能的帧时间轴,按时间顺序排序。包含不同刷新率下的预测数据。 */
@NonNull
@SuppressLint("ArrayReturn") // 为了 API 一致性和速度。
public FrameTimeline[] getFrameTimelines() {
checkInCallback();
return mFrameTimelines;
}

/** 平台首选的帧时间轴。应用应优先尝试达到此时间轴定义的 deadline。 */
@NonNull
public FrameTimeline getPreferredFrameTimeline() {
checkInCallback();
return mFrameTimelines[mPreferredFrameTimelineIndex];
}

/** 设置回调状态标记 */
void setInCallback(boolean inCallback) {
mInCallback = inCallback;
for (int i = 0; i < mFrameTimelines.length; i++) {
mFrameTimelines[i].setInCallback(inCallback);
}
}

/** 安全检查,确保 FrameData 仅在 VSYNC 回调期间被有效读取 */
private void checkInCallback() {
if (!mInCallback) {
throw new IllegalStateException(
"FrameData 在 vsync 回调之外无效");
}
}

/** 分配时间轴存储空间 */
private void allocateFrameTimelines(int length) {
// 维护一个默认帧时间轴以保持 API(如 getFrameTimelines 和 getPreferredFrameTimeline)的一致性。
length = Math.max(1, length);

if (mFrameTimelines == null || mFrameTimelines.length != length) {
mFrameTimelines = new FrameTimeline[length];
for (int i = 0; i < mFrameTimelines.length; i++) {
mFrameTimelines[i] = new FrameTimeline();
}
}
}

/**
* 正常更新:使用从底层 DisplayEventReceiver 接收的 Vsync 数据同步更新。
*/
FrameTimeline update(
long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
allocateFrameTimelines(vsyncEventData.frameTimelinesLength);
mFrameTimeNanos = frameTimeNanos;
mPreferredFrameTimelineIndex = vsyncEventData.preferredFrameTimelineIndex;
for (int i = 0; i < mFrameTimelines.length; i++) {
DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline =
vsyncEventData.frameTimelines[i];
mFrameTimelines[i].update(frameTimeline.vsyncId,
frameTimeline.expectedPresentationTime, frameTimeline.deadline);
}
return mFrameTimelines[mPreferredFrameTimelineIndex];
}

/**
* 掉帧后补偿更新:当主线程延迟启动(jitterNanos > frameInterval)时,重新寻找一个有效的时间轴。
*
* @param frameTimeNanos 修正后的帧时间
* @param displayEventReceiver 用于在本地缓存失效时向系统索要最新 Vsync 数据
* @param jitterNanos 实际开始时间与预定时间的偏差(抖动量)
*/
FrameTimeline update(
long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) {
int newPreferredIndex = 0;
// 计算新的最低可接受截止时间
final long minimumDeadline =
mFrameTimelines[mPreferredFrameTimelineIndex].mDeadlineNanos + jitterNanos;

// 1. 在现有时间轴中查找一个 deadline 尚未过期的时间轴
while (newPreferredIndex < mFrameTimelines.length - 1
&& mFrameTimelines[newPreferredIndex].mDeadlineNanos < minimumDeadline) {
newPreferredIndex++;
}

long newPreferredDeadline = mFrameTimelines[newPreferredIndex].mDeadlineNanos;

// 2. 如果现有的时间轴全部已经过期(主线程卡顿太久),则发起 Binder 跨进程调用
// 获取 SurfaceFlinger 端最新的 VsyncEventData
if (newPreferredDeadline < minimumDeadline) {
DisplayEventReceiver.VsyncEventData latestVsyncEventData =
displayEventReceiver.getLatestVsyncEventData();
if (latestVsyncEventData == null) {
Log.w(TAG, "无法获取最新的 VsyncEventData。SurfaceFlinger 是否崩溃了?");
} else {
update(frameTimeNanos, latestVsyncEventData);
}
} else {
// 3. 否则,直接更新为本地查找到的更靠后的有效时间轴
update(frameTimeNanos, newPreferredIndex);
}
return mFrameTimelines[mPreferredFrameTimelineIndex];
}

/** 内部状态更新 */
void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) {
mFrameTimeNanos = frameTimeNanos;
mPreferredFrameTimelineIndex = newPreferredFrameTimelineIndex;
}
}
遍历起点:ViewRootImpl.performTraversals()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
private void performTraversals() {
// 缓存 mView 到本地变量 host,因为在整个遍历过程中会频繁使用,且需要保证一致性
final View host = mView;
// 如果调试模式开启,打印调试信息
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}

// 检查 host 是否为空或者是否尚未添加,如果满足其中之一,记录原因并直接返回
if (host == null || !mAdded) {
mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";
return;
}

// 如果存在同步请求导致的暂停计数,则记录 Trace 事件并跳过本次遍历
if (mNumPausedForSync > 0) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW,
TextUtils.formatSimple("performTraversals#mNumPausedForSync=%d",
mNumPausedForSync));
}
if (DEBUG_BLAST) {
Log.d(mTag, "Skipping traversal due to sync " + mNumPausedForSync);
}
mLastPerformTraversalsSkipDrawReason = "paused_for_sync";
return;
}

// 标记当前正在遍历中
mIsInTraversal = true;
// 标记即将进行绘制
mWillDrawSoon = true;
// 绘制取消标记及原因初始化
boolean cancelDraw = false;
String cancelReason = null;
// 是否为同步请求标记
boolean isSyncRequest = false;

// 窗口大小是否可能发生变化的标志位
boolean windowSizeMayChange = false;

// 获取窗口当前的布局参数
WindowManager.LayoutParams lp = mWindowAttributes;

// 期望的窗口宽高
int desiredWindowWidth;
int desiredWindowHeight;

// 获取当前视图可见性及其原因
final int viewVisibility = getHostVisibility();
final String viewVisibilityReason = getHostVisibilityReason();
// 检查可见性是否改变:非首次且 (记录的可见性不等 或 需要新 Surface 或 应用可见性改变)
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
// 重置应用可见性改变标记
mAppVisibilityChanged = false;
// 检查用户感知的可见性状态是否发生了实质性切换(VISIBLE 与非 VISIBLE 之间)
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
// 是否应该优化测量过程
final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);

// 初始化 relayout 相关的参数和帧引用
WindowManager.LayoutParams params = null;
Rect frame = mWinFrame;
// 如果是第一次执行 performTraversals
if (mFirst) {
// 第一次必须执行完整重绘和布局
mFullRedrawNeeded = true;
mLayoutRequested = true;

// 获取资源配置
final Configuration config = getConfiguration();
// 根据布局参数确定期望的窗口尺寸
if (shouldUseDisplaySize(lp)) {
// 系统代码:直接获取屏幕真实物理尺寸
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// Wrap content 情况,获取系统栏内边距后的可用边界
final Rect bounds = getWindowBoundsInsetSystemBars();
desiredWindowWidth = bounds.width();
desiredWindowHeight = bounds.height();
} else {
// 默认使用窗口管理器的 frameHint 尺寸,减少后续 relayout 导致的重测
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
}

// 现代窗口默认 32 位,设置绘制标志位
mAttachInfo.mUse32BitDrawingCache = true;
// 初始化 AttachInfo 中的可见性状态
mAttachInfo.mWindowVisibility = viewVisibility;
// 禁止全局属性重计算
mAttachInfo.mRecomputeGlobalAttributes = false;
// 备份当前配置
mLastConfigurationFromResources.setTo(config);
// 备份系统 UI 可见性
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// 若初始方向为继承,则设置宿主视图的布局方向
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
// 分发视图附加到窗口的消息,这会触发所有子视图的 onAttachedToWindow
host.dispatchAttachedToWindow(mAttachInfo, 0);
// 通知 TreeObserver 窗口已附加
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
// 首次应用 Insets
dispatchApplyInsets(host);
// 如果未启用新版返回拦截且无自定义回调,则注册兼容性返回键回调
if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()
&& mWindowlessBackKeyCallback == null) {
registerCompatOnBackInvokedCallback();
}
} else {
// 非首次,直接取当前帧的宽高
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
// 如果宽高与当前记录的不符,标记需要布局和重绘
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}

// 处理可见性变化后的分发和资源清理
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
mAttachInfo.mTreeObserver.dispatchOnWindowVisibilityChange(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
// 若不可见或需要新 Surface,结束缩放并销毁硬件资源
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}

// 可见时尝试提升帧率
if (shouldEnableDvrr() && viewVisibility == View.VISIBLE) {
boostFrameRate(FRAME_RATE_BOOST_TIME);
}
}

// 不可见窗口清理辅助功能焦点
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}

// 执行运行队列中的动作(如 View.post)
getRunQueue().executeActions(mAttachInfo.mHandler);

// 首次遍历初始化触摸模式
if (mFirst) {
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
}

// 测量阶段:若请求了布局且非停止状态
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
if (!mFirst) {
// 非首次,针对 wrap content 再次评估期望宽高
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;

if (shouldUseDisplaySize(lp)) {
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
final Rect bounds = getWindowBoundsInsetSystemBars();
desiredWindowWidth = bounds.width();
desiredWindowHeight = bounds.height();
}
}
}

// 核心测量逻辑:计算视图树的大小
windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
}

// 收集并可能更新窗口属性
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}

// 软键盘调整模式的动态确定
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode & SOFT_INPUT_MASK_ADJUST;
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i<N; i++) {
if (mAttachInfo.mScrollContainers.get(i).isShown()) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
}
if (resizeMode == 0) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
}
if ((lp.softInputMode & SOFT_INPUT_MASK_ADJUST) != resizeMode) {
lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode;
params = lp;
}
}
}

// Insets 变更后的重新分发和测量拦截
if (mApplyInsetsRequested) {
dispatchApplyInsets(host);
if (mLayoutRequested) {
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
shouldOptimizeMeasure);
}
}

// 清除标记,防止重复测量
if (layoutRequested) {
mLayoutRequested = false;
}

// 确定窗口是否真的需要 Resize(基于测量结果和属性)
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mPendingDragResizing;

// 内部 Insets 计算标记
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;

boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;

final int surfaceGenerationId = mSurface.getGenerationId();

final boolean isViewVisible = viewVisibility == View.VISIBLE;
boolean surfaceSizeChanged = false;
boolean surfaceCreated = false;
boolean surfaceDestroyed = false;
boolean surfaceReplaced = false;

// 检查属性变更
final boolean windowAttributesChanged = mWindowAttributesChanged;
if (windowAttributesChanged) {
mWindowAttributesChanged = false;
params = lp;
}

// relayout 前的参数适配
if (params != null) {
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
&& !PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
adjustLayoutParamsForCompatibility(params,
mInsetsController.getAppearanceControlled(),
mInsetsController.isBehaviorControlled());
controlInsetsForCompatibility(params);
if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
}
}

// 执行 relayoutWindow 与 WMS 通信的核心逻辑
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
|| mForceNextWindowRelayout) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
TextUtils.formatSimple("%s-relayoutWindow#"
+ "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag,
mFirst, windowShouldResize, viewVisibilityChanged, params != null,
mForceNextWindowRelayout));
}

mForceNextWindowRelayout = false;

// Insets 挂起设置
insetsPending = computesInternalInsets
&& mWindowAttributes.providedInsets == null;

if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
}

boolean hwInitialized = false;
boolean dispatchApplyInsets = false;
boolean hadSurface = mSurface.isValid();

try {
if (DEBUG_LAYOUT) {
Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
host.getMeasuredHeight() + ", params=" + params);
}

if (mFirst || viewVisibilityChanged) {
mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
}
// 【核心 IPC】调用 Session.relayout 更新窗口状态
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
// 检查是否取消并重绘
cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
== RELAYOUT_RES_CANCEL_AND_REDRAW;
cancelReason = "relayout";
final boolean dragResizing = mPendingDragResizing;
// 处理同步请求
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
if (DEBUG_BLAST) {
Log.d(mTag, "Relayout called with blastSync");
}
reportNextDraw("relayout");
mSyncBuffer = true;
isSyncRequest = true;
if (!cancelDraw) {
mDrewOnceForSync = false;
}
}

final boolean surfaceControlChanged =
(relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
== RELAYOUT_RES_SURFACE_CHANGED;

if (mSurfaceControl.isValid()) {
updateOpacity(mWindowAttributes, dragResizing,
surfaceControlChanged /*forceUpdate */);
}
} catch (RemoteException e) {
} finally {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

if (DEBUG_ORIENTATION) Log.v(
TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);

// 更新 AttachInfo 中的窗口偏移
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;

// 修正本地宽高记录
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}

// 同步 SurfaceHolder 的回调和状态
if (mSurfaceHolder != null) {
if (mSurface.isValid()) {
mSurfaceHolder.mSurface = mSurface;
}
mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
mSurfaceHolder.mSurfaceLock.unlock();
if (surfaceCreated) {
mSurfaceHolder.ungetCallbacks();

mIsCreating = true;
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
}

if ((surfaceCreated || surfaceReplaced || surfaceSizeChanged
|| windowAttributesChanged) && mSurface.isValid()) {
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, lp.format,
mWidth, mHeight);
}
}
mIsCreating = false;
}

if (surfaceDestroyed) {
notifyHolderSurfaceDestroyed();
mSurfaceHolder.mSurfaceLock.lock();
try {
mSurfaceHolder.mSurface = new Surface();
} finally {
mSurfaceHolder.mSurfaceLock.unlock();
}
}
}

// 配置 ThreadedRenderer
final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
if (threadedRenderer != null && threadedRenderer.isEnabled()) {
if (hwInitialized
|| mWidth != threadedRenderer.getWidth()
|| mHeight != threadedRenderer.getHeight()
|| mNeedsRendererSetup) {
threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
mNeedsRendererSetup = false;
}
}

// Relayout 后的最终测量:处理因 relayout 导致的尺寸差异
if (!mStopped || mReportNextDraw) {
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
|| dispatchApplyInsets || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
lp.privateFlags);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
lp.privateFlags);

if (DEBUG_LAYOUT) Log.v(mTag, "糟糕,出了一些变化! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " dispatchApplyInsets=" + dispatchApplyInsets);

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

// 实现权重分配逻辑
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;

if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}

if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"嘿,让我们再测量一次:width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

layoutRequested = true;
}
}
} else {
// 简单处理移动
maybeHandleWindowMove(frame);
}

// 处理之前推迟的测量
if (mViewMeasureDeferred) {
performMeasure(
MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
}

// 布局阶段判定
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);

if (didLayout) {
// 执行真正的布局分发
performLayout(lp, mWidth, mHeight);
}

// 处理焦点请求
if (mFirst) {
if (sAlwaysAssignFocus || !isInTouchMode()) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
}
if (mView != null) {
if (!mView.hasFocus()) {
mView.restoreDefaultFocus();
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: requested focused view=" + mView.findFocus());
}
} else {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: existing focused view=" + mView.findFocus());
}
}
}
} else {
View focused = mView.findFocus();
if (focused instanceof ViewGroup
&& ((ViewGroup) focused).getDescendantFocusability()
== ViewGroup.FOCUS_AFTER_DESCENDANTS) {
focused.restoreDefaultFocus();
}
}

if (shouldEnableDvrr()) {
boostFrameRate(FRAME_RATE_BOOST_TIME);
}
}

// 触发辅助功能状态改变事件
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
if (changedVisibility) {
maybeFireAccessibilityWindowStateChangedEvent();
}

// 重置首次遍历和准备绘制标记
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;

// 通知 IME
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);

// 首次 relayout 报告
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw("first_relayout");
}

mCheckIfCanDraw = isSyncRequest || cancelDraw;

// 【关键点】PreDraw 回调分发,允许在此拦截并取消绘制
boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);

// 如果不取消,准备同步和开始绘制通知
if (!cancelAndRedraw) {
if (mActiveSurfaceSyncGroup != null) {
mSyncBuffer = true;
}

createSyncIfNeeded();
notifyDrawStarted(isInWMSRequestedSync());
mDrewOnceForSync = true;

if (mActiveSurfaceSyncGroup != null && mSyncBuffer) {
updateSyncInProgressCount(mActiveSurfaceSyncGroup);
safeguardOverlappingSyncs(mActiveSurfaceSyncGroup);
}
}

// 最终绘制决策
if (!isViewVisible) {
// 不可见,跳过绘制
if (mLastTraversalWasVisible) {
logAndTrace("Not drawing due to not visible. Reason=" + viewVisibilityReason);
}
mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}

handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
mPendingTransaction, "view not visible");
} else if (cancelAndRedraw) {
// 被取消绘制,重新请求遍历
if (!mWasLastDrawCanceled) {
logAndTrace("Canceling draw."
+ " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+ " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
}
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
: "cancel_" + cancelReason;
scheduleTraversals();
} else {
// 执行绘制
if (mWasLastDrawCanceled) {
logAndTrace("Draw frame after cancel");
}
if (!mLastTraversalWasVisible) {
logAndTrace("Start draw after previous draw not visible");
}
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 【核心】执行绘制流程
if (!performDraw(mActiveSurfaceSyncGroup)) {
handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
mPendingTransaction, mLastPerformDrawSkippedReason);
}
}
// 状态位更新
mWasLastDrawCanceled = cancelAndRedraw;
mLastTraversalWasVisible = isViewVisible;

if (mAttachInfo.mContentCaptureEvents != null) {
notifyContentCaptureEvents();
}

mIsInTraversal = false;
mRelayoutRequested = false;

// 善后清理
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
mActiveSurfaceSyncGroup = null;
if (mHasPendingTransactions) {
mPendingTransaction.apply();
mHasPendingTransactions = false;
}
mSyncBuffer = false;
if (isInWMSRequestedSync()) {
mWmsRequestSyncGroup.markSyncReady();
mWmsRequestSyncGroup = null;
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}

// VRR 帧率动态调整逻辑
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
mInvalidationIdleMessagePosted = true;
mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
}
// 设定最终的首选帧率和类别
setCategoryFromCategoryCounts();
updateInfrequentCount();
updateFrameRateFromThreadedRendererViews();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
if (mPreferredFrameRate > 0
|| (mLastPreferredFrameRate != 0 && mPreferredFrameRate == 0)
) {
mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
FRAME_RATE_SETTING_REEVALUATE_TIME);
}
// 计数器衰减
mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
} else if (mPreferredFrameRate == 0) {
// 重置
setPreferredFrameRate(0);
mPreferredFrameRate = -1;
}
}
指令录制:ThreadedRenderer.draw()

将 UI 线程的 Java 代码绘制逻辑录制为 Native 层的 DisplayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 绘制指定的视图。
*
* @param view 要绘制的视图(通常是 DecorView)。
* @param attachInfo 绑定到该视图的 AttachInfo 包含渲染所需的状态信息。
* @param callbacks 绘制过程中的回调接口。
*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
// 1. 记录绘制开始的时间点,用于性能监控和 FrameMetrics 统计
attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();

// 2. 更新根显示列表(DisplayList)。
// 这一步会遍历视图树,录制每个 View 的绘制指令(如 drawRect, drawText 等)。
updateRootDisplayList(view, callbacks);

// 3. 处理在渲染器创建之前就开始执行的动画。
// 典型的场景是在第一帧绘制前就启动了动画,这些动画节点需要注册到当前的硬件渲染器中。
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
// 将待处理的动画渲染节点注册到渲染引擎
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
// 清空并释放待处理列表
attachInfo.mPendingAnimatingRenderNodes.clear();
// 之后的动画会通过 ViewRootImpl#attachRenderNodeAnimator 直接注册,不再需要此暂存列表
attachInfo.mPendingAnimatingRenderNodes = null;
}

// 4. 获取当前帧最新的信息(包含各种时间戳和状态)
final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();

// 5. 核心步骤:同步并绘制。
// 此函数会将 UI 线程录制的绘制信息“同步”到渲染线程(RenderThread),
// 并触发 GPU 开始执行实际的绘制操作。
int syncResult = syncAndDrawFrame(frameInfo);

// 6. 错误处理:如果 Surface 丢失
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
Log.w("HWUI", "Surface 丢失,强制进行重新布局 (relayout)");
// 标记下次遍历时强制执行 WindowManager 的 relayout,
// 期望从中获取一个新的、有效的 Surface。
attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
attachInfo.mViewRootImpl.requestLayout();
}

// 7. 调度重绘:如果同步结果要求重绘
// 例如:动画正在运行,或者渲染引擎需要再画一帧来更新状态。
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}

Native 层:RenderThread 异步渲染 (frameworks/base/libs/hwui/)

资源同步:CanvasContext::prepareTree()

在正式绘制前,将 UI 线程录制的变化应用到 Native 渲染树中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* 准备渲染树(Sync 阶段)。
* 此方法在渲染线程(RenderThread)中执行,负责将 UI 线程的变更同步到渲染节点。
*
* @param info 渲染树信息,用于存储同步过程中的各种状态(如是否有动画、脏区等)
* @param uiFrameInfo 指向 UI 线程传递过来的帧时间戳数据的指针
* @param syncQueued 同步请求进入队列的时间点
* @param target 目标渲染节点(通常是根节点)
*/
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
RenderNode* target) {
// 1. 清除之前可能存在的帧回调,准备开始新的帧流程
mRenderThread.removeFrameCallback(this);

// 2. 检查上一帧是否被跳过,并维护跳帧信息
const auto reason = wasSkipped(mCurrentFrameInfo);
if (reason.has_value()) {
if (!mSkippedFrameInfo) {
switch (*reason) {
case SkippedFrameReason::AlreadyDrawn:
case SkippedFrameReason::NoBuffer:
case SkippedFrameReason::NoOutputTarget:
// 记录因技术原因(非业务逻辑)跳过的帧信息,用于后续的 FrameTimeline 统计
mSkippedFrameInfo.emplace();
mSkippedFrameInfo->vsyncId =
mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
mSkippedFrameInfo->startTime =
mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
break;
case SkippedFrameReason::DrawingOff:
case SkippedFrameReason::ContextIsStopped:
case SkippedFrameReason::NothingToDraw:
break;
}
}
} else {
// 如果上一帧正常执行,则开启 JankTracker 对新一帧的监控
mCurrentFrameInfo = mJankTracker.startFrame();
mSkippedFrameInfo.reset();
}

// 3. 导入 UI 线程的时间信息,并记录同步开始的时间戳
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
mCurrentFrameInfo->markSyncStart();

// 4. 初始化同步环境
info.damageAccumulator = &mDamageAccumulator; // 用于累加每一层产生的脏区
info.layerUpdateQueue = &mLayerUpdateQueue; // 待更新的硬件图层队列
info.damageGenerationId = mDamageId++;
info.out.skippedFrameReason = std::nullopt;

// 5. 遍历渲染节点树,执行实际的同步操作(核心递归逻辑)
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
// 如果节点是目标节点,执行完整同步;否则仅同步渲染线程相关的部分
info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
node->prepareTree(info); // 这里会递归调用所有子 View 的同步逻辑
GL_CHECKPOINT(MODERATE);
}
// 运行剩余的渲染线程动画(如 RenderNodeAnimator)
mAnimationContext->runRemainingAnimations(info);
GL_CHECKPOINT(MODERATE);

// 6. 释放预取但不再需要的图层
freePrefetchedLayers();
GL_CHECKPOINT(MODERATE);

mIsDirty = true;

// 7. 各种跳帧预判(性能优化)

// 7.1 检查是否有渲染输出目标(Surface)
if (CC_UNLIKELY(!hasOutputTarget())) {
info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
return;
}

// 7.2 检查是否已经在极短时间内(2ms内)画过同一帧 Vsync,避免重复渲染
if (CC_LIKELY(mSwapHistory.size() && !info.forceDrawFrame)) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
SwapHistory& lastSwap = mSwapHistory.back();
nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
if (vsyncDelta < 2_ms) {
info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
}
} else {
info.out.skippedFrameReason = std::nullopt;
}

// 7.3 检查内容是否可渲染(例如只有 2 个节点且第二个节点内容为空)
if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
}

// 8. 关键步骤:尝试从 BufferQueue 预留(reserve)一个缓冲区
if (!info.out.skippedFrameReason) {
int err = mNativeSurface->reserveNext();
if (err != OK) {
// 如果拿不到 Buffer(通常是 SurfaceFlinger 消费太慢),标记本帧跳过
info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
ALOGW("reserveNext 失败,错误 = %d (%s)", err, strerror(-err));
if (err != TIMED_OUT) {
setSurface(nullptr); // 严重错误则断开 Surface
return;
}
}
} else {
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
}

// 9. 处理动画和重绘逻辑
bool postedFrameCallback = false;
if (info.out.hasAnimations || info.out.skippedFrameReason) {
// 如果渲染线程有动画,或者由于某种原因本帧没画,需要请求下一帧
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
}
if (!info.out.requiresUiRedraw) {
// 在渲染线程注册一个 Vsync 回调,以驱动动画持续运行
mRenderThread.postFrameCallback(this);
postedFrameCallback = true;
}
}

// 10. 处理动图(如 GIF/WebP)的播放延迟逻辑
if (!postedFrameCallback &&
info.out.animatedImageDelay != TreeInfo::Out::kNoAnimatedImageDelay) {
const nsecs_t kFrameTime = mRenderThread.timeLord().frameIntervalNanos();
if (info.out.animatedImageDelay <= kFrameTime) {
// 如果延迟小于一帧时间,立即请求下一帧
mRenderThread.postFrameCallback(this);
} else {
// 否则,在指定延迟后请求重绘
const auto delay = info.out.animatedImageDelay - kFrameTime;
int genId = mGenerationID;
mRenderThread.queue().postDelayed(delay, [this, genId]() {
if (mGenerationID == genId) {
mRenderThread.postFrameCallback(this);
}
});
}
}
}

绘制与提交:CanvasContext::draw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/**
* 执行实际的绘制操作。
* 这是渲染管线的核心阶段:计算脏区 -> 调用 GPU 绘制 -> 交换缓冲区 -> 报告性能指标。
*
* @param solelyTextureViewUpdates 是否仅更新 TextureView(影响性能提示策略)
*/
void CanvasContext::draw(bool solelyTextureViewUpdates) {
#ifdef __ANDROID__
// 1. 安全检查:确保 GPU 上下文(GrContext)可用且未丢失
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
if (grContext->isDeviceLost()) {
LOG_ALWAYS_FATAL("意外丢失 GPU 设备");
return;
}
LOG_ALWAYS_FATAL("在 CanvasContext::draw 开始时 GrContext 已被放弃");
return;
}
}
#endif

// 2. 计算并获取当前帧的脏区(需要重新绘制的矩形区域)
SkRect dirty;
mDamageAccumulator.finish(&dirty);

// 暂存并重置同步延迟和空闲时长(用于后续性能计算)
nsecs_t syncDelayDuration = mSyncDelayDuration;
nsecs_t idleDuration = mIdleDuration;
mSyncDelayDuration = 0;
mIdleDuration = 0;

// 3. 跳帧逻辑检查:判断这一帧是否真的需要绘制
const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
if (!Properties::isDrawingEnabled()) {
return SkippedFrameReason::DrawingOff; // 全局绘制已关闭
}
// 如果脏区为空,且允许跳过空帧,且 Surface 不需要强制重绘,则跳过
if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
return SkippedFrameReason::NothingToDraw;
}
return std::nullopt;
}();

if (skippedFrameReason) {
// 如果决定跳过本帧,记录原因并清理状态
mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
#ifdef __ANDROID__
if (auto grContext = getGrContext()) {
grContext->flushAndSubmit(); // 依然提交之前的异步任务
}
#endif
waitOnFences(); // 等待之前的栅栏同步完成
for (auto& func : mFrameCommitCallbacks) {
std::invoke(func, false /* didProduceBuffer: 未生成新缓冲区 */);
}
mFrameCommitCallbacks.clear();
return;
}

// 4. 开始正式绘制流程
ScopedActiveContext activeContext(this);
mCurrentFrameInfo->set(FrameInfoIndex::FrameInterval) =
mRenderThread.timeLord().frameIntervalNanos();

// 标记:开始向 GPU 发送绘制命令
mCurrentFrameInfo->markIssueDrawCommandsStart();

// 获取当前待渲染的 Frame 对象(此处触发 Surface::dequeueBuffer(),是潜在的强阻塞点)
Frame frame = getFrame();

// 计算相对于窗口的最终脏区
SkRect windowDirty = computeDirtyRect(frame, &dirty);

ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));

// 5. 调用渲染管线(SkiaGL/Vulkan)执行真正的绘制命令提交
IRenderPipeline::DrawResult drawResult;
{
drawResult = mRenderPipeline->draw(
frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}

uint64_t frameCompleteNr = getFrameNumber();

// 等待 GPU 指令执行的同步信号
waitOnFences();

// 6. 向底层系统窗口设置帧时间轴信息(用于同步 Vsync 和处理输入事件延迟)
if (mNativeSurface) {
const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
const ANativeWindowFrameTimelineInfo ftl = {
.frameNumber = frameCompleteNr,
.frameTimelineVsyncId = vsyncId,
.inputEventId = inputEventId,
.startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
.useForRefreshRateSelection = solelyTextureViewUpdates,
.skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
: UiFrameInfoBuilder::INVALID_VSYNC_ID,
.skippedFrameStartTimeNanos =
mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
};
native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
}
}

bool requireSwap = false; // 是否需要交换缓冲区(将画面提交给屏幕)
bool didDraw = false; // 是否成功绘制

// 7. 提交缓冲区(Swap Buffers)
int error = OK;
bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
&requireSwap);

// 记录 GPU 指令提交完成的时间点
mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));

mIsDirty = false;

if (requireSwap) {
didDraw = true;
error = mNativeSurface->getAndClearError();
if (error == TIMED_OUT) {
// 如果获取 Buffer 超时,请求下一帧重试
mRenderThread.postFrameCallback(this);
didDraw = false;
} else if (error != OK || !didSwap) {
// 如果发生严重错误,断开 Surface 绑定
setSurface(nullptr);
didDraw = false;
}

// 记录交换历史,用于性能追溯
SwapHistory& swap = mSwapHistory.next();
if (didDraw) {
swap.damage = windowDirty;
} else {
float max = static_cast<float>(INT_MAX);
swap.damage = SkRect::MakeWH(max, max);
}
swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC);
swap.vsyncTime = mRenderThread.timeLord().latestVsync();

// 记录 Dequeue(出队)和 Queue(入队)Buffer 的耗时,这是 Jank 的常见原因
if (didDraw) {
nsecs_t dequeueStart =
ANativeWindow_getLastDequeueStartTime(mNativeSurface->getNativeWindow());
if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
swap.dequeueDuration = 0;
} else {
swap.dequeueDuration =
ANativeWindow_getLastDequeueDuration(mNativeSurface->getNativeWindow());
}
swap.queueDuration =
ANativeWindow_getLastQueueDuration(mNativeSurface->getNativeWindow());
} else {
swap.dequeueDuration = 0;
swap.queueDuration = 0;
}
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
mHaveNewSurface = false;
mFrameNumber = 0;
} else {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
}

// 标记:缓冲区交换操作完成
mCurrentFrameInfo->markSwapBuffersCompleted();

// 如果开启了调试宏,计算并打印移动平均帧耗时
#if LOG_FRAMETIME_MMA
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F;
if (sFrameCount) {
sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
} else {
sBenchMma = thisFrame;
}
if (++sFrameCount == 10) {
sFrameCount = 1;
ALOGD("平均帧耗时: %.4f", sBenchMma);
}
#endif
// 执行所有注册的帧提交回调(通知应用层这一帧已经发给系统了)
if (didSwap) {
for (auto& func : mFrameCommitCallbacks) {
std::invoke(func, true /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
}

// 8. 上报 Jank(卡顿)数据和帧指标
if (requireSwap) {
if (mExpectSurfaceStats) {
// 如果支持 SurfaceStats,则异步报告包含 Present Time(实际显示时间)的指标
reportMetricsWithPresentTime();
{
std::lock_guard lock(mLast4FrameMetricsInfosMutex);
FrameMetricsInfo& next = mLast4FrameMetricsInfos.next();
next.frameInfo = mCurrentFrameInfo;
next.frameNumber = frameCompleteNr;
next.surfaceId = mSurfaceControlGenerationId;
}
} else {
// 否则直接结束当前帧记录
mCurrentFrameInfo->markFrameCompleted();
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
std::scoped_lock lock(mFrameInfoMutex);
mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
mSurfaceControlGenerationId);
}
}

// 9. 性能调节:向系统 HintSession 反馈实际工作负载
int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync);
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);

// 更新目标耗时(截止时间 - 预期 Vsync 时间)
mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);

if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
// 计算实际 CPU 工作耗时,排除掉等待 Buffer 和空闲的时间,以便更准地调整 CPU 频率
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
mHintSessionWrapper->reportActualWorkDuration(actualDuration);

// 如果有 WebView 渲染线程,也一并通知系统这些线程的活跃状态
mHintSessionWrapper->setActiveFunctorThreads(
WebViewFunctorManager::instance().getRenderingThreadsForActiveFunctors());
}

mLastDequeueBufferDuration = dequeueBufferDuration;

// 清理缓存管理器中的过期资源
mRenderThread.cacheManager().onFrameCompleted();
return;
}
COMMAND_ISSUE_DURATION (指令发布)时间的计算
计时起点

在代码第 40 行,它通过一个名为 markIssueDrawCommandsStart() 的函数打入了一个时间戳:

1
2
// 标记:开始向 GPU 发送绘制命令
mCurrentFrameInfo->markIssueDrawCommandsStart();
计时终点

在代码第 104 行,记录了命令提交完成的时间:

1
2
mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));

你可能会疑惑:为什么要取 drawResult.commandSubmissionTimemCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers) 的最大值?

这主要是为了防止 时间戳逻辑倒置

  • 理想情况: 命令提交(Submission)应该发生在 SwapBuffers 之前。
  • 实际情况(驱动行为): 在某些图形驱动实现中,SwapBuffers(交换缓冲区)操作实际上包含了隐含的“命令刷新(Flush)”。也就是说,虽然 HWUI 认为提交在 SwapBuffers 之前就完成了,但实际上驱动程序可能在 SwapBuffers 调用时才真正完成了最后一块指令的推送。
  • 为什么用 max 如果 drawResult.commandSubmissionTime(我们测量的提交完成时间)比 SwapBuffers 的时间还要早,这在统计上是没有问题的。但如果驱动程序的 SwapBuffers 动作比我们记录的提交时间还晚,为了保证性能数据的逻辑性(提交动作不可能在交换缓冲区之后才完成),系统强制将 CommandSubmissionCompleted 的时间点至少设为 SwapBuffers 的时间。

将渲染完成的 Buffer 提交给 SurfaceFlinger

SkiaOpenGLPipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/*
* Copyright (C) 2016 The Android Open Source Project
* ... (License header remains unchanged)
*/

#include "pipeline/skia/SkiaOpenGLPipeline.h"

#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
#include <cutils/properties.h>
#include <gui/TraceUtils.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/gl/GrGLTypes.h>
#include <strings.h>

#include "DeferredLayerUpdater.h"
#include "FrameInfo.h"
#include "LightingInfo.h"
#include "hwui/Bitmap.h"
#include "pipeline/skia/LayerDrawable.h"
#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaProfileRenderer.h"
#include "private/hwui/DrawGlInfo.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
#include "renderthread/Frame.h"
#include "renderthread/IRenderPipeline.h"
#include "utils/GLUtils.h"

using namespace android::uirenderer::renderthread;

namespace android {
namespace uirenderer {
namespace skiapipeline {

// 构造函数:初始化渲染流水线,并将自身注册到 RenderThread 的状态监听中
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
: SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
thread.renderState().registerContextCallback(this);
}

// 析构函数:移除上下文回调注册
SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
mRenderThread.renderState().removeContextCallback(this);
}

// 将当前的 EGL 上下文设置为当前线程的上下文
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);

// 如果 Surface 在之前(例如 trimMemory 内存修剪)被销毁,则需要在此处重建
if (mHardwareBuffer) {
mRenderThread.requireGlContext();
} else if (!isSurfaceReady() && mNativeWindow) {
setSurface(mNativeWindow.get(), mSwapBehavior);
}

EGLint error = 0;
// 尝试使当前 Surface 处于激活状态
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}

EGLint majorVersion = 0;
eglQueryContext(eglGetCurrentDisplay(), eglGetCurrentContext(), EGL_CONTEXT_CLIENT_VERSION, &majorVersion);

// 处理 GL ES 3.x 在默认帧缓冲区状态切换时的兼容性问题
// 当默认帧缓冲区从“无 Surface”切换到“有 Surface”时,不同厂商的实现对 Read/Draw Buffer 的状态定义不一致
// 这是一个 Khronos 规范中的已知模糊点,通过显式设置确保状态正确
if (error == 0 && (majorVersion > 2) && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
GLint curReadFB = 0;
GLint curDrawFB = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);

GLint buffer = GL_NONE;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
if (buffer == GL_NONE) {
const GLenum drawBuffer = GL_BACK;
glDrawBuffers(1, &drawBuffer);
}

glGetIntegerv(GL_READ_BUFFER, &buffer);
if (buffer == GL_NONE) {
glReadBuffer(GL_BACK);
}

glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);

GL_CHECKPOINT(LOW);
}

return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}

// 获取当前帧信息,准备开始渲染
Frame SkiaOpenGLPipeline::getFrame() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no surface!");
return mEglManager.beginFrame(mEglSurface);
}

// 核心渲染方法:执行每一帧的绘制操作
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {

// 如果不是在捕获 SKP 或者没有使用硬件缓冲区,则处理损坏区域(Damage Region),减少绘制消耗
if (!isCapturingSkp() && !mHardwareBuffer) {
mEglManager.damageFrame(frame, dirty);
}

// 设置 OpenGL 的帧缓冲区(FBO)信息
SkColorType colorType = getSurfaceColorType();
GrGLFramebufferInfo fboInfo;
fboInfo.fFBOID = 0; // 0 表示默认帧缓冲区

// 根据 Surface 的颜色格式配置 GL 格式
if (colorType == kRGBA_F16_SkColorType) {
fboInfo.fFormat = GL_RGBA16F;
} else if (colorType == kN32_SkColorType) {
fboInfo.fFormat = GL_RGBA8;
} else if (colorType == kRGBA_1010102_SkColorType) {
fboInfo.fFormat = GL_RGB10_A2;
} else if (colorType == kAlpha_8_SkColorType) {
fboInfo.fFormat = GL_R8;
} else {
LOG_ALWAYS_FATAL("Unsupported color type.");
}

// 创建 Skia 后端渲染目标
auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
STENCIL_BUFFER_SIZE, fboInfo);

SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
kUnknown_SkPixelGeometry);

SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface;
SkMatrix preTransform;

// 如果存在硬件缓冲区,则使用它创建 Surface,否则使用默认的后端渲染目标
if (mHardwareBuffer) {
surface = getBufferSkSurface(bufferParams);
preTransform = bufferParams.getTransform();
} else {
surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
getSurfaceOrigin(), colorType,
mSurfaceColorSpace, &props);
preTransform = SkMatrix::I();
}

// 计算光照信息
SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
LightGeometry localGeometry = lightGeometry;
localGeometry.center.x = lightCenter.fX;
localGeometry.center.y = lightCenter.fY;
LightingInfo::updateLighting(localGeometry, lightInfo);

// 执行实际的渲染流程
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
preTransform);

// 绘制调试用的视觉辅助工具(例如脏区域矩形、性能分析图表)
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = surface->getCanvas();
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}

// 提交 GPU 指令
{
ATRACE_NAME("flush commands");
skgpu::ganesh::FlushAndSubmit(surface);
}
layerUpdateQueue->clear();

// 调试模式下记录资源缓存使用情况
if (CC_UNLIKELY(Properties::debugLevel != kDebugDisabled)) {
dumpResourceCacheUsage();
}

return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
}

// 交换前后缓冲区,将渲染好的帧显示到屏幕上
bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
bool* requireSwap) {
GL_CHECKPOINT(LOW);

// 即使取消了渲染,为了保持帧率统计的连续性,也需标记交换缓冲区
currentFrameInfo->markSwapBuffers();

if (mHardwareBuffer) {
return false;
}

*requireSwap = drawResult.success || mEglManager.damageRequiresSwap();

// 执行底层的 swap 操作
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
return false;
}

return *requireSwap;
}

// 创建纹理层(用于支持 View 的 layer 功能)
DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() {
mRenderThread.requireGlContext();
return new DeferredLayerUpdater(mRenderThread.renderState());
}

// 当 GL 上下文销毁时,销毁 EGL Surface
void SkiaOpenGLPipeline::onContextDestroyed() {
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
}

// 停止渲染时,解绑当前 EGL Surface
void SkiaOpenGLPipeline::onStop() {
if (mEglManager.isCurrent(mEglSurface)) {
mEglManager.makeCurrent(EGL_NO_SURFACE);
}
}

// 设置 NativeWindow (Surface),即建立与屏幕或 Buffer 的连接
bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
mNativeWindow = surface;
mSwapBehavior = swapBehavior;

// 清除旧的 Surface
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}

// 创建新的 Surface
if (surface) {
mRenderThread.requireGlContext();
auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace);
if (!newSurface) {
return false;
}
mEglSurface = newSurface.unwrap();
}

// 设置缓冲区的保留策略
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
return true;
}

return false;
}

// 刷新指令并等待同步栅栏(Fence),确保 GPU 任务完成
[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() {
int fence = -1;
EGLSyncKHR sync = EGL_NO_SYNC_KHR;
mEglManager.createReleaseFence(true, &sync, &fence);

// 如果不支持原生栅栏,则使用 EGL 同步对象进行阻塞等待
if (sync != EGL_NO_SYNC_KHR) {
EGLDisplay display = mEglManager.eglDisplay();
EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000);
if (result == EGL_FALSE) {
ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x",
eglGetError());
} else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence");
}
eglDestroySyncKHR(display, sync);
}
return android::base::unique_fd(fence);
}

bool SkiaOpenGLPipeline::isSurfaceReady() {
return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
}

bool SkiaOpenGLPipeline::isContextReady() {
return CC_LIKELY(mEglManager.hasEglContext());
}

// 调用自定义 Functor(用于旧版 SurfaceView 或特定的 GL 绘制插件)
void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) {
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
if (thread.eglManager().hasEglContext()) {
mode = DrawGlInfo::kModeProcess;
}

(*functor)(mode, nullptr);

// 如果有 GL 上下文,需要重置 Skia 的状态,防止 Functor 污染 GL 状态机
if (mode != DrawGlInfo::kModeProcessNoContext) {
thread.getGrContext()->resetContext();
}
}

} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
SkiaOpenGLPipeline 职能

通过 mEglManager.swapBuffers(...) 将绘制好的 Buffer 放入了 BufferQueue。
SurfaceFlinger (系统侧): 在下一次 VSync 到来时,感知到 BufferQueue 中有了新数据。
合成动作: SurfaceFlinger 将你提交的这个 Buffer,与其他所有可见层(如状态栏)合并。
最终显示: 将合成后的结果发给屏幕控制器。

flush commands 耗时监控

ATRACE_NAME 利用了 C++ 的 RAII (Resource Acquisition Is Initialization) 机制。你不需要手动写“开始”和“结束”,它会自动管理生命周期:

  • 执行宏时: 它会在当前作用域创建一个临时的 Trace 对象,内部调用 ATRACE_BEGIN("flush commands"),告诉系统:“我要开始记录了,名字叫 flush commands”。
  • 离开作用域时: 当代码执行到该大括号 {} 的末尾,临时对象被销毁,自动调用 ATRACE_END(),告诉系统:“这个动作结束了”。
1
2
3
4
{
ATRACE_NAME("flush commands"); // 在这里调用 ATRACE_BEGIN
skgpu::ganesh::FlushAndSubmit(surface);
} // 在这里自动触发 ATRACE_END

这意味着系统会自动计算 FlushAndSubmit 这一行代码到底消耗了多少微秒/毫秒。

COMMAND_ISSUE_DURATION 和 ATRACE_NAME(“flush commands”) 耗时的不同

COMMAND_ISSUE_DURATION 的定义是:从“开始处理绘制指令”到“指令提交完毕”的完整周期。

  • COMMAND_ISSUE_DURATION = [绘制指令生成 (CPU)] + [FlushAndSubmit (指令提交)]

ATRACE_NAME("flush commands") 仅仅覆盖了其中的后半部分:

  • ATRACE("flush commands") = [仅 FlushAndSubmit 这一步的耗时]
SWAP_BUFFERS_DURATION (将渲染完成的 Buffer 提交给 SurfaceFlinger。)
计时开始 (Start)
  • 在哪里出发: SkiaOpenGLPipeline::swapBuffers() 函数中。
  • 动作: 当代码执行到 currentFrameInfo->markSwapBuffers() 这一行时,它会立刻记录下当前的系统时间戳,作为一个“记号”。
  • 含义: “计时开始,现在开始进行缓冲区交换。”
计时结束与计算 (End)
  • 在哪里结束: JankTracker::finishFrame() 函数中。
  • 动作: 这一帧的所有渲染工作全部完成后,系统会调用这个函数来“结算”这一帧的表现。

底层图形系统 (frameworks/native/libs/gui/)

关键阻塞点:Surface::dequeueBuffer()

该函数在 CanvasContext::draw() 执行过程中被调用(对应源码中的 Frame frame = getFrame(); 这一行),用于向 BufferQueue 索要一个可写的图形缓冲区。

  • 阻塞逻辑: 如果当前所有的 Buffer 都在 SurfaceFlinger 端等待显示,或者 GPU 上一帧还没跑完导致没有空闲 Buffer,渲染线程会在此处挂起。这是全链路中最著名的潜在阻塞点
  • 监控: 通过 dumpsys gfxinfo 查看到的 Dequeue Buffer 耗时(或 Trace 中的 DequeueBufferDuration)即代表此处的等待时长。较高的数值通常意味着系统负载极重、SurfaceFlinger 合成压力大或 GPU 性能达到瓶颈。

JankTracker 源代码(Android 15)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
#include "JankTracker.h"

#include <cutils/ashmem.h>
#include <cutils/trace.h>
#include <errno.h>
#include <inttypes.h>
#include <log/log.h>

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
#include <sstream>

#include "DeviceInfo.h"
#include "Properties.h"
#include "utils/TimeUtils.h"
#include "utils/Trace.h"

namespace android {
namespace uirenderer {

// 定义各类卡顿检测的比较规则
struct Comparison {
JankType type; // 卡顿类型
std::function<int64_t(nsecs_t)> computeThreadshold; // 计算阈值的函数
FrameInfoIndex start; // 起始时间点索引
FrameInfoIndex end; // 结束时间点索引
};

// 具体的卡顿判定标准
static const std::array<Comparison, 4> COMPARISONS{
// 1. 错过 Vsync:预期启动时间与实际启动时间偏差 > 1ns 即视为错过
Comparison{JankType::kMissedVsync, [](nsecs_t) { return 1; }, FrameInfoIndex::IntendedVsync,
FrameInfoIndex::Vsync},

// 2. UI 线程慢:从 Vsync 信号到 Sync 开始,耗时超过 0.5 倍帧间隔
Comparison{JankType::kSlowUI,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.5 * frameInterval); },
FrameInfoIndex::Vsync, FrameInfoIndex::SyncStart},

// 3. Sync 阶段慢:主线程与渲染线程数据同步耗时超过 0.2 倍帧间隔
Comparison{JankType::kSlowSync,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.2 * frameInterval); },
FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart},

// 4. 渲染线程慢:从提交命令到帧完成耗时超过 0.75 倍帧间隔
Comparison{JankType::kSlowRT,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.75 * frameInterval); },
FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::FrameCompleted},
};

// 如果单帧耗时超过 10 秒,则认为不是普通的卡顿而是 ANR,不再计入卡顿统计
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);

/*
* 目前不跟踪通过 Surface:lockHardwareCanvas() 直接进行的绘制。
* TODO: kSurfaceCanvas 会占用渲染线程时间,可能负面影响其他绘制,未来需要研究如何归因。
*/
static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;

// 测试环境下,过滤掉帧起始阶段的未知延迟,将其视为测试基建的开销
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;

JankTracker::JankTracker(ProfileDataContainer* globalData)
: mData(globalData->getDataMutex())
, mDataMutex(globalData->getDataMutex()) {
mGlobalData = globalData;
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
nsecs_t offsetDelta = sfOffset - DeviceInfo::getAppOffset();

// 根据 App 偏移和 SF 偏移的差值,计算 dequeueBuffer 阻塞的容忍时间(Legacy)
if (offsetDelta <= 4_ms && offsetDelta >= 0) {
// SF 将在 VSYNC-app + offsetDelta 开始合成。
// 在三缓冲情况下,这是由于 VSYNC-app 和 VSYNC-sf 交错而预期 dequeueBuffer 返回的时间。
mDequeueTimeForgivenessLegacy = offsetDelta + 4_ms;
}
mFrameIntervalLegacy = frameIntervalNanos;
}

/**
* 计算旧版(Legacy)卡顿指标。
* 主要处理 dequeueBuffer 阻塞时间的宽赦逻辑,避免因为 Buffer 调度导致的误判。
*/
void JankTracker::calculateLegacyJank(FrameInfo& frame) REQUIRES(mDataMutex) {
// 基础总耗时:从预期开始到 Swap 完成
int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::SwapBuffersCompleted);

// 如果存在 dequeueBuffer 耗时(>500us),尝试进行“宽赦”
if (mDequeueTimeForgivenessLegacy && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
// 预期 dequeue 时间 = 偏移容忍值 + Vsync时间 - 发送绘制命令的时间
nsecs_t expectedDequeueDuration = mDequeueTimeForgivenessLegacy
+ frame[FrameInfoIndex::Vsync]
- frame[FrameInfoIndex::IssueDrawCommandsStart];
if (expectedDequeueDuration > 0) {
// 宽赦量取“预期阻塞值”和“实际阻塞值”的较小者
nsecs_t forgiveAmount =
std::min(expectedDequeueDuration, frame[FrameInfoIndex::DequeueBufferDuration]);
if (forgiveAmount >= totalDuration) {
ALOGV("不可能的出队耗时! reported %" PRId64 ", total %" PRId64,
forgiveAmount, totalDuration);
return;
}
// 从总时长中减去宽赦的部分
totalDuration -= forgiveAmount;
}
}

if (totalDuration <= 0) {
ALOGV("总时长异常 %" PRId64 " start=%" PRIi64 " gpuComplete=%" PRIi64,
totalDuration, frame[FrameInfoIndex::IntendedVsync],
frame[FrameInfoIndex::GpuCompleted]);
return;
}

// 排除特殊标记的帧(如 lockHardwareCanvas)
if (CC_UNLIKELY(frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS)) {
return;
}

// 如果扣除宽赦后依然超过 16.6ms(对于 60Hz),记为卡顿
if (totalDuration > mFrameIntervalLegacy) {
mData->reportJankLegacy();
(*mGlobalData)->reportJankLegacy();
}

// 更新 Swap 截止时间
if (mSwapDeadlineLegacy < 0) {
mSwapDeadlineLegacy = frame[FrameInfoIndex::IntendedVsync] + mFrameIntervalLegacy;
}

// 判断是否处于三缓冲状态:当前截止时间与预期开始时间的偏差 > 0.1倍帧间隔
bool isTripleBuffered = (mSwapDeadlineLegacy - frame[FrameInfoIndex::IntendedVsync])
> (mFrameIntervalLegacy * 0.1);

mSwapDeadlineLegacy = std::max(mSwapDeadlineLegacy + mFrameIntervalLegacy,
frame[FrameInfoIndex::IntendedVsync] + mFrameIntervalLegacy);

// 如果在截止时间内完成,或总耗时小于帧间隔,说明没有产生肉眼可见的卡顿
if (frame[FrameInfoIndex::FrameCompleted] < mSwapDeadlineLegacy
|| totalDuration < mFrameIntervalLegacy) {
if (isTripleBuffered) {
// 虽然没卡顿,但如果触发了三缓冲,属于高延迟状态
mData->reportJankType(JankType::kHighInputLatency);
(*mGlobalData)->reportJankType(JankType::kHighInputLatency);
}
return;
}

// 否则记为错过旧版截止日期
mData->reportJankType(JankType::kMissedDeadlineLegacy);
(*mGlobalData)->reportJankType(JankType::kMissedDeadlineLegacy);

// 发生卡顿,根据实际完成时间重置下一个周期的截止时间
nsecs_t jitterNanos = frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::Vsync];
nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalLegacy;
mSwapDeadlineLegacy = frame[FrameInfoIndex::FrameCompleted]
- lastFrameOffset + mFrameIntervalLegacy;
}

/**
* 帧渲染结束后的统计入口。
* @param frame 包含帧各阶段时间戳的信息
*/
void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsReporter>& reporter,
int64_t frameNumber, int32_t surfaceControlId) {
std::lock_guard lock(mDataMutex);

// 1. 先计算旧版卡顿指标
calculateLegacyJank(frame);

// 2. 计算当前帧的总时长(从预期 Vsync 到帧完成)
int64_t totalDuration = frame.duration(FrameInfoIndex::IntendedVsync,
FrameInfoIndex::FrameCompleted);

if (totalDuration <= 0) {
ALOGV("总时长异常 %" PRId64, totalDuration);
return;
}

// 上报帧时长数据到直方图
mData->reportFrame(totalDuration);
(*mGlobalData)->reportFrame(totalDuration);

if (CC_UNLIKELY(frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS)) {
return;
}

int64_t frameInterval = frame[FrameInfoIndex::FrameInterval];

// 判断是否触发“三缓冲”(Buffer Stuffing)
// 如果本帧开始时间早于(假设未发生堆积时的)预期开始时间,说明前面有帧堆积了
bool isTripleBuffered = (mNextFrameStartUnstuffed - frame[FrameInfoIndex::IntendedVsync])
> (frameInterval * 0.1);

int64_t deadline = frame[FrameInfoIndex::FrameDeadline];

// 在三缓冲情况下,队列中有足够的缓冲区来支撑一次掉帧而不产生卡顿
// 因此根据堆积情况动态调整截止日期判定
if (isTripleBuffered) {
int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync];
deadline = mNextFrameStartUnstuffed + originalDeadlineDuration;
frame.set(FrameInfoIndex::FrameDeadline) = deadline;
}

// 3. 检查是否达到了性能截止日期 (Deadline)
if (frame[FrameInfoIndex::GpuCompleted] < deadline) {
if (isTripleBuffered) {
// 虽未掉帧,但属于高延迟输入
mData->reportJankType(JankType::kHighInputLatency);
(*mGlobalData)->reportJankType(JankType::kHighInputLatency);

// 除非有明显的“暂停”,否则三缓冲状态会延续到下一帧
mNextFrameStartUnstuffed += frameInterval;
}
} else {
// 判定为掉帧 (Jank)
mData->reportJankType(JankType::kMissedDeadline);
(*mGlobalData)->reportJankType(JankType::kMissedDeadline);
mData->reportJank();
(*mGlobalData)->reportJank();

// 计算抖动偏移,用于修正下一帧的期望开始时间
nsecs_t jitterNanos = frame[FrameInfoIndex::GpuCompleted]
- frame[FrameInfoIndex::Vsync];
nsecs_t lastFrameOffset = jitterNanos % frameInterval;

// 记录在非堆积情况下,下一帧应该开始的时间
mNextFrameStartUnstuffed = frame[FrameInfoIndex::GpuCompleted]
- lastFrameOffset + frameInterval;

// 4. 卡顿溯源分析:遍历所有的 COMPARISONS 规则,找出是哪个阶段慢了
recomputeThresholds(frameInterval);
for (auto& comparison : COMPARISONS) {
int64_t delta = frame.duration(comparison.start, comparison.end);
// 如果某阶段耗时超过对应阈值,则上报该类型的卡顿原因
if (delta >= mThresholds[comparison.type] && delta < IGNORE_EXCEEDING) {
mData->reportJankType(comparison.type);
(*mGlobalData)->reportJankType(comparison.type);
}
}

// 5. "Davey" 严重卡顿日志
// 如果一帧耗时超过 700ms,打印极其详细的时间轴数据辅助排查
if (totalDuration >= 700_ms) {
static int sDaveyCount = 0;
std::stringstream ss;
ss << "Davey! duration=" << ns2ms(totalDuration) << "ms; ";
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
ss << FrameInfoNames[i] << "=" << frame[i] << ", ";
}
ALOGI("%s", ss.str().c_str());
// 并在 Trace 中标记
ATRACE_INT(ss.str().c_str(), ++sDaveyCount);
}
}

// 6. 上报 GPU 绘制时长(如果平台支持)
int64_t totalGPUDrawTime = frame.gpuDrawTime();
if (totalGPUDrawTime >= 0) {
mData->reportGPUFrame(totalGPUDrawTime);
(*mGlobalData)->reportGPUFrame(totalGPUDrawTime);
}

// 7. 如果注册了 Metrics 监听器(如 FrameMetrics API),同步上报底层数据
if (CC_UNLIKELY(reporter.get() != nullptr)) {
reporter->reportFrameMetrics(frame.data(), false /* hasPresentTime */, frameNumber,
surfaceControlId);
}
}

/**
* 根据帧预算(取决于刷新率)重新计算各阶段的卡顿判定阈值
*/
void JankTracker::recomputeThresholds(int64_t frameBudget) REQUIRES(mDataMutex) {
if (mThresholdsFrameBudget == frameBudget) {
return;
}
mThresholdsFrameBudget = frameBudget;
for (auto& comparison : COMPARISONS) {
// 调用每个 Comparison 规则自带的 lambda 函数计算阈值
mThresholds[comparison.type] = comparison.computeThreadshold(frameBudget);
}
}

/**
* 将卡顿统计数据导出
*/
void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
const ProfileData* data) {
#ifdef __ANDROID__
if (description) {
switch (description->type) {
case JankTrackerType::Generic:
break;
case JankTrackerType::Package:
dprintf(fd, "\nPackage: %s", description->name.c_str());
break;
case JankTrackerType::Window:
dprintf(fd, "\nWindow: %s", description->name.c_str());
break;
}
}
if (sFrameStart != FrameInfoIndex::IntendedVsync) {
dprintf(fd, "\nNote: Data has been filtered!");
}
data->dump(fd);
dprintf(fd, "\n");
#endif
}

/**
* 打印最近所有帧的时间戳详细数据
*/
void JankTracker::dumpFrames(int fd) {
#ifdef __ANDROID__
dprintf(fd, "\n\n---PROFILEDATA---\n");
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
dprintf(fd, "%s", FrameInfoNames[i]);
dprintf(fd, ",");
}
for (size_t i = 0; i < mFrames.size(); i++) {
FrameInfo& frame = mFrames[i];
if (frame[FrameInfoIndex::SyncStart] == 0) {
continue;
}
dprintf(fd, "\n");
for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
dprintf(fd, "%" PRId64 ",", frame[i]);
}
}
dprintf(fd, "\n---PROFILEDATA---\n\n");
#endif
}

/**
* 重置卡顿统计
*/
void JankTracker::reset() REQUIRES(mDataMutex) {
mFrames.clear();
mData->reset();
(*mGlobalData)->reset();
// 根据系统属性决定是否过滤掉测试导致的起始开销
sFrameStart = Properties::filterOutTestOverhead ? FrameInfoIndex::HandleInputStart
: FrameInfoIndex::IntendedVsync;
}

} /* namespace uirenderer */
} /* namespace android */

ViewModel 源代码分析 (一)

  • viewmodel

    • CreationExtras
      ViewModel创建过程中传递额外参数的容器。

    • InitializerViewModelFactory

      ViewModelProvider.Factory接口实现类,使用ViewModelInitializer来创建ViewModel实例。

    • InitializerViewModelFactoryBuilder

      用于构建InitializerViewModelFactory的工具,通常以DSL形式提供。

    • MutableCreationExtras

      可变版本的CreationExtras,允许添加或修改额外参数。

    • ViewModelFactoryDsl

      一个DSL(领域特定语言),用于更声明式地定义如何创建ViewModel。

    • ViewModelInitializer

      用于初始化ViewModel的类,通常与ViewModelFactoryDsl一起使用。

    • InitializerViewModelFactory

      提供了使用InitializerViewModelFactory的Kotlin扩展。

  • AndroidViewModel

    AndroidViewModel是ViewModel的一个子类,它接受应用程序的Application作为上下文,这对于需要访问Application资源的ViewModel特别有用。

  • HasDefaultViewModelProviderFactory

    一个接口,标识一个类拥有默认的ViewModelProvider.Factory,用于创建ViewModel。

  • ViewModel

  • ViewModelLazy

    一个提供懒加载ViewModel实例的工具类。

  • ViewModelProvider

    用于获取ViewModel实例,确保配置更改时ViewModel可以持续使用。

  • ViewModelStore

    用于保存ViewModel实例的类,以便它们可以跨配置更改持续存在。

  • ViewModelStoreOwner

    一个接口,标识一个类可以拥有ViewModelStore。

  • ViewTreeViewModelStoreOwner

    用于从视图树中查找ViewModelStoreOwner的工具类。

  • ViewTreeViewModelKt

    提供了操作和查询视图树中ViewModel的Kotlin扩展

CreationExtras 与 MutableCreationExtra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package androidx.lifecycle.viewmodel


public abstract class CreationExtras internal constructor() {
internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()
public interface Key<T>
public abstract operator fun <T> get(key: Key<T>): T?

object Empty : CreationExtras() {
override fun <T> get(key: Key<T>): T? = null
}
}

/**
* [CreationExtras]的可变实现
*
* @param initialExtras 将被填充到结果MutableCreationExtras中的额外信息
*/
public class MutableCreationExtras(initialExtras: CreationExtras = Empty) : CreationExtras() {

init {
map.putAll(initialExtras.map)
}
/**
* 将给定的[key]与[t]关联
*/
public operator fun <T> set(key: Key<T>, t: T) {
map[key] = t
}

public override fun <T> get(key: Key<T>): T? {
@Suppress("UNCHECKED_CAST")
return map[key] as T?
}
}

fun viewModelFactory 和 InitializerViewModelFactoryBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package androidx.lifecycle.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlin.reflect.KClass

@DslMarker
public annotation class ViewModelFactoryDsl


public inline fun viewModelFactory(
builder: InitializerViewModelFactoryBuilder.() -> Unit
): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()

/**
* 用于构建新的[ViewModelProvider.Factory]的DSL。
*/
@ViewModelFactoryDsl
public class InitializerViewModelFactoryBuilder {
private val initializers = mutableListOf<ViewModelInitializer<*>>()

/**
* 为给定的ViewModel类添加初始化器。
*
* @param clazz 与初始化器关联的类。
* @param initializer 用于创建ViewModel类实例的lambda表达式
*/
fun <T : ViewModel> addInitializer(clazz: KClass<T>, initializer: CreationExtras.() -> T) {
initializers.add(ViewModelInitializer(clazz.java, initializer))
}

fun build(): ViewModelProvider.Factory =
InitializerViewModelFactory(*initializers.toTypedArray())
}

inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
noinline initializer: CreationExtras.() -> VM
) {
addInitializer(VM::class, initializer)
}

class ViewModelInitializer<T : ViewModel>(
internal val clazz: Class<T>,
internal val initializer: CreationExtras.() -> T,
)

internal class InitializerViewModelFactory(
private vararg val initializers: ViewModelInitializer<*>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
var viewModel: T? = null
@Suppress("UNCHECKED_CAST")
initializers.forEach {
if (it.clazz == modelClass) {
viewModel = it.initializer.invoke(extras) as? T
}
}
return viewModel ?: throw IllegalArgumentException(
"No initializer set for given class ${modelClass.name}"
)
}
}

AndroidViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package androidx.lifecycle;

import android.annotation.SuppressLint;
import android.app.Application;

import androidx.annotation.NonNull;

public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;

public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}

@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}

AndroidViewModel 是一个扩展了标准 ViewModel 功能的类,通过提供对 Application 上下文的访问,它允许 ViewModel 执行需要这种上下文的操作。使用 AndroidViewModel 可以帮助你编写更清晰、更健壮的应用程序代码,尤其是在处理配置更改和应用程序范围的资源访问时

HasDefaultViewModelProviderFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package androidx.lifecycle;

import androidx.annotation.NonNull;
import androidx.lifecycle.viewmodel.CreationExtras;

public interface HasDefaultViewModelProviderFactory {
@NonNull
ViewModelProvider.Factory getDefaultViewModelProviderFactory();

@NonNull
default CreationExtras getDefaultViewModelCreationExtras() {
return CreationExtras.Empty.INSTANCE;
}
}

ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package androidx.lifecycle;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public abstract class ViewModel {
// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
@Nullable
private final Set<Closeable> mCloseables = new LinkedHashSet<>();
private volatile boolean mCleared = false;

public ViewModel() {
}

public ViewModel(@NonNull Closeable... closeables) {
mCloseables.addAll(Arrays.asList(closeables));
}

public void addCloseable(@NonNull Closeable closeable) {
if (mCloseables != null) {
synchronized (mCloseables) {
mCloseables.add(closeable);
}
}
}

@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}

@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
if (mCloseables != null) {
synchronized (mCloseables) {
for (Closeable closeable : mCloseables) {
closeWithRuntimeException(closeable);
}
}
}
onCleared();
}

@SuppressWarnings("unchecked")
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}

@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}

private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

举例子说明哪些特殊数据是需要 ViewModel 特别管理的

ViewModel 的使用场景中,有几种特殊数据需要通过 ViewModel 来特别管理,主要是因为这些数据的生命周期需要与 ViewModel 保持一致,或者这些数据在 ViewModel 被销毁时需要进行特殊处理。以下是一些示例:

  1. 网络请求的取消标识(Cancellation Tokens):当使用 ViewModel 进行网络请求时,可以在 mBagOfTags 中保存请求的取消标识(如 Call 对象或其他取消机制)。如果用户离开了关联的界面(例如,Activity 或 Fragment 被销毁),则可以在 ViewModelonCleared() 方法中取消所有挂起的网络请求,以避免不必要的网络流量消耗或潜在的内存泄漏。

  2. 数据库监听器(Database Listeners):如果 ViewModel 监听数据库变化(例如,使用 SQLite 数据库或 Room 持久库),可以将监听器对象存储在 mBagOfTags 中。当 ViewModel 被清理时,这些监听器可以被适当地移除,防止因为监听器持续激活而导致的内存泄漏。

  3. 文件资源(File Resources):应用可能会打开文件进行读写操作,这些文件的句柄或相关的流对象(如 FileInputStreamFileOutputStream)可以放在 mBagOfTags 中。在 ViewModel 生命周期结束时,确保这些资源被正确关闭,避免文件资源泄漏。

  4. 自定义资源的清理(Custom Resource Cleanup):如果有一些自定义的资源或对象需要在 ViewModel 生命周期结束时进行清理(比如自定义的缓存机制、临时文件的删除等),也可以利用 mBagOfTags 来管理这些资源。通过实现 Closeable 接口,可以确保这些资源在 ViewModelclear() 方法被调用时得到正确的处理。

  5. 订阅服务(Subscriptions):当 ViewModel 订阅了某些事件通知或数据流(例如,使用 RxJava 的 Observable 或 LiveData 订阅),可以将这些订阅的取消引用(比如 Disposable)存储在 mBagOfTags 中。这样,可以在不需要这些订阅时或 ViewModel 销毁时,统一取消订阅,防止内存泄漏。

通过以上示例,可以看出 ViewModel 中的 mBagOfTags 提供了一种便捷的方式来管理与 ViewModel 生命周期紧密相关的特殊数据和资源,帮助开发者维护应用的健壮性和性能。

ViewModelLazy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package androidx.lifecycle

import androidx.lifecycle.viewmodel.CreationExtras
import kotlin.reflect.KClass

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
private var cached: VM? = null

override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(
store,
factory,
extrasProducer()
).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}

override fun isInitialized(): Boolean = cached != null
}

ViewModelProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
@file:JvmName("ViewModelProviderGetKt")

package androidx.lifecycle

import android.app.Application
import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.DEFAULT_KEY
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.defaultFactory
import androidx.lifecycle.viewmodel.CreationExtras.Key
import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.VIEW_MODEL_KEY
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.InitializerViewModelFactory
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.ViewModelInitializer
import java.lang.IllegalArgumentException
import java.lang.RuntimeException
import java.lang.reflect.InvocationTargetException
import kotlin.UnsupportedOperationException

public open class ViewModelProvider

@JvmOverloads
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {

public interface Factory {

public fun <T : ViewModel> create(modelClass: Class<T>): T {
throw UnsupportedOperationException(
"Factory.create(String) is unsupported. This Factory requires " +
"`CreationExtras` to be passed into `create` method."
)
}

public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
create(modelClass)

companion object {
@JvmStatic
fun from(vararg initializers: ViewModelInitializer<*>): Factory =
InitializerViewModelFactory(*initializers)
}
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open class OnRequeryFactory {
public open fun onRequery(viewModel: ViewModel) {}
}

public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
owner.viewModelStore,
factory,
defaultCreationExtras(owner)
)

@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}

// actually there is getInstance()
@Suppress("SingletonConstructor")
public open class NewInstanceFactory : Factory {
@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return try {
modelClass.newInstance()
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
}

public companion object {
private var sInstance: NewInstanceFactory? = null

@JvmStatic
public val instance: NewInstanceFactory
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
if (sInstance == null) {
sInstance = NewInstanceFactory()
}
return sInstance!!
}

private object ViewModelKeyImpl : Key<String>

@JvmField
val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl
}
}

public open class AndroidViewModelFactory
private constructor(
private val application: Application?,
@Suppress("UNUSED_PARAMETER") unused: Int,
) : NewInstanceFactory() {

@Suppress("SingletonConstructor")
public constructor() : this(null, 0)

@Suppress("SingletonConstructor")
public constructor(application: Application) : this(application, 0)

@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return if (application != null) {
create(modelClass)
} else {
val application = extras[APPLICATION_KEY]
if (application != null) {
create(modelClass, application)
} else {
// For AndroidViewModels, CreationExtras must have an application set
if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
throw IllegalArgumentException(
"CreationExtras must have an application by `APPLICATION_KEY`"
)
}
super.create(modelClass)
}
}
}

@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (application == null) {
throw UnsupportedOperationException(
"AndroidViewModelFactory constructed " +
"with empty constructor works only with " +
"create(modelClass: Class<T>, extras: CreationExtras)."
)
} else {
create(modelClass, application)
}
}

@Suppress("DocumentExceptions")
private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
try {
modelClass.getConstructor(Application::class.java).newInstance(app)
} catch (e: NoSuchMethodException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InvocationTargetException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
} else super.create(modelClass)
}

public companion object {
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance

internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

private var sInstance: AndroidViewModelFactory? = null

@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
if (sInstance == null) {
sInstance = AndroidViewModelFactory(application)
}
return sInstance!!
}

private object ApplicationKeyImpl : Key<Application>

@JvmField
val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
}
}
}

internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras {
return if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelCreationExtras
} else CreationExtras.Empty
}

@MainThread
public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class.java)

简单解析一下 ViewModelProvider

在这段代码中,定义了一个ViewModelProvider类,其作用是提供ViewModel实例。
在MVVM架构中,ViewModel是负责准备和管理UI相关数据的组件,它使数据和命令可以轻松地绑定到UI控件,同时保持UI控件和应用逻辑的分离。
ViewModelProvider负责创建和管理这些ViewModel对象。
以下是该代码段中定义的主要函数及其作用的分析:

构造函数

  • ViewModelProvider构造函数: 接收一个ViewModelStore和一个Factory作为参数,以及一个可选的CreationExtras。这些构造函数允许你在创建ViewModelProvider实例时指定ViewModel的存储和创建方式。

内部接口和类

  • Factory接口: 定义了创建ViewModel实例的方法。这个接口允许自定义ViewModel的创建过程,包括通过CreationExtras传递额外的创建参数。
  • OnRequeryFactory: 这是一个可选的回调,当ViewModel被重新查询时调用。
  • NewInstanceFactory: 一个默认的Factory实现,它使用反射来创建ViewModel的新实例。
  • AndroidViewModelFactory: 专门为AndroidViewModel定制的Factory实现,它需要Application实例来创建ViewModel

主要方法

  • get方法: 重载的方法,用于获取指定类的ViewModel实例。如果ViewModel已经存在于ViewModelStore中,则返回这个实例;如果不存在,则通过Factory创建新的实例并存储起来。这些方法确保ViewModel的生命周期被正确管理,即使在配置更改(如屏幕旋转)后也能保持状态。
  • defaultCreationExtras函数: 为ViewModel创建提供默认的CreationExtras。这是用于传递额外参数给ViewModel构造函数的一种机制。

其他重要组件

  • ViewModelStore: 一个容器,用于存储和管理ViewModel实例。这确保了ViewModel能够跨配置更改存活。
  • ViewModelStoreOwner: 一个接口,表示拥有ViewModelStore的对象,通常是UI控制器,如Activity或Fragment。
  • CreationExtras: 一个容器,用于在创建ViewModel时传递额外的参数。它支持通过键值对形式传递任意数据。

整体而言,这段代码提供了一个灵活的机制来创建和管理ViewModel实例,支持自定义创建过程,并确保ViewModel能够有效地与UI组件的生命周期同步。

这段代码体现了Android架构组件中ViewModel的灵活创建和管理机制,包括支持自定义工厂、传递额外参数以及通过工厂模式解耦ViewModel的创建过程。这套机制不仅提高了ViewModel使用的灵活性,还增强了组件之间的解耦,使得应用架构更加清晰和易于维护。

参数 defaultCreationExtras 的作用

defaultCreationExtras函数在ViewModelProvider的上下文中扮演着重要的角色。它提供了一种机制,用于在创建ViewModel实例时传递额外的参数,这些参数被封装在CreationExtras对象中。这允许ViewModel的构造过程中接收来自ViewModelProvider以外的信息,进而使得ViewModel的创建更加灵活和动态。

CreationExtras的作用

CreationExtras是一种键值对集合,它允许开发者在创建ViewModel时传递任意类型的数据。这些数据可以是应用上下文(如Application实例)、配置参数,或者任何对于ViewModel初始化过程中可能需要的信息。

defaultCreationExtras 使用场景

defaultCreationExtras的使用场景通常涉及到需要向ViewModel传递额外信息的情况。例如,如果某个ViewModel需要访问全局的应用状态,或者依赖于特定的服务实例,这些依赖项可以通过CreationExtras在创建时传递给ViewModel,而defaultCreationExtras函数就是为了方便地提供这类默认参数的。

总之,defaultCreationExtras函数和CreationExtras机制为ViewModel的创建提供了额外的灵活性和动态配置能力,使得ViewModel能够更好地适应不同的应用场景和需求。

Android Bitmap 性能专题 - Bitmap 内存从申请到回收

问题

1
2
The formulation of the problem is often more essential than its solution, which may be merely a matter of mathematical or experimental skill.
― Albert Einstein

Q:Bitmap 如何开辟内存?

Q:Bitmap 内存是怎么复用和销毁的?本地资源图片应该怎么去做适配?

Q:该如何去优化这些占用较大内存的本地资源图片?

Q:如何判断和优化 Bitmap OOM,如何 dump 线上内存来做优化分析?

从 Bitmap 的创建说起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Private constructor that must received an already allocated native bitmap
* int (pointer).
*/
// called from JNI
Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}

mWidth = width;
mHeight = height;
mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;

mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}

mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);

if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
sPreloadTracingNumInstantiatedBitmaps++;
sPreloadTracingTotalBitmapsSize += nativeSize;
}
}

called from JNI 这个解释其实已经很明确了,也就是说这个对象是 Native 层构建返回的。因此我们跟踪到 BitmapFactory.decodeResource() 中去看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream is = null;

try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);

bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
If it happened on close, bm is still valid.
*/
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}

if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}

return bm;
}

public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}

if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
// 获取当前手机设备的 dpi
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}

return decodeStream(is, pad, opts);
}

// 省略部分跟踪代码 ......

private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);

最终调用的是 native 方法 nativeDecodeStream

这里以 Android N 版本为例:

/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
static jobject nativeDecodeStream(JNIEnv *env, jobject clazz, jobject is, jbyteArray storage,
jobject padding, jobject options) {
jobject bitmap = NULL;
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));

if (stream.get()) {
std::unique_ptr<SkStreamRewindable> bufferedStream(
SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
SkASSERT(bufferedStream.get() != NULL);
bitmap = doDecode(env, bufferedStream.release(), padding, options);
}
return bitmap;
}

static jobject doDecode(JNIEnv *env, SkStreamRewindable *stream, jobject padding, jobject options) {
// This function takes ownership of the input stream. Since the SkAndroidCodec
// will take ownership of the stream, we don't necessarily need to take ownership
// here. This is a precaution - if we were to return before creating the codec,
// we need to make sure that we delete the stream.
std::unique_ptr<SkStreamRewindable> streamDeleter(stream);

// Set default values for the options parameters.
int sampleSize = 1;
// 是否只是获取图片的大小
bool onlyDecodeSize = false;
SkColorType prefColorType = kN32_SkColorType;
bool isMutable = false;
float scale = 1.0f;
bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;

// Update with options supplied by the client.
// 解析 options 参数
if (options != NULL) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
// Correct a non-positive sampleSize. sampleSize defaults to zero within the
// options object, which is strange.
if (sampleSize <= 0) {
sampleSize = 1;
}

if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
onlyDecodeSize = true;
}

// initialize these, in case we fail later on
env->SetIntField(options, gOptions_widthFieldID, -1);
env->SetIntField(options, gOptions_heightFieldID, -1);
env->SetObjectField(options, gOptions_mimeFieldID, 0);
// 解析 ColorType ,复用参数等等
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
// 计算缩放的比例
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
// 获取图片当前 xhdpi 的 density
const int density = env->GetIntField(options, gOptions_densityFieldID);
// 获取当前设备的 dpi
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
// scale = 当前设备的 dpi / xhdpi 的 density
// scale = 420/320 = 1.3125
scale = (float) targetDensity / density;
}
}
}

// Create the codec.
NinePatchPeeker peeker;
std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(),
280 & peeker));
if (!codec.get()) {
return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
}

// Do not allow ninepatch decodes to 565. In the past, decodes to 565
// would dither, and we do not want to pre-dither ninepatches, since we
// know that they will be stretched. We no longer dither 565 decodes,
// but we continue to prevent ninepatches from decoding to 565, in order
// to maintain the old behavior.
if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) {
prefColorType = kN32_SkColorType;
}
// 获取当前图片的大小
// Determine the output size.
SkISize size = codec->getSampledDimensions(sampleSize);

int scaledWidth = size.width();
int scaledHeight = size.height();
bool willScale = false;
// 处理 simpleSize 压缩,我们这里没穿,上面默认是 1
// Apply a fine scaling step if necessary.
if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
willScale = true;
scaledWidth = codec->getInfo().width() / sampleSize;
scaledHeight = codec->getInfo().height() / sampleSize;
}

// Set the options and return if the client only wants the size.
if (options != NULL) {
jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat());
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
// 设置 options 对象中的 outWidth 和 outHeight
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
// 如果只是获取大小直接 return null 这里是 nullptr 而不是 NULL
if (onlyDecodeSize) {
return nullptr;
}
}

// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
// 计算 scaledWidth 和 scaledHeight
// scaledWidth = 864 * 1.3125 + 0.5f = 1134 + 0.5f = 1134
scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
// scaledHeight = 582 * 1.3125 + 0.5f = 763.875 + 0.5f = 764
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}
// 判断是否有复用的 Bitmap
android::Bitmap *reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
// 无法重用一个不变的位图图像解码器的目标。
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = NULL;
reuseBitmap = nullptr;
} else {
existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
}
}

JavaPixelAllocator javaAllocator(env);
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
SkBitmap::HeapAllocator heapAllocator;
SkBitmap::Allocator *decodeAllocator;
if (javaBitmap != nullptr && willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory. This allocator
// also checks that the recycled javaBitmap is large enough.
decodeAllocator = &scaleCheckingAllocator;
} else if (javaBitmap != nullptr) {
decodeAllocator = &recyclingAllocator;
} else if (willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory.
decodeAllocator = &heapAllocator;
} else {
decodeAllocator = &javaAllocator;
}

// Set the decode colorType. This is necessary because we can't always support
// the requested colorType.
SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);

// Construct a color table for the decode if necessary
SkAutoTUnref <SkColorTable> colorTable(nullptr);
SkPMColor *colorPtr = nullptr;
int *colorCount = nullptr;
int maxColors = 256;
SkPMColor colors[256];
if (kIndex_8_SkColorType == decodeColorType) {
colorTable.reset(new SkColorTable(colors, maxColors));

// SkColorTable expects us to initialize all of the colors before creating an
// SkColorTable. However, we are using SkBitmap with an Allocator to allocate
// memory for the decode, so we need to create the SkColorTable before decoding.
// It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
// not being used elsewhere.
colorPtr = const_cast<SkPMColor *>(colorTable->readColors());
colorCount = &maxColors;
}

// Set the alpha type for the decode.
SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
// 创建 SkImageInfo 信息,宽,高,ColorType,alphaType
const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
alphaType);
SkImageInfo bitmapInfo = decodeInfo;
if (decodeColorType == kGray_8_SkColorType) {
// The legacy implementation of BitmapFactory used kAlpha8 for
// grayscale images (before kGray8 existed). While the codec
// recognizes kGray8, we need to decode into a kAlpha8 bitmap
// in order to avoid a behavior change.
bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height());
}
// 解析 SkBitmap 设置 bitmapInfo,tryAllocPixels 开辟内存,具体分析在后面
SkBitmap decodingBitmap;
if (!decodingBitmap.setInfo(bitmapInfo) ||
!decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
// SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
// should only only fail if the calculated value for rowBytes is too
// large.
// tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
// native heap, or the recycled javaBitmap being too small to reuse.
return nullptr;
}

// Use SkAndroidCodec to perform the decode.
SkAndroidCodec::AndroidOptions codecOptions;
codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ?
SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
codecOptions.fColorPtr = colorPtr;
codecOptions.fColorCount = colorCount;
codecOptions.fSampleSize = sampleSize;
// 解析获取像素值
SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
decodingBitmap.rowBytes(), &codecOptions);
switch (result) {
case SkCodec::kSuccess:
case SkCodec::kIncompleteInput:
break;
default:
return nullObjectReturn("codec->getAndroidPixels() failed.");
}

jbyteArray ninePatchChunk = NULL;
if (peeker.mPatch != NULL) {
if (willScale) {
scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
}

size_t ninePatchArraySize = peeker.mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == NULL) {
return nullObjectReturn("ninePatchChunk == null");
}

jbyte *array = (jbyte *) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
if (array == NULL) {
return nullObjectReturn("primitive array == null");
}

memcpy(array, peeker.mPatch, peeker.mPatchSize);
env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
}

jobject ninePatchInsets = NULL;
if (peeker.mHasInsets) {
ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
if (ninePatchInsets == NULL) {
return nullObjectReturn("nine patch insets == null");
}
if (javaBitmap != NULL) {
env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
}
}
// 构建 SkBitmap 这个才是最终的
SkBitmap outputBitmap;
if (willScale) {
// 如果需要缩放,那需要重新创建一张图片,上面加载的是图片的本身大小
// This is weird so let me explain: we could use the scale parameter
// directly, but for historical reasons this is how the corresponding
// Dalvik code has always behaved. We simply recreate the behavior here.
// The result is slightly different from simply using scale because of
// the 0.5f rounding bias applied when computing the target image size
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());

// Set the allocator for the outputBitmap.
SkBitmap::Allocator *outputAllocator;
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator;
} else {
outputAllocator = &javaAllocator;
}

SkColorType scaledColorType = colorTypeForScaledOutput(decodingBitmap.colorType());
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
// 设置 SkImageInfo ,注意这里是 scaledWidth ,scaledHeight
outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
scaledColorType, decodingBitmap.alphaType()));
// 开辟当前 Bitmap 图片的内存
if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
// This should only fail on OOM. The recyclingAllocator should have
// enough memory since we check this before decoding using the
// scaleCheckingAllocator.
return nullObjectReturn("allocation failed for scaled bitmap");
}

SkPaint paint;
// kSrc_Mode instructs us to overwrite the unininitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
paint.setFilterQuality(kLow_SkFilterQuality);
// decodingBitmap -> 画到 outputBitmap
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}

if (padding) {
if (peeker.mPatch != NULL) {
GraphicsJNI::set_jrect(env, padding,
peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
} else {
GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
}
}

// If we get here, the outputBitmap should have an installed pixelref.
if (outputBitmap.pixelRef() == NULL) {
return nullObjectReturn("Got null SkPixelRef");
}

if (!isMutable && javaBitmap == NULL) {
// promise we will never change our pixels (great for sharing and pictures)
outputBitmap.setImmutable();
}
// 如果有复用返回原来的 javaBitmap
bool isPremultiplied = !requireUnpremultiplied;
if (javaBitmap != nullptr) {
GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
outputBitmap.notifyPixelsChanged();
// If a java bitmap was passed in for reuse, pass it back
return javaBitmap;
}

int bitmapCreateFlags = 0x0;
if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
// 没有复用的 Bitmap 创建一个新的 Bitmap
// now create the java bitmap
return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

jobject GraphicsJNI::createBitmap(JNIEnv *env, android::Bitmap *bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk,
jobject ninePatchInsets,
int density) {
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(bitmap->info(), isPremultiplied);

jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}

上面的代码看起来比较长,其实是非常简单的,相信大家都能看得懂,这里我对上面的流程再做一些总结:

  1. 解析 java 层传递过来的 Options 的参数,如 simpleSize ,isMutable,javaBitmap 等等,同时计算出 scale 。
  2. 获取当前图片的大小,根据 sampleSize 判断是否需要压缩,同时计算出 scaledWidth ,scaledHeight。
  3. 设置 options 宽高为 scaledWidth ,scaledHeight ,如果只是解析宽高那么就直接返回,也就是 options.inJustDecodeBounds = true 时,但是这里需要注意返回的是,资源图片的宽高并不是 Bitmap 最终的宽高。(我们大部分人对这个有误解)
  4. 创建 native 层的 SkImageInfo 和 SkBitmap ,然后调用 tryAllocPixels 去开辟图片的内存空间,然后调用 getAndroidPixels 去解析像素值 ,这里的 decodingBitmap 也并不是最终需要返回的 Bitmap ,而是原资源图片的 Bitmap 。
  5. 构建需要返回的 outputBitmap ,如果需要缩放那么重新去开辟一块内存空间,如果不需要缩放直接调用 swap 方法即可。最后判断有没有复用的 JavaBitmap ,如果有复用调用 reinitBitmap 然后直接返回,如果没有则调用 createBitmap 去创建一个新的 Bitmap 。

究竟需要多少内存?

通过上面的分析,我们可能会有疑问?我们调用了两次 tryAllocPixels ,那如果加载一张 (1440x2560) 10M 的图片,岂不是需要 20M 的内存?

在 Android 平台上,Bitmap 的内存申请操作通常涉及到 Java 层和 Native 层的协作,而 tryAllocPixels 方法的多次调用可能是由于内部的内存分配和管理流程所致。

在 Android 4.4 及以上版本中,当你尝试为 Bitmap 对象申请内存时,tryAllocPixels 方法可能会在底层进行两次调用。这个行为在底层库中被设计用来提高内存分配的成功率。

第一次调用 tryAllocPixels 旨在检查是否有足够的内存来分配 Bitmap 的像素数据。如果这次调用失败,系统会尝试触发一次垃圾回收(GC)操作,以释放一些已不再使用的内存资源。

第二次调用 tryAllocPixels 发生在垃圾回收之后,此时系统期望能够分配足够的内存来满足 Bitmap 的需求。如果第二次调用仍然失败,那么可能会抛出 OutOfMemoryError 错误。

这种设计的目的是为了提高 Bitmap 内存分配的成功率。由于 Android 平台上的内存管理和垃圾回收机制是相对复杂的,经过多次尝试分配内存和触发垃圾回收,可以增加 Bitmap 成功分配内存的可能性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
问题:两次调用需要两倍的内存吗

GPT4: 在处理Android图片加载时,内存管理是一个关键考虑因素。
特别是对于高分辨率的图片,如1440x2560像素大小的图片,
观上可能会担心单纯通过分析像素数和颜色深度得出的内存需求量会加倍,
尤其是在调用像`tryAllocPixels`这样的函数两次时。
这里的关键在于理解`tryAllocPixels`函数的作用及其在Android图片处理中的实际行为。

`tryAllocPixels`是 Android Skia 图形库中的一个函数,用于尝试分配内存以存储图片的像素数据。
当你看到代码中似乎调用了两次`tryAllocPixels`
可能会误以为这意味着图片数据会被加载或复制两次,
从而导致内存使用翻倍。然而,实际情况可能并非如此。

首先,调用`tryAllocPixels`并不总是意味着实际的内存分配。
这个函数的行为取决于多个因素,
包括图片的存储格式、是否已经有足够的内存分配给图片,以及图像解码器的实现等。
在某些情况下,如果图片已经在内存中有一个分配好的空间,
第二次调用可能仅仅是一个重复的操作,而不会实际增加内存消耗。

其次,即使图片数据需要被重新处理或在内存中重新定位,
这也不一定意味着整个图片大小的内存会被再次完整分配。
例如,某些优化操作可能仅仅需要额外的小量内存来处理图片数据,而不是复制整个图片。

最后,Android平台和许多现代操作系统都采用了高效的内存管理和图像处理策略。
这包括图片的压缩存储、延迟解码(只有在显示图片时才完全解码)、以及内存共享和重用机制等。
这些机制意味着即使处理大型图片,系统也能通过优化减少实际的内存占用。

因此,尽管从理论上分析像素和颜色深度可能会让人担心高内存消耗,
实际上Android的图片加载和处理框架设计有多种优化,以保持内存使用的效率。
对于开发者而言,关键是了解并利用这些机制,例如使用适当的图片加载库(如Glide、Picasso等),
它们为图片处理提供了进一步的优化和内存管理策略。

在 Android 中,Bitmap 对象的内存使用量取决于图像的像素数量、每个像素的位深度和像素数据的存储方式。一个 10MB 的图片通常指的是文件大小,而不是图像的像素数量,因此具体的内存使用量会根据图像的属性而有所不同。

假设你有一张 10MB 的图片,我们可以估算一下将其加载为 Bitmap 时可能需要的内存量。请注意,以下是一个粗略的估算,实际内存使用量可能会受到压缩、图像格式和设备性能的影响。

  1. 图像大小: 假设你的图片大小为 10MB,这指的是文件的大小。这并不直接表示图像的像素数量,因为图像可以使用不同的压缩算法和格式存储。通常情况下,JPEG 格式的图像文件较小,而无损格式如 PNG 或 BMP 文件较大。

  2. 解码后的像素数据: 当你将图片解码为 Bitmap 时,其像素数据将被加载到内存中。假设该图片是一个 24 位的彩色图片(每个像素使用 8 位红、绿、蓝通道),那么像素数据的大小将是图像的宽度乘以高度乘以 3(每个像素 3 个通道)字节。这个估算不考虑额外的颜色通道、Alpha 通道等。

所以,一个粗略的估算如下:

假设图片的像素数据大小为 pixelsSize 字节,则需要的内存量约为:pixelsSize + Overhead

OverheadBitmap 对象的开销,它通常取决于设备的位数、对象头信息、内存对齐等因素。对于一个普通的 24 位彩色图片,Overhead 大约在 4-8MB 左右。

需要注意的是,这只是一个估算,并且实际情况可能因为图片格式、加载库的实现、设备性能等因素而有所不同。在实际开发中,你可以使用一些工具来测量实际的内存使用量,例如 Android Studio 的内存分析工具。

Bitmap 的内存申请

Bitmap 的内存申请不同版本间有些许差异,在 3.0-7.0 的 bitmap 像素内存都是存放在 Java heap 中的,而 8.0 以后则是放在 Native heap 中的

8.0 Bitmap 内存申请

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

logMemory();

Bitmap bitmap = Bitmap.createBitmap(1024, 1024 * 500, Bitmap.Config.ARGB_8888);

logMemory();
}

private void logMemory() {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
Log.e("TAG", "AvailMem :" + memoryInfo.availMem / 1024 / 1024);
Log.e("TAG", "lowMemory:" + memoryInfo.lowMemory);
Log.e("TAG", "NativeHeapAllocatedSize :" + Debug.getNativeHeapAllocatedSize() / 1024 / 1024);
}

上面我们创建了一张 2G 大小的 bitmap 我们在 8.0 以下的版本运行是会 OOM 的,而我们在 8.0 以上的版本运行是完全没问题,但 Native 内存多了 2G 的内存。

1
2
3
4
5
6
7
E/TAG: AvailMem :1654
E/TAG: lowMemory:false
E/TAG: NativeHeapAllocatedSize :4

E/TAG: AvailMem :1656
E/TAG: lowMemory:false
E/TAG: NativeHeapAllocatedSize :2052

为什么会有一张大图片的像素数据放在Java层会OOM,放在Native层却不会的情况

在 Android 应用中,一张大图片的像素数据如果放在 Java 层可能会导致 OutOfMemoryError (OOM) 错误,而放在 Native 层不会出现这个问题。这涉及到 Java 层和 Native 层内存管理的不同方式以及底层实现的细节。

  1. Java 层内存管理: 在 Java 层,对象的内存管理由 Java 虚拟机 (JVM) 负责。当你创建一个 Bitmap 对象并加载大图片的像素数据时,这些像素数据会在 Java 堆内存中分配。Android 应用的 Java 堆内存限制通常较小,这是为了确保系统的稳定性,避免应用占用过多内存影响其他应用和系统的运行。Java 堆内存受到限制,当堆内存用尽时就会触发 OutOfMemoryError 错误。由于 Java 层的内存管理涉及垃圾回收和其他 Java 虚拟机机制,加载大量像素数据可能会导致内存碎片和性能问题,最终导致 OOM 错误。

  2. Native 层内存管理: 在 Native 层,像素数据的内存分配和释放由底层操作系统和 C/C++ 代码负责。通过在 Native 层使用本地代码(如 C/C++),你可以更好地控制内存的分配和释放。Native 层不受 Java 堆内存限制,因此可以更灵活地处理大量的像素数据,减少内存碎片问题,并降低 OOM 风险。

7.0 与 8.0 Bitmap 内存申请 tryAllocPixels 方法

通过之前的源码分析可知 bitmap 的内存创建都是通过 tryAllocPixels 方法来申请的,我们通过源码来对比一下他们之间的区别,我们首先来看下 7.0 的代码:

/frameworks/base/core/jni/android/graphics/Bitmap.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bool SkBitmap::tryAllocPixels(Allocator *allocator, SkColorTable *ctable) {
HeapAllocator stdalloc;

if (nullptr == allocator) {
allocator = &stdalloc;
}
return allocator->allocPixelRef(this, ctable);
}

bool JavaPixelAllocator::allocPixelRef(SkBitmap *bitmap, SkColorTable *ctable) {
JNIEnv *env = vm2env(mJavaVM);

mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
return mStorage != nullptr;
}

android::Bitmap *GraphicsJNI::allocateJavaPixelRef(JNIEnv *env, SkBitmap *bitmap,
SkColorTable *ctable) {
const SkImageInfo &info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return NULL;
}

size_t size;
if (!computeAllocationSize(*bitmap, &size)) {
return NULL;
}

// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();

jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
gVMRuntime_newNonMovableArray,
gByte_class, size);
if (env->ExceptionCheck() != 0) {
return NULL;
}
SkASSERT(arrayObj);
jbyte *addr = (jbyte *) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
if (env->ExceptionCheck() != 0) {
return NULL;
}
SkASSERT(addr);
android::Bitmap *wrapper = new android::Bitmap(env, arrayObj, (void *) addr, info, rowBytes,
ctable);
wrapper->getSkBitmap(bitmap);
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();

return wrapper;
}

从上面就可以看到, new android::Bitmap 见:
frameworks/base/core/jni/android/graphics/Bitmap.cpp

1
2
3
4
5
6
7
8
9
10
11
Bitmap::Bitmap(JNIEnv *env, jbyteArray storageObj, void *address,
const SkImageInfo &info, size_t rowBytes, SkColorTable *ctable)
: mPixelStorageType(PixelStorageType::Java) {
env->GetJavaVM(&mPixelStorage.java.jvm);
mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
mPixelStorage.java.jstrongRef = nullptr;
mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
// Note: this will trigger a call to onStrongRefDestroyed(), but
// we want the pixel ref to have a ref count of 0 at this point
mPixelRef->unref();
}

address 获取的是 arrayObj 的地址,而 arrayObj 是 jbyteArray 数据类型,也就是说这里是通过 JNI 进入了 Java 世界开辟了内存,好比 Zygote 进入 Java 世界是通过 JNI 调用 com.android.internal.os.ZygoteInit 类的 main 函数是一个道理~ 我们还可以继续跟到 gVMRuntime_newNonMovableArray 中去看看实现,最后是 runtime->GetHeap() 上分配内存也就是 Java heap 内存。

我们还得看下 8.0 的源码,比较一下它与 7.0 之间的区别:
external/skia/src/core/SkBitmap.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
bool SkBitmap::tryAllocPixels(Allocator *allocator, SkColorTable *ctable) {
HeapAllocator stdalloc;

if (nullptr == allocator) {
allocator = &stdalloc;
}
return allocator->allocPixelRef(this, ctable);
}

bool HeapAllocator::allocPixelRef(SkBitmap *bitmap, SkColorTable *ctable) {
mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
return !!mStorage;
}

allocateHeapBitmap方法会最终new Bitmap,分配内存 ,见:
/frameworks/base/libs/hwui/hwui/Bitmap.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
sk_sp <Bitmap> Bitmap::allocateHeapBitmap(SkBitmap *bitmap, SkColorTable *ctable) {
return allocateBitmap(bitmap, ctable, &android::allocateHeapBitmap);
}

static sk_sp <Bitmap> allocateBitmap(SkBitmap *bitmap, SkColorTable *ctable, AllocPixeRef alloc) {
const SkImageInfo &info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
LOG_ALWAYS_FATAL("unknown bitmap configuration");
return nullptr;
}

size_t size;

// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}

auto wrapper = alloc(size, info, rowBytes, ctable);
if (wrapper) {
wrapper->getSkBitmap(bitmap);
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();
}
return wrapper;
}

Bitmap 内存模型

0-2.3 3.0-4.4 5.0-7.1 8.0
Bitmap对象 Java Heap Java Heap Java Heap
像素数据 Native Heap Java Heap Native Heap
迁移原因 解决Native Bitmap内存泄漏 共享整个系统的内存减少OOM

Android Bitmap 回收机制源代码分析 (从2.3到7.0,8.0)

android 2.3.3 和 更低的版本

在 Android 2.3.3 之前开发者必须手动调用 recycle 方法去释放 Native 内存,因为那个时候管理Bitmap内存比较复杂,需要手动维护引用计数器

官网解释:

1
2
3
4
5
On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle()method allows an app to reclaim memory as soon as possible.
Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap".
The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed or in the cache. The code recycles the bitmap when these conditions are met:
The reference count for both mDisplayRefCount and mCacheRefCount is 0.
The bitmap is not null, and it hasn't been recycled yet.

在 Android 2.3.3 以后不需要开发者主动调用 recycle 方法来回收内存了,但 Android K,L,M,N,O 版本上,都还能看到 recycle 方法,为什么没有干掉呢? 调用它会不会真正的释放内存呢?既然不需要手动释放 Native Bitmap ,那 Native 层的对象是怎么自动释放的?我们先来看下 7.0 和 8.0 中 recycle 的方法实现。

7.0 和 8.0 中 recycle 的方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data synchronously;
* it simply allows it to be garbage collected if there are no other references.
* The bitmap is marked as "dead", meaning it will throw an exception if
* getPixels() or setPixels() is called, and will draw nothing. This operation
* cannot be reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/

/*
* 释放与此位图关联的本机对象,并清除对像素数据的引用。这不会立即释放像素数据;
* 它只是允许在没有其他引用时进行垃圾回收。该位图被标记为"无效",这意味着如果调用
* getPixels()或setPixels(),它将抛出异常,
* 并且不会绘制任何内容。此操作无法撤销,因此只有在确保不再需要位图时才应调用此方法。
* 这是一个高级调用,通常不需要调用,因为正常的垃圾回收过程将在不再引用该位图时释放此内
* 存。
*/
public void recycle() {
if (!mRecycled && mNativePtr != 0) {
if (nativeRecycle(mNativePtr)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.

// 返回值指示本机像素对象是否实际已被回收。
// false 表示它仍在本机级别上使用,现在不应收集这些对象。它们将在位图本身被回收时稍后收集。
mNinePatchChunk = null;
}
mRecycled = true;
}
}

private static native boolean nativeRecycle(long nativeBitmap);

都是调用了native方法,下面看一下native方法

8.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
bitmap->freePixels();
return JNI_TRUE;
}

void freePixels() {
mInfo = mBitmap->info();
mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
mAllocationSize = mBitmap->getAllocationByteCount();
mRowBytes = mBitmap->rowBytes();
mGenerationId = mBitmap->getGenerationID();
mIsHardware = mBitmap->isHardware();
// 清空了数据
mBitmap.reset();

7.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

1
2
3
4
5
static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
bitmap->freePixels();
return JNI_TRUE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void Bitmap::doFreePixels() {
switch (mPixelStorageType) {
case PixelStorageType::Invalid:
// already free'd, nothing to do
break;
case PixelStorageType::External:
mPixelStorage.external.freeFunc(mPixelStorage.external.address,
mPixelStorage.external.context);
break;
case PixelStorageType::Ashmem:
munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
close(mPixelStorage.ashmem.fd);
break;
case PixelStorageType::Java:
// 只是释放了 Java 层之前创建的引用
JNIEnv *env = jniEnv();
LOG_ALWAYS_FATAL_IF(
mPixelStorage.java.jstrongRef,
"Deleting a bitmap wrapper while there are outstanding strong "
"references! mPinnedRefCount = %d", mPinnedRefCount);
env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
break;
}

if (android::uirenderer::Caches::hasInstance()) {
android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
mPixelRef->getStableID());
}
}

7.0 和 8.0 中 手动调用 recycle 的方法,像素数据会被回收吗

从上面的源码可以看出,如果是
8.0 : 我们手动调用 recycle 方法,数据是会立即释放的,因为像素数据本身就是在 Native 层开辟的。
8.0 以下,就算我们手动调用 recycle 方法,数据也是不会立即释放的,而是 DeleteWeakGlobalRef 交由 Java GC 来回收。

注意:以上的所说的释放数据仅代表释放像素数据,并未释放 Native 层的 Bitmap 对象。

Android M 版本及以前的版本, Bitmap 的内存回收

Android M 版本及以前的版本, Bitmap 的内存回收主要是通过 BitmapFinalizer 来完成的见:
/frameworks/base/graphics/java/android/graphics/Bitmap.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatchInsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}

mWidth = width;
mHeight = height;
mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;
mBuffer = buffer;

mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}

mNativePtr = nativeBitmap;
// 这个对象对象来回收
mFinalizer = new BitmapFinalizer(nativeBitmap);
int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
}

private static class BitmapFinalizer {
private long mNativeBitmap;

// Native memory allocated for the duration of the Bitmap,
// if pixel data allocated into native memory, instead of java byte[]
private int mNativeAllocationByteCount;

BitmapFinalizer(long nativeBitmap) {
mNativeBitmap = nativeBitmap;
}

public void setNativeAllocationByteCount(int nativeByteCount) {
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
}
mNativeAllocationByteCount = nativeByteCount;
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
}
}

@Override
public void finalize() {
try {
super.finalize();
} catch (Throwable t) {
// Ignore
} finally {
// finalize 这里是 GC 回收该对象时会调用
setNativeAllocationByteCount(0);
nativeDestructor(mNativeBitmap);
mNativeBitmap = 0;
}
}
}

private static native void nativeDestructor(long nativeBitmap);

Android N 和 Android O 后,Bitmap 的内存回收

没有了 BitmapFinalizer 类,但在 new Bitmap 时会注册 native 的 Finalizer 方法见: /frameworks/base/graphics/java/android/graphics/Bitmap.java

使用 NativeAllocationRegistry 回收 native 内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Private constructor that must received an already allocated native bitmap
* int (pointer).
*/
// called from JNI
Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}

mWidth = width;
mHeight = height;
mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;

mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}

mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
}

Bitmap 的 Java 对象 GC 之后,对应的 native 内存如何回收

NativeAllocationRegistry 的原理与设计思想

NativeAllocationRegistryAndroid 8.0(API 27)引入的一种辅助回收native内存的机制

  • Bitmap 的内存分配分外两块:Java 堆和 native 堆。我们都知道 JVM 有垃圾回收机制,那么当 Bitmap的Java对象GC之后,对应的 native 堆内存会回收吗?

  • 提出问题

    掌握了NativeAllocationRegistry的作用和使用步骤后,很自然地会有一些疑问:

    • 为什么在Java层对象被垃圾回收后,native内存会自动被回收呢?
    • NativeAllocationRegistry是从Android 8.0(API 27)开始引入,那么在此之前,native内存是如何回收的呢?

使用步骤

Android 8.0(API 27)开始,Android中很多地方可以看到NativeAllocationRegistry的身影,我们以Bitmap为例子介绍NativeAllocationRegistry的使用步骤,涉及文件:Bitmap.javaBitmap.hBitmap.cpp

步骤1:创建 NativeAllocationRegistry

首先,我们看看实例化NativeAllocationRegistry的地方,具体在Bitmap的构造函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// # Android 8.0

// Bitmap.java

// called from JNI
Bitmap(long nativeBitmap,...){
// 省略其他代码...

// 【分析点 1:native 层需要的内存大小】
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
// 【分析点 2:回收函数 nativeGetNativeFinalizer()】
// 【分析点 3:加载回收函数的类加载器:Bitmap.class.getClassLoader()】
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
// 注册 Java 层对象引用与 native 层对象的地址
registry.registerNativeAllocation(this, nativeBitmap);
}

private static final long NATIVE_ALLOCATION_SIZE = 32;
private static native long nativeGetNativeFinalizer();

可以看到,Bitmap的构造函数(在从JNI中调用)中实例化了NativeAllocationRegistry,并传递了三个参数:

参数 解释
classLoader 加载freeFunction函数的类加载器
freeFunction 回收native内存的native函数直接地址
size 分配的native内存大小(单位:字节)
步骤2:注册对象

紧接着,调用了registerNativeAllocation(...),并传递两个参数:

参数 解释
referent Java层对象的引用
nativeBitmap native层对象的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Bitmap.java

// called from JNI
Bitmap(long nativeBitmap,...){
// 省略其他代码...
// 注册 Java 层对象引用与 native 层对象的地址
registry.registerNativeAllocation(this, nativeBitmap);
}

// NativeAllocationRegistry.java

public Runnable registerNativeAllocation(Object referent, long nativePtr) {
// 代码省略,下文补充...
}
步骤3:回收内存

完成前面两步后,当Java层对象被垃圾回收后,NativeAllocationRegistry会自动回收注册的native内存。例如,我们加载几张图片,随后释放Bitmap的引用,可以观察到GC之后,native层的内存也自动回收了:

ava
1
2
3
4
5
tv.setOnClickListener{
val map = HashSet<Any>()
for(index in 0 .. 2){
map.add(BitmapFactory.decodeResource(resources,R.drawable.test))
}
  • GC 前的内存分配情况 —— Android 8.0

img

  • GC 后的内存分配情况 —— Android 8.0

img


-

通过分析NativeAllocationRegistry源码,我们将一步步解答这些问题,请继续往下看。


NativeAllocationRegistry 源码分析

现在我们将视野回到到NativeAllocationRegistry的源码,涉及文件:NativeAllocationRegistry.javaNativeAllocationRegistry_Delegate.javalibcore_util_NativeAllocationRegistry.cpp

构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// NativeAllocationRegistry.java

public class NativeAllocationRegistry {
// 加载 freeFunction 函数的类加载器
private final ClassLoader classLoader;
// 回收 native 内存的 native 函数直接地址
private final long freeFunction;
// 分配的 native 内存大小(字节)
private final long size;

public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}

this.classLoader = classLoader;
this.freeFunction = freeFunction;
this.size = size;
}
}

可以看到,NativeAllocationRegistry的构造函数只是将三个参数保存下来,并没有执行额外操作。以Bitmap为例,三个参数在Bitmap的构造函数中获得,我们继续上一节未完成的分析过程:

  • 分析点 1:native 层需要的内存大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Bitmap.java

// 【分析点 1:native 层需要的内存大小】
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();

public final int getAllocationByteCount() {
if (mRecycled) {
Log.w(TAG, "Called getAllocationByteCount() on a recycle()'d bitmap! "
+ "This is undefined behavior!");
return 0;
}
// 调用 native 方法
return nativeGetAllocationByteCount(mNativePtr);
}

private static final long NATIVE_ALLOCATION_SIZE = 32;

可以看到,nativeSize由固定的32字节加上getAllocationByteCount(),总之,NativeAllocationRegistry需要一个native层内存大小的参数。

  • 分析点 2:回收函数 nativeGetNativeFinalizer()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Bitmap.java

// 【分析点 2:回收函数 nativeGetNativeFinalizer()】
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);

private static native long nativeGetNativeFinalizer();

// Java 层
// ----------------------------------------------------------------------
// native 层

// Bitmap.cpp
static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
// 转为long
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
}

static void Bitmap_destruct(BitmapWrapper* bitmap) {
delete bitmap;
}

可以看到,nativeGetNativeFinalizer()是一个native函数,返回值是一个long,这个值其实相当于Bitmap_destruct()函数的直接地址。很明显,Bitmap_destruct()就是用来回收native层内存的。

那么,Bitmap_destruct()是在哪里调用的呢?继续往下看!

  • 分析点 3:加载回收函数的类加载器
1
2
// Bitmap.java
Bitmap.class.getClassLoader()

另外,NativeAllocationRegistry还需要ClassLoader参数,文档注释指出:**classloader是加载freeFunction所在native库的类加载器**,但是NativeAllocationRegistry内部并没有使用这个参数。这里笔者也不理解为什么需要传递这个参数,如果有知道答案的小伙伴请告诉我一下~

注册对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Bitmap.java

// 注册 Java 层对象引用与 native 层对象的地址
registry.registerNativeAllocation(this, nativeBitmap);

// NativeAllocationRegistry.java

public Runnable registerNativeAllocation(Object referent, long nativePtr) {
if (referent == null) {
throw new IllegalArgumentException("referent is null");
}
if (nativePtr == 0) {
throw new IllegalArgumentException("nativePtr is null");
}

CleanerThunk thunk;
CleanerRunner result;
try {
thunk = new CleanerThunk();
Cleaner cleaner = Cleaner.create(referent, thunk);
result = new CleanerRunner(cleaner);
registerNativeAllocation(this.size);
} catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
applyFreeFunction(freeFunction, nativePtr);
throw vme;
// Other exceptions are impossible.
// Enable the cleaner only after we can no longer throw anything, including OOME.
thunk.setNativePtr(nativePtr);
return result;
}

可以看到,registerNativeAllocation (...)方法参数是**Java层对象引用与native层对象的地址**。函数体乍一看是有点绕,笔者在这里也停留了好长一会。我们简化一下代码,try-catch代码先省略,函数返回值Runnable暂时用不到也先省略,瘦身后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// NativeAllocationRegistry.java

// (简化)
public void registerNativeAllocation(Object referent, long nativePtr) {
CleanerThunk thunk thunk = new CleanerThunk();
// Cleaner 绑定 Java 对象与回收函数
Cleaner cleaner = Cleaner.create(referent, thunk);
// 注册 native 内存
registerNativeAllocation(this.size);
thunk.setNativePtr(nativePtr);
}

private class CleanerThunk implements Runnable {
// 代码省略,下文补充...
}

看到这里,上文提出的第一个疑问就可以解释了,原来NativeAllocationRegistry内部是利用了sun.misc.Cleaner.java机制,简单来说:使用虚引用得知对象被GC的时机,在GC前执行额外的回收工作

# 举一反三

DirectByteBuffer内部也是利用了Cleaner实现堆外内存的释放的。若不了解,请务必阅读:《Java | 堆内存与堆外内存》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private class CleanerThunk implements Runnable {
// native 层对象的地址
private long nativePtr;

public CleanerThunk() {
this.nativePtr = 0;
}

public void run() {
if (nativePtr != 0) {
// 【分析点 4:执行内存回收方法】
applyFreeFunction(freeFunction, nativePtr);
// 【分析点 5:注销 native 内存】
registerNativeFree(size);
}
}

public void setNativePtr(long nativePtr) {
this.nativePtr = nativePtr;
}
}

继续往下看,CleanerThunk其实是Runnable的实现类,run()Java层对象被垃圾回收时触发,主要做了两件事:

  • 分析点 4:执行内存回收方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static native void applyFreeFunction(long freeFunction, long nativePtr);

// NativeAllocationRegistry.cpp

typedef void (*FreeFunction)(void*);

static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*,
jclass,
jlong freeFunction,
jlong ptr) {
void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
FreeFunction nativeFreeFunction = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
// 调用回收函数
nativeFreeFunction(nativePtr);
}

可以看到,applyFreeFunction(...)最终就是执行到了前面提到的内存回收函数,对于Bitmap就是Bitmap_destruct()

  • 分析点 5:注册 / 注销native内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// NativeAllocationRegistry.java

// 注册 native 内存
registerNativeAllocation(this.size);
// 注销 native 内存
registerNativeFree(size);

// 提示:这一层函数其实就是为了将参数转为long
private static void registerNativeAllocation(long size) {
VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE));
}

private static void registerNativeFree(long size) {
VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE));
}

VM注册native内存,比便在内存占用达到界限时触发GC,在该native内存回收时,需要向VM注销该内存量


对比 Android 8.0 之前回收 native 内存的方式

前面我们已经分析完NativeAllocationRegistry的源码了,我们看一看在Android 8.0之前,Bitmap是用什么方法回收native内存的,涉及文件: /frameworks/base/graphics/java/android/graphics/Bitmap.java

  • 共同点:
    • 分配的native层内存需要向VM注册 / 注销
    • 通过一个native层的内存回收函数来回收内存
  • 不同点:
    • NativeAllocationRegistry依赖于sun.misc.Cleaner.java
    • BitmapFinalizer依赖于Object#finalize()

我们知道,finalize()Java对象被垃圾回收时会调用,BitmapFinalizer就是利用了这个机制来回收native层内存的。

1
2
3
4
5
6
7
8
以下是关于 finalize() 方法的一些要点:

finalize() 方法的定义: finalize() 是一个在 Object 类中定义的方法,所有的 Java 类都可以选择覆盖它。它的签名为 protected void finalize() throws Throwable。
垃圾回收触发: 当垃圾回收器决定要回收一个对象时(即对象变得不可达),在回收对象之前,会调用对象的 finalize() 方法。这个方法可能会在对象被回收之前执行一些清理操作,例如释放底层资源或资源的解引用。
finalize() 方法的调用时机不确定: 由于垃圾回收的时机是不确定的,因此 finalize() 方法的调用时机也是不确定的。这意味着不能依赖于它来进行资源的及时释放或其他重要的清理操作。
避免过度依赖 finalize(): 由于 finalize() 方法的调用时机不确定,且可能会影响性能,通常不建议过度依赖它。更好的做法是使用显式的资源管理,例如使用 try-finally 块来确保资源的释放。
不同的垃圾回收方式: Java 使用不同的垃圾回收策略,包括标记-清除、标记-压缩等。在不同的垃圾回收方式中,finalize() 方法的调用时机可能会有所不同。
需要注意的是,自从 Java 9 开始,finalize() 方法已经被标记为废弃(deprecated),这意味着在未来的 Java 版本中可能会被移除。推荐的做法是使用更可靠的资源管理和垃圾回收机制,例如使用 AutoCloseable 接口和 try-with-resources 语句来确保资源的正确释放。

再举几个常用的类在Android 8.0之前的源码为例子,原理都大同小异:Matrix.java (before Android 8.0)Canvas.java (before Android 8.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Matrix.java

@Override
protected void finalize() throws Throwable {
try {
finalizer(native_instance);
} finally {
super.finalize();
}
}
private static native void finalizer(long native_instance);

// Canvas.java

private final CanvasFinalizer mFinalizer;
private static final class CanvasFinalizer {
private long mNativeCanvasWrapper;

public CanvasFinalizer(long nativeCanvas) {
mNativeCanvasWrapper = nativeCanvas;
}

@Override
protected void finalize() throws Throwable {
try {
dispose();
} finally {
super.finalize();
}
}

public void dispose() {
if (mNativeCanvasWrapper != 0) {
finalizer(mNativeCanvasWrapper);
mNativeCanvasWrapper = 0;
}
}
}

public Canvas() {
// 省略其他代码...
mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
}

问题回归

  • NativeAllocationRegistry利用虚引用感知Java对象被回收的时机,来回收native层内存
  • Android 8.0 (API 27)之前,Android通常使用Object#finalize()调用时机来回收native层内存

Bitmap 的内存复用

Bitmap 绝对是我们 Android 开发中最容易引起 OOM 的对象之一,因为其占用的像素数据内存比较大,而加载图片又是很常见的操作。如果不断反复的去开辟和销毁 Bitmap 数据内存,势必可能会引起应用的内存抖动,因此 Google 的开发者也为我们想了一些办法,那就是允许 Bitmap 内存复用,具体如下:

  • 被复用的 Bitmap 必须为 Mutable(通过 BitmapFactory.Options 设置)

    这是因为复用操作涉及对 Bitmap 对象像素数据的修改,而只有 Mutable 的 Bitmap 才允许对其像素数据进行修改。

  • 4.4 之前

    1. 图像格式: 被复用的 Bitmap 和要解码的图像必须具有相同的像素格式,通常是 ARGB_8888 或 RGB_565 格式。这是因为像素格式的不匹配可能导致解码失败或图像显示异常。
    2. 图像尺寸: 被复用的 Bitmap 和要解码的图像必须具有相同的尺寸。如果尺寸不匹配,复用操作将被忽略。
    3. 图像类型: 在 Android 4.4 之前,被复用的 Bitmap 和要解码的图像必须是 JPEG 或 PNG 格式。其他格式的图像将无法被复用。
    4. inSampleSize 设置: BitmapFactory.OptionsinSampleSize 字段必须设置为 1,以表示不进行图像的缩放。这是因为在进行图像缩放的情况下,图像的尺寸和复用的 Bitmap 尺寸可能不匹配。
  • 4.4 以后

    BitmapFactory.OptionsinBitmap 字段用于指定要复用的 Bitmap 对象。通过设置这个字段,你可以告诉解码器将解码后的图像数据存储在已经存在的 Bitmap 中,以便进行复用。

    同时,要注意以下几点:

    1. 被解码的图像的内存大小不应该超过要复用的 Bitmap 的内存大小。
    2. 被解码的图像和要复用的 Bitmap 的像素格式和尺寸应该匹配。
    3. 解码时需要设置 inSampleSize 为 1,以确保图像不会被缩放。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 不复用的写法,消耗内存 32 M
logMemory();
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test2);
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test2);
logMemory();
// 复用的写法,消耗内存 16 M
logMemory();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test2, options);
options.inBitmap = bitmap1;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test2, options);
logMemory();

在 Glide 中,Bitmap 复用是通过使用 BitmapPool 接口来实现的。BitmapPool 是一个用于管理和复用 Bitmap 对象的接口,它允许 Glide 在加载和显示图像时复用之前分配的 Bitmap 对象,从而减少内存分配和垃圾回收的开销。

以下是简要的 Glide Bitmap 复用的流程:

  1. Glide 首先会尝试从 BitmapPool 中获取一个可复用的 Bitmap 对象。如果 BitmapPool 中存在满足条件的 Bitmap,就会使用它。
  2. 如果 BitmapPool 中没有可复用的 Bitmap,Glide 会根据要加载的图像的尺寸和像素格式创建一个新的 Bitmap 对象。
  3. 加载和显示图像时,Glide 会将 Bitmap 对象放入 BitmapPool,以便以后可以复用。

为了更深入地了解 Glide 中 Bitmap 复用的实现,你可以查阅 Glide 的源代码,并关注以下关键部分:

  1. BitmapPool 接口及其实现类:Glide 使用 BitmapPool 接口来管理 Bitmap 对象的复用。BitmapPool 接口定义了一系列的方法,如 get() 获取可复用的 Bitmap,put() 将 Bitmap 放回池中等。在 Glide 源代码中,你可以查看 LruBitmapPool 和其他的 BitmapPool 实现类。
  2. BitmapImageViewTarget:Glide 使用 BitmapImageViewTarget 来将 Bitmap 显示在 ImageView 中。在这个类中,你可以看到 Glide 如何从 BitmapPool 中获取可复用的 Bitmap 对象。
  3. 图片加载流程:Glide 的图片加载流程涉及到许多类和接口,包括请求管理、数据加载、资源解码等。在这些类中,你可以找到 Glide 如何利用 Bitmap 复用来优化内存使用。

Glide Bitmap 复用源代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
package com.bumptech.glide.load.engine.bitmap_recycle;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentCallbacks2;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.util.Synthetic;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an
* {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s
* and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least
* recently used bucket in order to keep the pool below a given maximum size limit.
*/
public class LruBitmapPool implements BitmapPool {
private static final String TAG = "LruBitmapPool";
private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888;

private final LruPoolStrategy strategy;
private final Set<Bitmap.Config> allowedConfigs;
private final long initialMaxSize;
private final BitmapTracker tracker;

private long maxSize;
private long currentSize;
private int hits;
private int misses;
private int puts;
private int evictions;

// Exposed for testing only.
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}

/**
* Constructor for LruBitmapPool.
*
* @param maxSize The initial maximum size of the pool in bytes.
*/
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}

/**
* Constructor for LruBitmapPool.
*
* @param maxSize The initial maximum size of the pool in bytes.
* @param allowedConfigs A white listed put of {@link android.graphics.Bitmap.Config} that are
* allowed to be put into the pool. Configs not in the allowed put will be rejected.
*/
// Public API.
@SuppressWarnings("unused")
public LruBitmapPool(long maxSize, Set<Bitmap.Config> allowedConfigs) {
this(maxSize, getDefaultStrategy(), allowedConfigs);
}

/** Returns the number of cache hits for bitmaps in the pool. */
public long hitCount() {
return hits;
}

/** Returns the number of cache misses for bitmaps in the pool. */
public long missCount() {
return misses;
}

/** Returns the number of bitmaps that have been evicted from the pool. */
public long evictionCount() {
return evictions;
}

/** Returns the current size of the pool in bytes. */
public long getCurrentSize() {
return currentSize;
}

@Override
public long getMaxSize() {
return maxSize;
}

@Override
public synchronized void setSizeMultiplier(float sizeMultiplier) {
maxSize = Math.round(initialMaxSize * sizeMultiplier);
evict();
}

@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
if (!bitmap.isMutable()
|| strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(
TAG,
"Reject bitmap from pool"
+ ", bitmap: "
+ strategy.logBitmap(bitmap)
+ ", is mutable: "
+ bitmap.isMutable()
+ ", is allowed config: "
+ allowedConfigs.contains(bitmap.getConfig()));
}
bitmap.recycle();
return;
}

final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);

puts++;
currentSize += size;

if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();

evict();
}

private void evict() {
trimToSize(maxSize);
}

@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
// Bitmaps in the pool contain random data that in some cases must be cleared for an image
// to be rendered correctly. we shouldn't force all consumers to independently erase the
// contents individually, so we do so here. See issue #131.
result.eraseColor(Color.TRANSPARENT);
} else {
result = createBitmap(width, height, config);
}

return result;
}

@NonNull
@Override
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result == null) {
result = createBitmap(width, height, config);
}
return result;
}

@NonNull
private static Bitmap createBitmap(int width, int height, @Nullable Bitmap.Config config) {
return Bitmap.createBitmap(width, height, config != null ? config : DEFAULT_CONFIG);
}

@TargetApi(Build.VERSION_CODES.O)
private static void assertNotHardwareConfig(Bitmap.Config config) {
// Avoid short circuiting on sdk int since it breaks on some versions of Android.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}

if (config == Bitmap.Config.HARDWARE) {
throw new IllegalArgumentException(
"Cannot create a mutable Bitmap with config: "
+ config
+ ". Consider setting Downsampler#ALLOW_HARDWARE_CONFIG to false in your"
+ " RequestOptions and/or in GlideBuilder.setDefaultRequestOptions");
}
}

@Nullable
private synchronized Bitmap getDirtyOrNull(
int width, int height, @Nullable Bitmap.Config config) {
assertNotHardwareConfig(config);
// Config will be null for non public config types, which can lead to transformations naively
// passing in null as the requested config here. See issue #194.
final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
if (result == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config));
}
misses++;
} else {
hits++;
currentSize -= strategy.getSize(result);
tracker.remove(result);
normalize(result);
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config));
}
dump();

return result;
}

// Setting these two values provides Bitmaps that are essentially equivalent to those returned
// from Bitmap.createBitmap.
private static void normalize(Bitmap bitmap) {
bitmap.setHasAlpha(true);
maybeSetPreMultiplied(bitmap);
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private static void maybeSetPreMultiplied(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
bitmap.setPremultiplied(true);
}
}

@Override
public void clearMemory() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "clearMemory");
}
trimToSize(0);
}

@SuppressLint("InlinedApi")
@Override
public void trimMemory(int level) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "trimMemory, level=" + level);
}
if ((level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
|| ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
&& (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN))) {
clearMemory();
} else if ((level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
|| (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL)) {
trimToSize(getMaxSize() / 2);
}
}

private synchronized void trimToSize(long size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}

private void dump() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
dumpUnchecked();
}
}

private void dumpUnchecked() {
Log.v(
TAG,
"Hits="
+ hits
+ ", misses="
+ misses
+ ", puts="
+ puts
+ ", evictions="
+ evictions
+ ", currentSize="
+ currentSize
+ ", maxSize="
+ maxSize
+ "\nStrategy="
+ strategy);
}

private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}

@TargetApi(Build.VERSION_CODES.O)
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// GIFs, among other types, end up with a native Bitmap config that doesn't map to a java
// config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured
// and are suitable for re-use.
configs.add(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
configs.remove(Bitmap.Config.HARDWARE);
}
return Collections.unmodifiableSet(configs);
}

private interface BitmapTracker {
void add(Bitmap bitmap);

void remove(Bitmap bitmap);
}

@SuppressWarnings("unused")
// Only used for debugging
private static class ThrowingBitmapTracker implements BitmapTracker {
private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>());

@Override
public void add(Bitmap bitmap) {
if (bitmaps.contains(bitmap)) {
throw new IllegalStateException(
"Can't add already added bitmap: "
+ bitmap
+ " ["
+ bitmap.getWidth()
+ "x"
+ bitmap.getHeight()
+ "]");
}
bitmaps.add(bitmap);
}

@Override
public void remove(Bitmap bitmap) {
if (!bitmaps.contains(bitmap)) {
throw new IllegalStateException("Cannot remove bitmap not in tracker");
}
bitmaps.remove(bitmap);
}
}

private static final class NullBitmapTracker implements BitmapTracker {

@Synthetic
NullBitmapTracker() {}

@Override
public void add(Bitmap bitmap) {
// Do nothing.
}

@Override
public void remove(Bitmap bitmap) {
// Do nothing.
}
}
}

下面对代码进行逐段分析:

  1. 导入相关的包和类。
  2. 定义了一个名为 “LruBitmapPool” 的类,实现了 “BitmapPool” 接口,用于管理位图内存池。
  3. 定义了一些常量,如默认的位图配置和日志标签。
  4. 声明了一些成员变量,包括内存池策略、允许的位图配置、最大大小、当前大小、命中次数、未命中次数、添加次数和驱逐次数等。
  5. 构造函数,初始化内存池大小、策略和允许的位图配置等。
  6. 提供了一些公开方法用于获取统计信息,如命中次数、未命中次数、驱逐次数、当前大小等。
  7. 实现了 “BitmapPool” 接口中的一些方法,包括 setSizeMultiplier、put、get、getDirty、clearMemory、trimMemory 等。
  8. 定义了一些辅助方法,如 evict、assertNotHardwareConfig、normalize、maybeSetPreMultiplied、getDirtyOrNull、trimToSize、dump 和 dumpUnchecked 等。
  9. 定义了 “BitmapTracker” 接口,其中包含了添加和移除位图的方法。
  10. 定义了一些实现了 “BitmapTracker” 接口的类,如 “ThrowingBitmapTracker” 和 “NullBitmapTracker”。

总体来说,这段代码实现了一个基于 LRU(Least Recently Used)算法的位图内存池,用于管理位图对象的内存分配和回收。它可以跟踪位图的使用情况,限制内存池的大小,通过策略进行位图的管理和驱逐,以及提供一些统计信息用于优化和调试。

Gilde 是如何实现 Bitmap 复用的?

在这段代码中,实现位图复用的核心思想是通过内存池策略(LruPoolStrategy)来管理位图的存储和获取。下面我将详细解释如何实现位图的复用:

  1. put(Bitmap bitmap) 方法:
    当你想要回收一个位图时,会调用这个方法。它会首先判断位图是否为空、是否已经被回收、是否是可变的位图,位图大小是否超过了内存池的最大大小,以及位图的配置是否允许被放入内存池。如果这些条件都满足,它会将位图放入内存池策略中,并更新当前内存池的大小。

  2. get(int width, int height, Bitmap.Config config)getDirty(int width, int height, Bitmap.Config config) 方法:
    当你想要获取一个位图时,会调用这两个方法之一。它首先会尝试从内存池策略中获取满足条件的位图,如果找到则返回该位图,否则会创建一个新的位图并返回。这里的位图创建使用了 Bitmap.createBitmap() 方法,确保了返回的位图是可复用的。

  3. trimToSize(long size) 方法:
    当内存池的当前大小超过了最大限制时,会调用这个方法进行驱逐。它会循环地从内存池策略中移除最近未使用的位图,直到当前内存池大小小于等于指定的大小。

  4. LruPoolStrategy 接口的实现:
    这个接口定义了位图内存池策略的规范,包括位图的添加、获取、移除等操作。在这段代码中,有两个实现类:SizeConfigStrategyAttributeStrategy,分别在不同的 Android 版本上进行位图的管理。

通过这些机制,当位图被回收时,它会被添加到内存池中,下次需要位图时会优先从内存池中获取,以达到位图复用的效果。这样可以减少内存分配和回收的开销,提高性能和效率。


BitMap 创建,内存申请与内存模型相关内容整理参考自

作者:红橙Darren
链接:https://www.jianshu.com/p/8e8ad414237e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

具体回收细节参考自

版权声明:本文为CSDN博主「shihongyu12345」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shihongyu12345/article/details/89681948

NativeAllocationRegistry 的原理与设计思想 摘录自

作者:彭旭锐
链接:https://www.jianshu.com/p/6f042f9e47a8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

部分相关知识来自于 chat-GPT 3.5

ViewModel 源代码分析 (二)

  • viewmodel

    • CreationExtras
      ViewModel创建过程中传递额外参数的容器。

    • InitializerViewModelFactory

      ViewModelProvider.Factory接口实现类,使用ViewModelInitializer来创建ViewModel实例。

    • InitializerViewModelFactoryBuilder

      用于构建InitializerViewModelFactory的工具,通常以DSL形式提供。

    • MutableCreationExtras

      可变版本的CreationExtras,允许添加或修改额外参数。

    • ViewModelFactoryDsl

      一个DSL(领域特定语言),用于更声明式地定义如何创建ViewModel。

    • ViewModelInitializer

      用于初始化ViewModel的类,通常与ViewModelFactoryDsl一起使用。

    • InitializerViewModelFactory

      提供了使用InitializerViewModelFactory的Kotlin扩展。

  • AndroidViewModel

    AndroidViewModel是ViewModel的一个子类,它接受应用程序的Application作为上下文,这对于需要访问Application资源的ViewModel特别有用。

  • HasDefaultViewModelProviderFactory

    一个接口,标识一个类拥有默认的ViewModelProvider.Factory,用于创建ViewModel。

  • ViewModel

  • ViewModelLazy

    一个提供懒加载ViewModel实例的工具类。

  • ViewModelProvider

    用于获取ViewModel实例,确保配置更改时ViewModel可以持续使用。

  • ViewModelStore

    用于保存ViewModel实例的类,以便它们可以跨配置更改持续存在。

  • ViewModelStoreOwner

    一个接口,标识一个类可以拥有ViewModelStore。

  • ViewTreeViewModelStoreOwner

    用于从视图树中查找ViewModelStoreOwner的工具类。

  • ViewTreeViewModelKt

    提供了操作和查询视图树中ViewModel的Kotlin扩展

ViewModelStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package androidx.lifecycle;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class ViewModelStore {

private final HashMap<String, ViewModel> mMap = new HashMap<>();

final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}

final ViewModel get(String key) {
return mMap.get(key);
}

Set<String> keys() {
return new HashSet<>(mMap.keySet());
}

/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}

如果有不想保存的数据可以在存储前调用 ViewModelStore clear 函数

使用场景

Activity或Fragment销毁时

在Activity或Fragment的onDestroy方法中调用ViewModelStoreclear方法,可以确保所有关联的ViewModel能够及时清理资源。这是因为,在某些情况下(如用户明确退出Activity或Fragment),保留ViewModel不再有意义,应该清除所有资源以避免内存泄漏。

示例代码:

1
2
3
4
5
6
7
8
javaCopy code
@Override
protected void onDestroy() {
super.onDestroy();
if (isFinishing()) {
viewModelStore.clear();
}
}

这里,isFinishing()用于检查Activity是否正在结束(用户退出或因其他原因被销毁),如果是,则清理ViewModelStore中的所有ViewModel。

在使用ViewModel时实现资源管理

如果你的应用程序具有复杂的业务逻辑,可能会创建多个ViewModel用于不同的数据处理和业务逻辑。在这种情况下,当确定某些ViewModel不再需要时,可以通过调用clear方法释放这些ViewModel占用的资源。

3. 单元测试

在进行单元测试时,特别是在测试涉及ViewModel生命周期的逻辑时,clear方法可以在测试完成后被调用来清理测试环境,保证每个测试运行的隔离性。

注意事项:

虽然clear方法的使用可以帮助管理资源,但需要谨慎使用,以避免在不适当的时机释放资源导致的应用崩溃或不稳定。通常,Android框架会自动处理ViewModel的生命周期,只有在特定情况下,如Activity或Fragment被销毁时,才需要手动调用clear方法。

ViewModelStoreOwner

1
2
3
4
5
6
7
8
9
10
package androidx.lifecycle;

import androidx.annotation.NonNull;

@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}

我们来看一下到底谁是 ViewModelStoreOwner

ViewModelStoreOwner - ComponentActivity

1
2
3
4
5
6
7
8
9
10
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}

ViewModelStoreOwner - Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
if (getMinimumMaxLifecycleState() == Lifecycle.State.INITIALIZED.ordinal()) {
throw new IllegalStateException("Calling getViewModelStore() before a Fragment "
+ "reaches onCreate() when using setMaxLifecycle(INITIALIZED) is not "
+ "supported");
}
return mFragmentManager.getViewModelStore(this);
}

ViewTreeViewModelStoreOwner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package androidx.lifecycle;

import android.view.View;
import android.view.ViewParent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.viewmodel.R;

public class ViewTreeViewModelStoreOwner {
private ViewTreeViewModelStoreOwner() {
// No instances
}

public static void set(@NonNull View view, @Nullable ViewModelStoreOwner viewModelStoreOwner) {
view.setTag(R.id.view_tree_view_model_store_owner, viewModelStoreOwner);
}


@Nullable
public static ViewModelStoreOwner get(@NonNull View view) {
ViewModelStoreOwner found = (ViewModelStoreOwner) view.getTag(
R.id.view_tree_view_model_store_owner);
if (found != null) return found;
ViewParent parent = view.getParent();
while (found == null && parent instanceof View) {
final View parentView = (View) parent;
found = (ViewModelStoreOwner) parentView.getTag(R.id.view_tree_view_model_store_owner);
parent = parentView.getParent();
}
return found;
}
}

ViewTreeViewModelStoreOwner 与 ViewModelStoreOwner 有什么区别

ViewModelStoreOwner

  • 定义ViewModelStoreOwner是一个接口,定义了可以拥有并管理ViewModelStore的类。ViewModelStore是一个容器,用于存储和管理ViewModel实例,确保它们能够跨配置更改(如屏幕旋转)生存下来。
  • 用途:这个接口通常由Activity和Fragment实现,使它们能够持有ViewModel实例。通过这种方式,Activity或Fragment可以在内部管理其ViewModels的生命周期,确保在发生配置更改时,ViewModels不会被销毁并重新创建。
  • 实现方式:在使用ViewModel时,开发者通常不需要直接实现ViewModelStoreOwner接口。例如,AppCompatActivityFragment已经实现了这个接口,提供了对ViewModelStore的访问。

ViewTreeViewModelStoreOwner

  • 定义ViewTreeViewModelStoreOwner是与Android视图树(View Tree)相关的工具类。它提供了静态方法来从视图树中查找ViewModelStoreOwner。这是通过在视图的Tag中设置和获取ViewModelStoreOwner实现的。
  • 用途ViewTreeViewModelStoreOwner的主要用途是在视图层级中找到最近的ViewModelStoreOwner。这对于自定义视图或视图组件特别有用,这些组件需要访问ViewModel但不直接与Activity或Fragment关联。
  • 使用场景:当你有一个嵌套在深层视图层次结构中的自定义视图,并且这个视图需要访问ViewModel时,可以使用ViewTreeViewModelStoreOwner.get(view)来找到最近的ViewModelStoreOwner实例。这使得即使在复杂的视图结构中,自定义视图也能正确地管理和访问ViewModel实例。

区别

  • 作用范围ViewModelStoreOwner直接关联于可以持有ViewModelStore的实体(如Activity或Fragment),而ViewTreeViewModelStoreOwner是一个工具类,用于在视图树中查找这些实体。
  • 使用上下文ViewModelStoreOwner通常用于直接管理ViewModels的生命周期,特别是在Activity和Fragment中。ViewTreeViewModelStoreOwner则用于在视图层级中寻找ViewModelStoreOwner,便于自定义视图或组件访问ViewModels
  • 实现与调用:Activity和Fragment通常自动实现ViewModelStoreOwner接口。相反,ViewTreeViewModelStoreOwner提供了一种机制,允许在视图树中任意位置将视图与最近的ViewModelStoreOwner关联起来,而无需直接实现或管理ViewModelStoreOwner接口。

简而言之,ViewModelStoreOwner关注于拥有和管理ViewModelStore的能力,而ViewTreeViewModelStoreOwner提供了一种查找视图树中最近的ViewModelStoreOwner的方法,使得在复杂的视图结构中的组件也能访问和使用ViewModels

ViewModelStoreOwner 的 set 方法

ViewModelStoreOwner 的 set 方法是什么时候被调用的,被添加到哪个 View 的 Tag 中?

androidx.activity.ComponentActivity 中的调用时机

androidx.activity.ComponentActivity#setContentView(android.view.View)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
super.setContentView(layoutResID);
}

@Override
public void setContentView(@SuppressLint({"UnknownNullness", "MissingNullability"}) View view) {
initViewTreeOwners();
super.setContentView(view);
}

@Override
public void setContentView(@SuppressLint({"UnknownNullness", "MissingNullability"}) View view,
@SuppressLint({"UnknownNullness", "MissingNullability"})
ViewGroup.LayoutParams params) {
initViewTreeOwners();
super.setContentView(view, params);
}

androidx.activity.ComponentActivity#addContentView

1
2
3
4
5
6
7
@Override
public void addContentView(@SuppressLint({"UnknownNullness", "MissingNullability"}) View view,
@SuppressLint({"UnknownNullness", "MissingNullability"})
ViewGroup.LayoutParams params) {
initViewTreeOwners();
super.addContentView(view, params);
})

在 initViewTreeOwners 中调用 ViewModelStoreOwner 的 set

androidx.activity.ComponentActivity#initViewTreeOwners

1
2
3
4
5
6
7
8
private void initViewTreeOwners() {
// Set the view tree owners before setting the content view so that the inflation process
// and attach listeners will see them already present
ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this);
ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this);
ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(), this);
ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(), this);
}

这里顺便提一下 ViewTreeLifecycleOwner,ViewTreeSavedStateRegistryOwner,ViewTreeOnBackPressedDispatcherOwner

在Android Jetpack架构组件中,ViewTreeLifecycleOwnerViewTreeSavedStateRegistryOwnerViewTreeOnBackPressedDispatcherOwner是用于在视图树中传递和存储与生命周期、状态保存和返回按钮事件处理相关的对象的工具类。它们允许开发者在任何视图层级中访问与这些功能相关的对象,增强了组件化和模块化开发的灵活性。

ViewTreeLifecycleOwner

ViewTreeLifecycleOwnerLifecycleOwner接口相关,后者是一个类可以实现的接口,表示这个类有一个生命周期,比如Activity或Fragment。ViewTreeLifecycleOwner提供了静态方法来在视图树中设置和获取LifecycleOwner。这使得在视图树中的任何位置,都可以访问到与之相关联的LifecycleOwner,进而可以根据生命周期事件来管理资源,比如开始/停止数据加载、绑定/解绑资源等。

ViewTreeSavedStateRegistryOwner

ViewTreeSavedStateRegistryOwnerSavedStateRegistryOwner接口相关,后者管理和存储状态,以便在进程被杀后恢复状态。通过ViewTreeSavedStateRegistryOwner,开发者可以为视图树中的任何视图设置和获取与状态保存相关的SavedStateRegistryOwner。这使得即使是自定义视图也能够利用Android的状态保存机制,无需依赖于Activity或Fragment来保存和恢复状态。

ViewTreeOnBackPressedDispatcherOwner

ViewTreeOnBackPressedDispatcherOwnerOnBackPressedDispatcherOwner接口相关,后者提供了一个组件化的方式来处理返回按钮事件。ViewTreeOnBackPressedDispatcherOwner允许开发者为视图树中的任何视图设置和获取OnBackPressedDispatcherOwner。这意味着在任何视图中,都可以独立处理返回按钮的点击事件,而不是仅限于在Activity中处理。这对于构建复杂的用户界面和处理嵌套的用户交互特别有用。

使用场景和好处

  • 组件化开发:这些工具类支持更加组件化的开发方式,视图或组件可以独立于Activity或Fragment使用这些架构组件。
  • 灵活性和可重用性:使得自定义视图和组件更加灵活,易于重用,因为它们可以在不依赖特定Activity或Fragment的情况下,管理自己的生命周期、状态保存和返回按钮处理。
  • 简化代码:简化了在自定义视图或深层嵌套的视图结构中访问这些功能的方式,避免了复杂的回调结构或过度依赖于上下文。

综上所述,ViewTreeLifecycleOwnerViewTreeSavedStateRegistryOwnerViewTreeOnBackPressedDispatcherOwner为Android开发者提供了强大的工具,以更加灵活和模块化的方式处理生命周期管理、状态保存和返回按钮事件处理。

ViewTreeViewModelKt

Google 也准备了一个 Kotlin 使用的扩展函数配合 ViewModelStoreOwner

1
2
3
4
5
6
package androidx.lifecycle

import android.view.View

public fun View.findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? =
ViewTreeViewModelStoreOwner.get(this)

ViewModel 源代码分析 (三)

  • viewmodel

    • CreationExtras
      ViewModel创建过程中传递额外参数的容器。

    • InitializerViewModelFactory

      ViewModelProvider.Factory接口实现类,使用ViewModelInitializer来创建ViewModel实例。

    • InitializerViewModelFactoryBuilder

      用于构建InitializerViewModelFactory的工具,通常以DSL形式提供。

    • MutableCreationExtras

      可变版本的CreationExtras,允许添加或修改额外参数。

    • ViewModelFactoryDsl

      一个DSL(领域特定语言),用于更声明式地定义如何创建ViewModel。

    • ViewModelInitializer

      用于初始化ViewModel的类,通常与ViewModelFactoryDsl一起使用。

    • InitializerViewModelFactory

      提供了使用InitializerViewModelFactory的Kotlin扩展。

  • AndroidViewModel

    AndroidViewModel是ViewModel的一个子类,它接受应用程序的Application作为上下文,这对于需要访问Application资源的ViewModel特别有用。

  • HasDefaultViewModelProviderFactory

    一个接口,标识一个类拥有默认的ViewModelProvider.Factory,用于创建ViewModel。

  • ViewModel

  • ViewModelLazy

    一个提供懒加载ViewModel实例的工具类。

  • ViewModelProvider

    用于获取ViewModel实例,确保配置更改时ViewModel可以持续使用。

  • ViewModelStore

    用于保存ViewModel实例的类,以便它们可以跨配置更改持续存在。

  • ViewModelStoreOwner

    一个接口,标识一个类可以拥有ViewModelStore。

  • ViewTreeViewModelStoreOwner

    用于从视图树中查找ViewModelStoreOwner的工具类。

  • ViewTreeViewModelKt

    提供了操作和查询视图树中ViewModel的Kotlin扩展

页面旋转的时候 ViewModelStore 到底被谁持有了

ViewModelStore

Activity 的销毁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
Class<? extends Activity> activityClass = null;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
activityClass = r.activity.getClass();
r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
r.activity.mFinished = true;
}

performPauseActivityIfNeeded(r, "destroy");

if (!r.stopped) {
callActivityOnStop(r, false /* saveState */, "destroy");
}
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to retain activity "
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
}
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity);
if (!r.activity.mCalled) {
throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+ " did not call through to super.onDestroy()");
}
if (r.window != null) {
r.window.closeAllPanels();
}
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to destroy activity "
+ safeToComponentShortString(r.intent) + ": " + e.toString(), e);
}
}
r.setState(ON_DESTROY);
mLastReportedWindowingMode.remove(r.activity.getActivityToken());
schedulePurgeIdler();
synchronized (this) {
if (mSplashScreenGlobal != null) {
mSplashScreenGlobal.tokenDestroyed(r.token);
}
}
// updatePendingActivityConfiguration() reads from mActivities to update
// ActivityClientRecord which runs in a different thread. Protect modifications to
// mActivities to avoid race.
synchronized (mResourcesManager) {
mActivities.remove(r.token);
}
StrictMode.decrementExpectedActivityCount(activityClass);
}

关键代码

1
2
3
4
5
6
7
8
9
10
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to retain activity "
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
}

android.app.Activity

retainNonConfigurationInstances

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

// We're already stopped but we've been asked to retain.
// Our fragments are taken care of but we need to mark the loaders for retention.
// In order to do this correctly we need to restart the loaders first before
// handing them off to the next activity.
mFragments.doLoaderStart();
mFragments.doLoaderStop(true);
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

if (activity == null && children == null && fragments == null && loaders == null
&& mVoiceInteractor == null) {
return null;
}

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}

onRetainNonConfigurationInstance

1
2
3
public Object onRetainNonConfigurationInstance() {
return null;
}

onRetainNonConfigurationChildInstances

1
2
3
4
@Nullable
HashMap<String,Object> onRetainNonConfigurationChildInstances() {
return null;
}

androidx.activity.ComponentActivity

onRetainNonConfigurationInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();

ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}

if (viewModelStore == null && custom == null) {
return null;
}

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}

关键代码

1
2
3
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;

androidx.activity.ComponentActivity 中实现了 onRetainNonConfigurationInstance

ViewModelStore 被保存到了 NonConfigurationInstances.viewModelStore 中

NonConfigurationInstances 又被 ActivityClientRecord 持有

android.app.ActivityThread.ActivityClientRecord 源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
public static final class ActivityClientRecord {
@UnsupportedAppUsage
public IBinder token;
public IBinder assistToken;
// A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
// used without security checks
public IBinder shareableActivityToken;
// The token of the initial TaskFragment that embedded this activity. Do not rely on it
// after creation because the activity could be reparented.
@Nullable public IBinder mInitialTaskFragmentToken;
int ident;
@UnsupportedAppUsage
Intent intent;
String referrer;
IVoiceInteractor voiceInteractor;
Bundle state;
PersistableBundle persistentState;
@UnsupportedAppUsage
Activity activity;
Window window;
Activity parent;
String embeddedID;
Activity.NonConfigurationInstances lastNonConfigurationInstances;
// TODO(lifecycler): Use mLifecycleState instead.
@UnsupportedAppUsage
boolean paused;
@UnsupportedAppUsage
boolean stopped;
boolean hideForNow;
Configuration createdConfig;
Configuration overrideConfig;
// Used for consolidating configs before sending on to Activity.
private Configuration tmpConfig = new Configuration();
// Callback used for updating activity override config and camera compat control state.
ViewRootImpl.ActivityConfigCallback activityConfigCallback;
ActivityClientRecord nextIdle;

// Indicates whether this activity is currently the topmost resumed one in the system.
// This holds the last reported value from server.
boolean isTopResumedActivity;
// This holds the value last sent to the activity. This is needed, because an update from
// server may come at random time, but we always need to report changes between ON_RESUME
// and ON_PAUSE to the app.
boolean lastReportedTopResumedState;

ProfilerInfo profilerInfo;

@UnsupportedAppUsage
ActivityInfo activityInfo;
@UnsupportedAppUsage
CompatibilityInfo compatInfo;
@UnsupportedAppUsage
public LoadedApk packageInfo;

List<ResultInfo> pendingResults;
List<ReferrerIntent> pendingIntents;

boolean startsNotResumed;
public final boolean isForward;
int pendingConfigChanges;
// Whether we are in the process of performing on user leaving.
boolean mIsUserLeaving;

Window mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@UnsupportedAppUsage
boolean mPreserveWindow;

/** The options for scene transition. */
ActivityOptions mActivityOptions;

/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;

@LifecycleState
private int mLifecycleState = PRE_ON_CREATE;

private SizeConfigurationBuckets mSizeConfigurations;

@VisibleForTesting
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public ActivityClientRecord() {
this.isForward = false;
init();
}

public ActivityClientRecord(IBinder token, Intent intent, int ident,
ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
IBinder initialTaskFragmentToken) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
this.ident = ident;
this.intent = intent;
this.referrer = referrer;
this.voiceInteractor = voiceInteractor;
this.activityInfo = info;
this.compatInfo = compatInfo;
this.state = state;
this.persistentState = persistentState;
this.pendingResults = pendingResults;
this.pendingIntents = pendingNewIntents;
this.isForward = isForward;
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
mInitialTaskFragmentToken = initialTaskFragmentToken;
init();
}

/** Common initializer for all constructors. */
private void init() {
parent = null;
embeddedID = null;
paused = false;
stopped = false;
hideForNow = false;
nextIdle = null;
activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
@Override
public void onConfigurationChanged(Configuration overrideConfig,
int newDisplayId) {
if (activity == null) {
throw new IllegalStateException(
"Received config update for non-existing activity");
}
activity.mMainThread.handleActivityConfigurationChanged(
ActivityClientRecord.this, overrideConfig, newDisplayId);
}

@Override
public void requestCompatCameraControl(boolean showControl,
boolean transformationApplied, ICompatCameraControlCallback callback) {
if (activity == null) {
throw new IllegalStateException(
"Received camera compat control update for non-existing activity");
}
ActivityClient.getInstance().requestCompatCameraControl(
activity.getResources(), token, showControl, transformationApplied,
callback);
}

};
}

/** Get the current lifecycle state. */
public int getLifecycleState() {
return mLifecycleState;
}

/** Update the current lifecycle state for internal bookkeeping. */
public void setState(@LifecycleState int newLifecycleState) {
mLifecycleState = newLifecycleState;
switch (mLifecycleState) {
case ON_CREATE:
paused = true;
stopped = true;
break;
case ON_START:
paused = true;
stopped = false;
break;
case ON_RESUME:
paused = false;
stopped = false;
break;
case ON_PAUSE:
paused = true;
stopped = false;
break;
case ON_STOP:
paused = true;
stopped = true;
break;
}
}

private boolean isPreHoneycomb() {
return activity != null && activity.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.HONEYCOMB;
}

private boolean isPreP() {
return activity != null && activity.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.P;
}

public boolean isPersistable() {
return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
}

public boolean isVisibleFromServer() {
return activity != null && activity.mVisibleFromServer;
}

public String toString() {
ComponentName componentName = intent != null ? intent.getComponent() : null;
return "ActivityRecord{"
+ Integer.toHexString(System.identityHashCode(this))
+ " token=" + token + " " + (componentName == null
? "no component name" : componentName.toShortString())
+ "}";
}

public String getStateString() {
StringBuilder sb = new StringBuilder();
sb.append("ActivityClientRecord{");
sb.append("paused=").append(paused);
sb.append(", stopped=").append(stopped);
sb.append(", hideForNow=").append(hideForNow);
sb.append(", startsNotResumed=").append(startsNotResumed);
sb.append(", isForward=").append(isForward);
sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
sb.append(", preserveWindow=").append(mPreserveWindow);
if (activity != null) {
sb.append(", Activity{");
sb.append("resumed=").append(activity.mResumed);
sb.append(", stopped=").append(activity.mStopped);
sb.append(", finished=").append(activity.isFinishing());
sb.append(", destroyed=").append(activity.isDestroyed());
sb.append(", startedActivity=").append(activity.mStartedActivity);
sb.append(", changingConfigurations=").append(activity.mChangingConfigurations);
sb.append("}");
}
sb.append("}");
return sb.toString();
}
}

ActivityClientRecord是Android系统内部使用的一个类,主要存在于ActivityThread中,用于管理和跟踪Activity的状态信息。这个类作为Activity实例状态的容器,承载了Activity的生命周期状态、Intent信息、窗口和其他与Activity实例相关的配置信息。ActivityClientRecordActivityThread处理Activity生命周期事件时的关键数据结构,它帮助ActivityThread高效地管理Activity的创建、启动、恢复、暂停、停止以及销毁过程。

主要职责

  • 生命周期管理ActivityClientRecord存储了Activity的生命周期状态,例如是否处于暂停状态、是否已经创建等,这对于ActivityThread正确处理Activity生命周期事件至关重要。

  • 配置管理:它还包含了与Activity实例相关的配置信息,如屏幕方向、主题等,这些信息对于Activity在不同配置下正确表现自己非常重要。

  • Intent处理ActivityClientRecord包含了启动Activity所用的Intent,这个Intent携带了从其他组件传递给Activity的数据。

  • 窗口管理:它还管理着Activity的窗口信息,包括用于描绘Activity UI的窗口。这对于在屏幕上正确渲染Activity视图非常关键。

工作原理

当一个新的Activity被启动时,ActivityThread会接收到一个来自AMS(Activity Manager Service)的请求,此请求包含了创建或启动Activity所需的所有信息,包括Intent和Activity的配置信息。ActivityThread随后会创建一个新的ActivityClientRecord实例,用来存储这些信息,并根据这些信息处理Activity的创建和启动过程。

在Activity的生命周期中,ActivityThread会根据接收到的来自AMS的指令,更新ActivityClientRecord的状态,并据此调用Activity的相应生命周期方法,如onCreateonStartonResumeonPauseonStoponDestroy

重要性

虽然ActivityClientRecord是一个内部类,不面向普通开发者,但了解它的存在和作用有助于深入理解Android的Activity管理机制。它是ActivityThread和AMS协同工作、高效管理Activity生命周期的关键数据结构,确保了Activity可以在正确的时间执行正确的操作,从而为用户提供流畅的应用体验。

Android Glide 三种池子

问题

1
2
3
4
5
6
7
Glide 中 LruPoolStrategy 是如何设计的

三个子类 SizeConfigStrategy AttributeStrategy SizeStrategy 的区别

从精细度管理的角度,对三种池子进行排序

如果图片都使用 `ARGB_8888` 应该使用哪个池子

LruPoolStrategy 接口设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 定义了在LRU(最近最少使用)缓存中管理可重用位图池的策略接口。
* 该接口的实现负责定义位图的存储、检索和根据缓存策略及位图的属性(如大小和配置)的逐出机制。
*/
internal interface LruPoolStrategy {

/**
* 将位图放入池中。实现应根据其大小、配置或其他属性决定如何将位图添加到池中。
*
* @param bitmap 要添加到池中的位图。
*/
fun put(bitmap: Bitmap)

/**
* 尝试从池中检索并返回与指定宽度、高度和配置匹配的位图。如果没有找到合适的位图,此方法返回null。
*
* @param width 请求的位图宽度。
* @param height 请求的位图高度。
* @param config 所需位图的Bitmap.Config配置。可以为null。
* @return 匹配请求属性的位图,如果没有合适的位图可用则返回null。
*/
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap?

/**
* 从池中移除并返回最近最少使用的位图。如果池为空,此方法返回null。
*
* @return 最近最少使用的位图,如果池为空则返回null。
*/
fun removeLast(): Bitmap?

/**
* 生成并返回指定位图属性的日志友好型字符串表示,如其大小和配置。
*
* @param bitmap 要为其生成日志字符串的位图。
* @return 位图属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(bitmap: Bitmap): String?

/**
* 根据指定的宽度、高度和配置生成并返回一个位图属性的日志友好型字符串表示。
*
* @param width 位图的宽度。
* @param height 位图的高度。
* @param config 位图的Bitmap.Config配置。可以为null。
* @return 指定属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String?

/**
* 返回指定位图的大小(以字节为单位)。实现应根据位图的尺寸和配置计算大小。
*
* @param bitmap 要计算大小的位图。
* @return 位图的字节大小。
*/
fun getSize(bitmap: Bitmap): Int
}

SizeConfigStrategy

SizeConfigStrategy 是一个实现了 LruPoolStrategy 接口的类,用于管理和重用 Bitmap 对象,其核心目标是通过控制 Bitmap 对象的尺寸和配置来优化内存使用。

这个策略通过将 Bitmap 对象分类存储在一个组织良好的结构中,并在需要时提供快速访问,以减少内存分配和回收的开销。下面是对这个类的关键部分的详细解析:

核心数据结构和方法

  • ARGB_8888_IN_CONFIGS

  • RGA_F16_IN_CONFIGS

  • RGB_565_IN_CONFIGS

  • ARGB_4444_IN_CONFIGS

  • ALPHA_8_IN_CONFIGS
    这些数组定义了在不同 Android 版本下,根据请求的 Bitmap.Config 所能接受的配置类型。例如,如果请求的是 ARGB_8888,那么可能接受的配置就包括了 ARGB_8888RGBA_F16(在支持的 Android 版本上)。

  • keyPool:
    KeyPool 实例,用于重用 Key 对象。每个 Key 对象代表一个 Bitmap 的尺寸和配置,这样可以减少内存分配。

  • groupedMap:
    GroupedLinkedMap<Key?, Bitmap> 实例,用于根据 Key 存储和检索 Bitmap 对象。这种结构支持快速查找、插入和删除操作。

  • sortedSizes:
    NavigableMap<Int, Int> 的映射,用于跟踪每种配置下不同大小的 Bitmap 数量。这对于找到最匹配的 Bitmap 尺寸非常有用。

方法

  • put(bitmap: Bitmap):
    Bitmap 添加到池中。这个方法计算 Bitmap 的字节大小,创建或获取一个对应的 Key,并更新 groupedMapsortedSizes

  • get(width: Int, height: Int, config: Bitmap.Config?):
    尝试根据提供的宽度、高度和配置从池中获取一个最匹配的 Bitmap。这涉及到查找一个尺寸合适、配置兼容的 Bitmap,如果找到,就对其进行重新配置并返回。

  • removeLast():
    移除并返回池中最后一个 Bitmap,这通常是最近最少使用的一个。这个方法还会更新 sortedSizes 以反映 变化。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):
    生成表示 Bitmap 尺寸和配置的字符串,用于日志记录和调试。

内部类

  • KeyPoolKey:
    这些类支持 Bitmap 尺寸和配置的高效存储和检索。KeyPool 用于管理 Key 对象的池,以减少创建新对象的需要。Key 对象表示一个 Bitmap 的尺寸和配置,用作 groupedMap 中的键。

整体设计思路

SizeConfigStrategy 的设计旨在通过细致管理 Bitmap 对象的存储和重用来优化内存使用。它通过精确匹配请求的 Bitmap 尺寸和配置,尽量减少创建新 Bitmap 对象的需要,从而降低了内存压力和提高了性能。这个策略特别适用于图片密集型的应用,比如图片浏览器或社交媒体应用,其中频繁地加载和显示图片。

AttributeStrategy

这段代码定义了一个名为 AttributeStrategy 的内部类,实现了 LruPoolStrategy 接口,用于管理位图(Bitmap)的缓存策略。这个策略通过位图的宽度、高度和配置(Bitmap.Config)来唯一标识和管理位图。下面是对这个类的主要组成部分和逻辑的解析:

类的主要组成部分

  1. KeyPool 类:一个用于管理 Key 对象池的内部类。它通过重写 create() 方法来创建新的 Key 对象,并提供了一个获取 Key 的方法,该方法接受位图的宽度、高度和配置作为参数,用于初始化 Key

  2. Key 类:一个内部类,实现了 Poolable 接口。每个 Key 对象包含位图的宽度、高度和配置属性。Key 类提供了 init 方法来设置这些属性,equalshashCode 方法被重写以确保 Key 对象可以根据其宽度、高度和配置被唯一地标识和比较。

  3. groupedMap:一个 GroupedLinkedMap 对象,用于根据 Key(位图的宽度、高度和配置)分组存储和管理位图。

类的主要方法

  • put(bitmap: Bitmap):将位图添加到缓存中。首先通过 keyPool 获取与位图尺寸和配置对应的 Key,然后将位图和 Key 添加到 groupedMap 中。

  • get(width: Int, height: Int, config: Bitmap.Config?):尝试获取一个符合指定尺寸和配置的位图。首先通过 keyPool 获取与指定尺寸和配置对应的 Key,然后从 groupedMap 中查找和返回相应的位图。

  • removeLast():移除并返回最近最少使用的位图。这是通过从 groupedMap 中移除最后一个位图来实现的。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):用于生成表示位图尺寸和配置的日志字符串。

  • getSize(bitmap: Bitmap):返回位图占用的字节大小。

特点和用途

AttributeStrategy 通过精确地考虑位图的尺寸和配置来管理位图缓存,使其能够更有效地利用内存并提高缓存的效率。通过使用对象池来管理 Key 对象,还可以减少内存分配和垃圾回收的压力。这种策略特别适用于需要存储和管理多种尺寸和配置位图的应用场景。

SizeStrategy

这段代码是一个用于管理位图(Bitmap)缓存策略的内部类 SizeStrategy,它实现了 LruPoolStrategy 接口。这个策略的核心是通过位图大小来管理和回收位图资源,以优化内存使用。下面是对这个类和它的主要组成部分的分析:

成员变量介绍

  • keyPool: 一个 KeyPool 对象,用于管理 Key 对象的池。每个 Key 对象都与一个特定大小的位图相关联。

  • groupedMap: 一个 GroupedLinkedMap<Key, Bitmap> 对象,用于根据 Key 分组存储 Bitmap 对象。这允许快速查找和回收特定大小的位图。

  • sortedSizes: 一个 NavigableMap<Int?, Int> 对象,存储每个大小的位图数量。这是一个 PrettyPrintTreeMap,可能是为了便于调试和打印。

    方法解析

  • put(bitmap: Bitmap): 将一个位图添加到缓存中。它计算位图的大小,获取或创建相应大小的 Key,将位图和 Key 添加到 groupedMap 中,并更新 sortedSizes 中对应大小的计数。

  • get(width: Int, height: Int, config: Bitmap.Config?): 尝试获取一个符合指定宽度、高度和配置的位图。它计算所需位图的大小,查找是否有足够大的可用位图,如果有,则从 groupedMap 中取出并返回该位图。

  • removeLast(): 移除并返回最近最少使用(LRU)的位图。这是通过从 groupedMap 中移除最后一个位图来实现的,并更新 sortedSizes 中的计数。

  • decrementBitmapOfSize(size: Int?): 减少特定大小的位图数量。如果该大小的位图只有一个,则从 sortedSizes 中移除该大小;否则,减少其计数。

    辅助类

  • KeyPool: 用于管理 Key 对象池的类。它重写了 create() 方法来生成新的 Key 对象,并提供了一个重载的 get(size: Int) 方法来获取或创建一个初始化了特定大小的 Key

  • Key: 实现了 Poolable 接口的类,表示与位图大小相关联的键。包含一个 size 属性和 init(size: Int) 方法来设置键的大小。重写了 equals()hashCode() 方法以支持正确的键比较和哈希操作。

常量和辅助方法

  • MAX_SIZE_MULTIPLE: 一个常量,定义了在查找时可以接受的最大位图大小倍数。
  • getBitmapString(bitmap: Bitmap)getBitmapString(size: Int): 辅助方法,用于生成表示位图大小的字符串。

这个类的设计目的是提高位图缓存的效率和灵活性,通过精细地管理不同大小的位图来优化内存使用。通过维护一个有序的大小映射和一个根据大小分组的位图映射,它可以快速地存取和回收位图资源。

SizeConfigStrategyAttributeStrategy、和SizeStrategy是Glide图像加载库用于位图缓存管理的三种不同策略,它们在位图的存储、查找和回收方式上各有特点。这些策略优化了内存使用,并改善了图像加载的性能。以下是它们的区别和各自适用的场景:

适用场景

SizeConfigStrategy

特点SizeConfigStrategy使用位图的大小(以字节为单位)和Bitmap.Config配置作为键来管理缓存。这种方法允许区分具有相同像素大小但不同像素配置的位图,如ARGB_8888RGB_565

适用场景:这种策略适用于需要根据位图的内存大小和配置精细管理缓存的应用。例如,如果应用中同时使用了不同配置的位图(以优化显示质量和内存使用),SizeConfigStrategy能有效地区分和管理这些位图。

AttributeStrategy

特点AttributeStrategy基于位图的宽度、高度和配置(Bitmap.Config)来识别和管理位图。这种方法提供了对缓存的精确控制,允许缓存系统区分尺寸相同但配置不同的位图。

适用场景:当应用需要在相同的尺寸下缓存不同配置的位图,且这些配置对位图的使用和性能有明显影响时,AttributeStrategy非常适用。它确保了即使是细微的配置差异也能被正确管理,适合对图像质量和性能有高要求的应用。

SizeStrategy

特点SizeStrategy仅基于位图占用的内存大小来管理缓存,不考虑位图的配置或尺寸。这种策略通过一种更简单的方式来回收和重用位图内存,忽略了位图的其他属性。

适用场景:对于那些不需要考虑位图配置差异,主要关注于减少内存占用和简化缓存管理的应用,SizeStrategy是一个理想的选择。它适合内存使用更为紧张,或者位图配置较为统一的场景。

总结

SizeConfigStrategyAttributeStrategy 提供了更细粒度的缓存管理,能够根据位图的具体特征(如配置和尺寸)进行优化,适合需要高度优化内存使用和图像质量的场景。

SizeStrategy 通过一个更简单的方法来管理缓存,适用于对缓存管理的要求相对简单,更关注于减少内存占用的应用。

选择哪种策略取决于应用的具体需求,包括对内存管理的敏感度、图像的多样性以及性能的要求。

三种池子的特点

SizeConfigStrategy

  • 内存大小:使用位图的内存大小作为缓存的关键因素之一。
  • 配置敏感:考虑了Bitmap.Config,区分了相同大小但配置不同的位图。
  • 精细管理:允许对缓存的位图进行更精细的管理,适用于内存和显示质量都很重要的场景。

AttributeStrategy

  • 尺寸配置:基于位图的宽度、高度和Bitmap.Config来管理位图。
  • 高度区分:能够精确区分尺寸相同但配置不同的位图。
  • 细节控制:提供对位图缓存的细节控制,适用于对图像显示细节有高要求的应用。

SizeStrategy

  • 简化内存:仅基于位图占用的内存大小来管理缓存,简化了缓存管理。
  • 统一处理:不区分位图的尺寸或配置,统一处理所有位图。
  • 内存优化:优先考虑内存使用效率,适用于内存敏感且配置统一的应用场景。

这些关键字概括了每种策略的核心特点和适用场景,有助于在实际开发中根据应用的需求选择最合适的位图缓存管理策略。

附件

SizeConfigStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class SizeConfigStrategy : LruPoolStrategy {
companion object {
private const val MAX_SIZE_MULTIPLE = 8
private val ARGB_8888_IN_CONFIGS: Array<Bitmap.Config?>

init {
var result = arrayOf(
Bitmap.Config.ARGB_8888, // The value returned by Bitmaps with the hidden Bitmap config.
null
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result = result.copyOf(result.size + 1)
result[result.size - 1] = Bitmap.Config.RGBA_F16
}
ARGB_8888_IN_CONFIGS = result
}

private val RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS
private val RGB_565_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.RGB_565)
private val ARGB_4444_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ARGB_4444)
private val ALPHA_8_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ALPHA_8)
fun getBitmapString(size: Int, config: Bitmap.Config?): String {
return "[$size]($config)"
}

private fun getInConfigs(requested: Bitmap.Config?): Array<Bitmap.Config?> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Bitmap.Config.RGBA_F16 == requested) { // NOPMD - Avoid short circuiting sdk checks.
return RGBA_F16_IN_CONFIGS
}
}
return when (requested) {
Bitmap.Config.ARGB_8888 -> ARGB_8888_IN_CONFIGS
Bitmap.Config.RGB_565 -> RGB_565_IN_CONFIGS
Bitmap.Config.ARGB_4444 -> ARGB_4444_IN_CONFIGS
Bitmap.Config.ALPHA_8 -> ALPHA_8_IN_CONFIGS
else -> arrayOf(requested)
}
}
}

private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
private val sortedSizes: MutableMap<Bitmap.Config?, NavigableMap<Int, Int>> = EnumMap(Bitmap.Config::class.java)
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size, bitmap.config]
groupedMap.put(key, bitmap)
val sizes = getSizesForConfig(bitmap.config)
val current = sizes[key!!.size]
sizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
val bestKey = findBestKey(size, config)
val result = groupedMap[bestKey]
if (result != null) {
decrementBitmapOfSize(bestKey!!.size, result)
result.reconfigure(width, height, config)
}
return result
}

private fun findBestKey(size: Int, config: Bitmap.Config?): Key? {
var result = keyPool[size, config]
for (possibleConfig in getInConfigs(config)) {
val sizesForPossibleConfig = getSizesForConfig(possibleConfig)
val possibleSize = sizesForPossibleConfig.ceilingKey(size)
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| if (possibleConfig == null) config != null else possibleConfig != config
) {
keyPool.offer(result)
result = keyPool[possibleSize, possibleConfig]
}
break
}
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize, removed)
}
return removed
}

private fun decrementBitmapOfSize(size: Int, removed: Bitmap) {
val config = removed.config
val sizes = getSizesForConfig(config)
val current = sizes[size]?: throw NullPointerException(
"Tried to decrement empty size"
+ ", size: "
+ size
+ ", removed: "
+ logBitmap(removed)
+ ", this: "
+ this
)
if (current == 1) {
sizes.remove(size)
} else {
sizes[size] = current - 1
}
}

private fun getSizesForConfig(config: Bitmap.Config?): NavigableMap<Int, Int> {
var sizes = sortedSizes[config]
if (sizes == null) {
sizes = TreeMap()
sortedSizes[config] = sizes
}
return sizes
}

override fun logBitmap(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size, bitmap.config)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
val sb = StringBuilder()
.append("SizeConfigStrategy{groupedMap=")
.append(groupedMap)
.append(", sortedSizes=(")
for ((key, value) in sortedSizes) {
sb.append(key).append('[').append(value).append("], ")
}
if (sortedSizes.isNotEmpty()) {
sb.replace(sb.length - 2, sb.length, "")
}
return sb.append(")}").toString()
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int, config: Bitmap.Config?): Key? {
val result = get()
result!!.init(size, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
private var config: Bitmap.Config? = null

@VisibleForTesting
constructor(pool: KeyPool, size: Int, config: Bitmap.Config?) : this(pool) {
init(size, config)
}

fun init(size: Int, config: Bitmap.Config?) {
this.size = size
this.config = config
}

override fun offer() {
pool.offer(this)
}

override fun toString(): String {
return getBitmapString(size, config)
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return if (size == other.size && config == null) other.config == null else config == other.config
}
return false
}

override fun hashCode(): Int {
var result = size
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}
}
}

AttributeStategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.max.hbbitmappool.pool.impl

import android.graphics.Bitmap
import androidx.annotation.VisibleForTesting
import com.max.hbbitmappool.utils.getBitmapByteSize

internal class AttributeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
override fun put(bitmap: Bitmap) {
val key = keyPool[bitmap.width, bitmap.height, bitmap.config]
groupedMap.put(key, bitmap)
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val key = keyPool[width, height, config]
return groupedMap[key]
}

override fun removeLast(): Bitmap? {
return groupedMap.removeLast()
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
return getBitmapString(width, height, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "AttributeStrategy:\n $groupedMap"
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Key? {
val result = get()
result?.init(width, height, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
private var width = 0
private var height = 0
private var config: Bitmap.Config? = null
fun init(width: Int, height: Int, config: Bitmap.Config?) {
this.width = width
this.height = height
this.config = config
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return width == other.width && height == other.height && config == other.config
}
return false
}

override fun hashCode(): Int {
var result = width
result = 31 * result + height
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}

override fun toString(): String {
return getBitmapString(width, height, config)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private fun getBitmapString(bitmap: Bitmap): String {
return getBitmapString(bitmap.width, bitmap.height, bitmap.config)
}

fun getBitmapString(width: Int, height: Int, config: Bitmap.Config?): String {
return "[" + width + "x" + height + "], " + config
}
}
}

SizeStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
internal class SizeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key, Bitmap>()
private val sortedSizes: NavigableMap<Int?, Int> = PrettyPrintTreeMap()
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size]
groupedMap.put(key, bitmap)
val current = sortedSizes[key.size]
sortedSizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
var key = keyPool[size]
val possibleSize = sortedSizes.ceilingKey(size)
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key)
key = keyPool[possibleSize]
}

val result = groupedMap[key]
if (result != null) {
result.reconfigure(width, height, config)
decrementBitmapOfSize(possibleSize)
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize)
}
return removed
}

private fun decrementBitmapOfSize(size: Int?) {
val current = sortedSizes[size]
if (current == 1) {
sortedSizes.remove(size)
} else {
sortedSizes[size] = current!! - 1
}
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "SizeStrategy:\n $groupedMap\n SortedSizes$sortedSizes"
}

// Non-final for mocking.
@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int): Key {
val result = super.get()!!
result.init(size)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
fun init(size: Int) {
this.size = size
}

override fun equals(o: Any?): Boolean {
if (o is Key) {
return size == o.size
}
return false
}

override fun hashCode(): Int {
return size
}

// PMD.AccessorMethodGeneration: https://github.com/pmd/pmd/issues/807
override fun toString(): String {
return getBitmapString(size)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private const val MAX_SIZE_MULTIPLE = 8
private fun getBitmapString(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size)
}

fun getBitmapString(size: Int): String {
return "[$size]"
}
}
}

三种池子精细化管理排序

  1. AttributeStrategy
  2. SizeConfigStrategy
  3. SizeStrategy

1. AttributeStrategy

  • 精细化等级:最高
  • 原因AttributeStrategy基于位图的宽度、高度和Bitmap.Config配置来管理位图,提供了最细致的控制。这允许它区分具有相同像素数量但不同尺寸或配置的位图,实现了对位图缓存的高度精细化管理。

2. SizeConfigStrategy

  • 精细化等级:中等
  • 原因SizeConfigStrategy结合了位图的内存大小和配置(如ARGB_8888RGB_565等)来管理位图。虽然它不如AttributeStrategy能够精确到位图的具体尺寸,但通过考虑配置信息,它在位图的管理上提供了比仅基于大小更精细的控制。

3. SizeStrategy

  • 精细化等级:最低
  • 原因SizeStrategy仅基于位图占用的内存大小来管理位图,完全忽略了位图的尺寸和配置信息。这种策略提供了最简单的管理方式,适合于那些内存使用效率是主要关注点、对位图的具体属性(如尺寸和配置)关注较少的场景。

总结来说,如果需要对缓存中的位图进行非常精细化的管理,优先选择AttributeStrategy
如果希望在精细化管理和简化逻辑之间取得平衡,SizeConfigStrategy是一个好的选择;
而如果主要关注简化缓存管理和优化内存使用,SizeStrategy将是最合适的策略。