ArrayDeque 的接口

正文

ArrayDeque 实现了 Deque 接口,该接口继承自 Queue 接口。下面是 Deque 接口中定义的一些主要方法:

  1. 添加元素操作:

    • addFirst(element: E):将元素添加到双端队列的开头。
    • addLast(element: E):将元素添加到双端队列的末尾。
    • offerFirst(element: E):将元素添加到双端队列的开头,并返回是否成功。
    • offerLast(element: E):将元素添加到双端队列的末尾,并返回是否成功。
  2. 获取元素操作:

    • getFirst(): E:获取双端队列的第一个元素,但不删除它。
    • getLast(): E:获取双端队列的最后一个元素,但不删除它。
    • peekFirst(): E:获取双端队列的第一个元素,如果队列为空则返回 null。
    • peekLast(): E:获取双端队列的最后一个元素,如果队列为空则返回 null。
  3. 移除元素操作:

    • removeFirst(): E:移除并返回双端队列的第一个元素。
    • removeLast(): E:移除并返回双端队列的最后一个元素。
    • pollFirst(): E:移除并返回双端队列的第一个元素,如果队列为空则返回 null。
    • pollLast(): E:移除并返回双端队列的最后一个元素,如果队列为空则返回 null。

此外,ArrayDeque 还实现了 Queue 接口中定义的方法,如 offer(element: E)remove(): Epoll(): E 等。

需要注意的是,ArrayDeque 是一个可变大小的数组双端队列,可以在队列的两端进行高效的插入和删除操作,同时也支持随机访问。

Android 面试题库:深度解析并发锁机制与应用实践

在 Android 高级开发面试中,并发锁是考察候选人底层功底、内存模型理解以及系统架构能力的核心考点。本文针对 Android 实际开发场景,系统性梳理了各种锁的原理与选型。


一、 Java 基础锁原语

1. synchronized (内置锁/监视器锁)

  • 特性:自动获取/释放、可重入、非公平。在现代 ART 虚拟机中经过了偏向锁、轻量级锁、重量级锁的膨胀优化。
  • 面试考点
    • 对象锁 vs 类锁:同步代码块与静态同步方法的区别。
    • 原理:每个对象关联的 monitor 对象(EntryList, WaitSet)。
  • 应用场景:本项目的 GLTextureView 中用于状态机的全局调度。

2. ReentrantLock (显示锁)

  • 特性:基于 AQS(AbstractQueuedSynchronizer)实现。支持公平/非公平选型、可中断、支持超时获取、多条件变量(Condition)。
  • 面试考点
    • synchronized 区别:灵活性(tryLock)、性能(高竞争下更稳定)、可扩展性。
  • 应用场景:复杂的数据结构操作,或需要响应中断的长时间等待场景。

二、 读写与性能优化锁

3. ReentrantReadWriteLock (读写锁)

  • 核心逻辑读读共享、读写互斥、写写互斥
  • 面试考点
    • 锁降级:写锁可以降级为读锁,但读锁不能升级为写锁。
    • 饥饿问题:大量读操作可能导致写操作长时间无法获取锁。
  • 应用场景:App 的内存缓存(LruCache)查询频率远高于更新频率的场景。

4. StampedLock (乐观读锁)

  • 特性:Java 8 引入。提供了一种乐观读模式,读操作不会阻塞写操作。
  • 面试考点
    • 对比 ReadWriteLock:通过版本戳(Stamp)验证数据有效性,在读多写极少的极端场景下性能近乎无锁。
  • 应用场景:坐标点数据计算、频繁读取的配置信息。

三、 原子操作与轻量级同步

5. CAS (Compare And Swap)

  • 原理:利用 CPU 的 cmpxchg 指令实现无锁原子更新。
  • 面试考点
    • ABA 问题:如何通过 AtomicStampedReference 解决。
    • 自旋开销:高竞争下 CPU 占用率过高。
  • 应用场景:计数器(AtomicInteger)、状态标记位。

6. volatile 关键字

  • 作用可见性、有序性(禁止指令重排)。不保证原子性。
  • 面试考点
    • DCL(双重检查锁定)单例模式:为什么必须加 volatile?(防止对象初始化未完成就被引用)。
  • 应用场景:本项目的 mHasContentToDraw 标志位同步。

四、 Android 场景下的特有应用

7. 渲染链路同步 (本项目核心)

  • 痛点:GL 线程与 UI 线程的生命周期不同步。
  • 方案:使用 synchronized 配合 wait/notifyAll 构建保护性暂停(Guarded Suspension)模型。
  • 面试回答:参考 GLTEXTUREVIEW_LOCKING.md 中的握手协议描述。

8. 锁的选型指南 (横向对比)

维度 synchronized ReentrantLock Atomic/CAS
灵活性 低(自动) 高(手动控制) 极高(无锁)
性能 中(低中竞争优) 高(高竞争稳定) 极高(极短操作)
功能 基础 丰富(Condition/公平性) 仅限原子变量
风险 高(易忘释放) 容易出现 ABA 问题

五、 资深面试官进阶追问

Q1:如何定位 App 线上发生的死锁?

  • :1. 使用 Thread.getAllStackTraces() 获取堆栈。2. 寻找 BLOCKED 状态的线程。3. 分析锁的循环等待链(A 等 B,B 等 A)。

Q2:为什么 ConcurrentLinkedQueue 不需要加锁?

  • :它采用了 Michael-Scott 非阻塞队列算法,底层完全基于 CAS 操作头尾指针,实现了极高的并发吞吐量。本项目在 HybridNormalRenderer 中使用它来处理弹幕队列,避免了每一帧渲染都要持锁的开销。

Q3:什么是偏向锁和锁消除?

  • :锁消除是 JIT 编译器的优化,若发现对象只会在单线程访问,则直接去掉 synchronized。偏向锁是假设锁一直由同一线程持有,减少 CAS 操作开销。

📌 总结

锁不是越多越好,而是越轻量越好。在 Android 开发中,首选无锁结构(Concurrent 容器),次选轻量级标记(volatile/Atomic),最后才考虑重权重的 Monitor 或显示锁。

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)则是通过直接访问大小来实现。

JVM 锁膨胀机制深度剖析:自动化路径与开发者影响力

本篇文档旨在回答一个核心问题:synchronized 的世界里,究竟是 JVM 说了算,还是程序员的写法能改变命运?


一、 锁升级的自动化流水线:JVM 的“自作主张”

锁的膨胀过程(无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁)确实是由 JVM 自动管理的。程序员无法通过代码直接下令“请使用轻量级锁”,但 JVM 会根据底层硬件状态 and 实时竞争情况进行“自适应”决策。

1. 膨胀的决策链路

  1. 偏向锁阶段:当一个线程进入 synchronized 时,JVM 检查对象头的 Mark Word。如果是空的,直接记录当前线程 ID。这是 JVM 对单线程场景的极致偏爱。
  2. 轻量级锁阶段:当第二个线程试图获取锁, JVM 发现 Mark Word 里不是它,于是通过 CAS(Compare And Swap) 尝试抢夺。如果抢到了,说明竞争不激烈,维持在轻量级锁。
  3. 自旋与适应性自旋:抢不到锁的线程不会立刻放弃,而是在门口“打转”(自旋)。JVM 会根据历史数据判断:如果这个锁以前很容易抢到,它就多自旋一会儿;反之则少自旋。
  4. 重量级锁阶段:如果自旋多次依然失败,或者竞争线程过多,JVM 认为“场面失控”,会调用操作系统的 mutex 指令,进入重量级锁,将线程挂起。

二、 程序员的写法:如何“间接”控制锁的命运?

虽然你不能直接指挥 JVM,但你的写法逻辑会直接决定 JVM 走哪条优化路径。

1. 锁的粒度(Granularity)

  • 做法synchronized(this) 锁定整个对象 vs synchronized(lockObj) 锁定特定变量。
  • 影响:如果你锁定整个大对象,多个无关业务的线程会产生“虚假竞争”,强行让 JVM 将原本可以维持在偏向锁/轻量级锁的状态升级为重量级锁。
  • 建议尽量减小同步块的范围。

2. 持锁时长(Hold Time)

  • 做法:在同步块里做 IO 操作或复杂计算。
  • 影响:子线程自旋是有时间限制的。如果你持锁时间太长,正在自旋的线程等不及了,就会触发 JVM 的“锁膨胀”机制,强行升级为重量级锁。
  • 建议同步块内只做最核心的、快速的状态变更。

3. 对象重用与逃逸分析

  • 做法:局部变量加锁 vs 成员变量加锁。
  • 影响:如果你对一个永远不会逃逸出当前方法的对象加锁(例如方法内的局部变量),JVM 的 JIT 编译器会通过逃逸分析触发 “锁消除” —— 即使你写了 synchronized,运行的时候根本没锁。
  • 建议利用局部变量的封闭性减少全局竞争。

三、 JVM 的“脾气”:你必须知道的潜规则

1. 偏向锁的延迟开启

JVM 启动时,偏向锁通常会有几秒钟的延迟(默认 4s)。

  • 为什么? 因为 JVM 启动初期会有大量线程竞争(如加载类),此时偏向锁反而会降低性能(撤销开销大)。
  • 写法影响:如果你在 App 刚启动时就做大量同步,它们可能直接从无锁跳到轻量级锁。

2. 锁降级(罕见现象)

虽然通常说锁“只升不降”,但在某些特定的 JVM 实现(如 HotSpot)中,在 STW(Stop The World) 进行全局垃圾回收时,可能会尝试将重量级锁降级。但这对程序员来说是不可控且不可感知的。


🎙 面试官进阶 Q&A

Q:程序员可以手动干预锁升级吗?

  • :不能直接干预。但可以通过参数调节(如 -XX:BiasedLockingStartupDelay=0)或者通过业务逻辑优化。例如,如果已知某个锁一定会有高并发竞争,我们可以提前让它“热身”,或者干脆改用 ReentrantLock 来获得更确定的控制力。

Q:为什么 synchronized 块比 synchronized 方法更好?

  • :核心在于减小临界区。锁块可以只包裹那几行真正需要同步的代码,从而缩短持锁时间,给 JVM 留出更多保持在“轻量级锁”状态的空间,避免膨胀到性能低下的重量级锁。

📌 总结

锁膨胀是 JVM 的“自动挡”,而程序员的逻辑是“油门和刹车”。 你写出的竞争频率、持锁时长、逃逸程度,就是 JVM 决定是否切换挡位的唯一依据。

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,它们提供了更高效和灵活的线程安全操作。

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 到底被谁持有了

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可以在正确的时间执行正确的操作,从而为用户提供流畅的应用体验。

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)