Fork me on GitHub

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

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

synchronized 关键字为什么会引入锁的竞争

正文

synchronized 关键字用于实现线程同步,它可以保证在同一时间只有一个线程能够执行被 synchronized 修饰的代码块或方法。当一个线程获取了对象的锁,其他线程需要等待该线程释放锁才能继续执行。

当多个线程同时竞争同一个锁时,就会发生锁的竞争。这种竞争会导致多个线程按顺序排队等待锁的释放,从而引入性能问题。具体来说,锁的竞争可能导致以下情况:

  1. 线程争用:多个线程同时竞争同一个锁,会导致线程进行上下文切换,从而增加了线程切换的开销。
  2. 阻塞等待:当一个线程获取了锁,其他线程需要等待,直到该线程释放锁。这会导致其他线程阻塞,降低了并发性能。
  3. 锁饥饿:如果某个线程频繁地获取锁,其他线程无法获得执行的机会,会导致其他线程长时间等待锁,造成锁饥饿的情况。

因此,当多个线程同时竞争同一个锁时,就会引入锁的竞争问题,进而影响程序的性能。在高并发场景中,合理设计并发控制机制,避免不必要的锁竞争,可以提高程序的并发性能。

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 Framework 专项 - Handler(一)

问题

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:一个线程有几个 Handler?

Q: 线程间的通信的原理是怎样的?

Q: Handler 内存泄漏的原因?为什么其他的内部类没有说过这个问题?

Q: 为何主线程可以 new Handler ?如果想要在子线程中 new Handler 要做些什么准备?

Q: 子线程中维护的 Looper,消息队列无消息的时候的处理方案是什么?有什么用?

Q: 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部时如何确保线程安全的?

Q: Looper 死循环为什么不会导致应用卡死

Q: 为什么主线程不需要自己创建和管理消息循环

Handler 在 Android 中的应用

1
Handler 是针对 Android 系统中与 UI 线程通信而专门设计的多线程通信机制

Retorfit,eventbus,rxjava,Looper

Handler 源代码分析

子线程 发送 MSG

​ android.os.Handler#sendMessage ->
​ android.os.Handler#sendMessageDelayed ->
​ android.os.Handler#sendMessageAtTime ->
​ android.os.Handler#enqueueMessage ->
​ android.os.MessageQueue#enqueueMessage ->
​ android.os.Looper#loop

android.os.Handler#sendMessage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
/**
* 在所有挂起的消息之后将消息推送到消息队列的末尾
* 当前时间之前。它将在 {@link #handleMessage} 中收到,
* 在附加到该处理程序的线程中。
*
* @return 如果消息成功放入则返回 true
* 消息队列。失败时返回 false,通常是因为
* 处理消息队列的 looper 正在退出。
*/
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
android.os.Handler#sendMessageDelayed
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
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
/**
* 将一条消息放入消息队列中,位于所有挂起的消息之后
* 之前(当前时间+delayMillis)。您将在以下时间收到它:
* {@link #handleMessage},在附加到该处理程序的线程中。
*
* @return 如果消息成功放入则返回 true
* 消息队列。失败时返回 false,通常是因为
* 处理消息队列的 looper 正在退出。请注意,一个
* true 的结果并不意味着该消息将被处理 -- 如果
* Looper 在消息发送之前退出
* 发生则消息将被丢弃。
*/
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
android.os.Handler#sendMessageAtTime
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
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
/**
* 将消息排队到消息队列中,在绝对时间(以毫秒为单位)<var>uptimeMillis</var>之后的所有挂起消息之后。
* <b>时间基准是 {@link android.os.SystemClock#uptimeMillis}。</b>
* 在深度睡眠期间花费的时间将会额外延迟执行。
* 您将在{@link #handleMessage}中接收它,该方法会在与此处理程序连接的线程中执行。
*
* @param uptimeMillis 消息应该传递的绝对时间,使用{@link android.os.SystemClock#uptimeMillis}作为时间基准。
*
* @return 如果消息成功放置到消息队列中,则返回 true 。如果失败,则返回 false ,通常是因为处理消息队列的消息循环正在退出。
* 请注意,返回true并不意味着消息将被处理 - 如果消息传递时间之前消息循环被退出,则消息将被丢弃。
*/
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
android.os.Handler#enqueueMessage
1
2
3
4
5
6
7
8
9
10
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

到这里,发送流程都没什么好说的

重点:android.os.Handler#handleMessage
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
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}

synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
msg.target 是什么

注意:这里 msg.target 就是 msg 持有的 handler 也是 为什么会有内存泄漏风险的原因。

分析代码:

Handler 内存泄漏原因

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler持有,这样最终就导致 Activity 泄露。

它的主要作用是将消息按照时间顺序插入到消息队列中,并在必要时唤醒队列以处理这些消息。下面逐步分析代码的功能和逻辑。

  1. 首先,代码对传入的消息 msg 进行一些验证。它确保消息有一个非空的目标(msg.target != null),否则抛出 IllegalArgumentException

  2. 然后,代码在同步块内执行以下操作:

    • 检查消息是否已经在使用中,如果是,则抛出 IllegalStateException。这可能是为了防止重复使用消息,确保每个消息只被处理一次。

    • 检查当前处理器是否正在退出(mQuitting 标志),如果是,就回收消息并返回 false,表示消息未被成功加入队列。

    • 将消息标记为正在使用,并设置消息的触发时间 msg.when 为传入的 when

    • 获取消息队列的头部消息 p

  3. 接下来,代码根据以下条件进行处理:

    • 如果消息队列为空,或者传入的触发时间 when 为 0,或者传入的 when 小于队列头部消息的触发时间 p.when,则将新消息插入到队列头部。如果队列当前被阻塞(mBlocked 标志),则设置需要唤醒队列(needWake = true)。

    • 否则,如果消息需要插入队列中间,则根据条件判断是否需要唤醒队列。具体判断条件是:队列被阻塞、队列头部消息的目标为 null,且传入的消息是异步消息。然后,代码在一个循环中遍历消息队列,找到合适的位置插入新消息。循环会一直迭代,直到找到合适的位置或者遍历完整个队列。

    • 在找到合适的位置后,代码将新消息 msg 插入到队列中。具体做法是,将 msg.next 设置为当前消息 p,然后将前一个消息 prev.next 设置为新消息 msg

  4. 最后,代码根据之前的标志 needWake 来决定是否唤醒队列。如果需要唤醒,则调用本地的 nativeWake 方法(可能是一个底层的本地方法)来唤醒消息队列。

  5. 整个同步块结束后,代码返回 true,表示消息已经成功加入队列。

这段代码的核心功能是在消息队列中插入消息并进行适当的排序,以确保消息按照触发时间顺序进行处理。同时,它还处理了一些异常情况,如消息已经在使用中或者处理器正在退出。唤醒队列的逻辑也在代码的最后部分进行处理。

让我们逐步分析 MSG 插入队列的位置:

第一种插入头部的情况:
1
2
3
4
5
6
7
8
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
}
  1. 如果消息队列为空,意味着当前没有任何消息在队列中等待处理。在这种情况下,不需要比较触发时间,直接将新消息插入到队列的头部。这会让新消息成为队列的第一个要处理的消息。
  2. 如果传入的触发时间 when 为 0,这可能表示该消息需要尽快处理,因此同样将它插入到队列的头部。
  3. 如果传入的触发时间 when 小于队列头部消息的触发时间 p.when,这意味着新消息应该在队列中位于当前头部消息之前,因此同样将它插入到队列的头部

综合上述情况,无论是队列为空,还是传入的 when 值为 0,或者传入的 when 值小于队列头部消息的触发时间,都会将新消息插入到队列的头部

第二种插入中间的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(p == null || when == 0 || when < p.when) {
...
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
  1. 首先,如果不满足前面提到的条件(消息队列为空,传入的触发时间为0,或传入的触发时间小于队列头部消息的触发时间),那么意味着要插入的新消息 msg 应该位于队列中间。
  2. 在这种情况下,代码会进入一个循环。这个循环的目的是遍历消息队列,以找到合适的位置将新消息 msg 插入队列中。
  3. 循环的条件是 for (;;),这将创建一个无限循环,直到内部的某个条件满足而跳出循环。
  4. 在每次循环迭代中,代码会做以下事情:
    • 将当前消息 p 的引用赋值给 prev,这样可以记录下前一个消息。
    • p 移动到下一个消息(p = p.next)。
  5. 然后,代码检查两个条件:
    • 如果当前消息 p 为空,意味着已经遍历了整个队列,或者队列只有一个消息且当前消息是最后一个消息。
    • 如果传入的触发时间 when 小于队列中当前消息 p 的触发时间 p.when
  6. 如果满足以上任一条件,循环会被中断,这表示找到了合适的位置将新消息 msg 插入到消息队列中。
  7. 在循环的每个迭代中,代码还会检查以下条件:
    • 如果需要唤醒队列(needWake = true),并且当前消息 p 是异步消息(p.isAsynchronous() 返回 true),则将 needWake 设置为 false。这个步骤可能是为了控制是否需要在队列中插入异步消息时唤醒队列。
  8. 一旦找到了合适的位置,代码会执行以下操作:
    • 将新消息 msgnext 指针指向当前消息 p,这相当于将新消息插入到当前消息 p 之前。
    • 将前一个消息 prevnext 指针指向新消息 msg,以确保队列中消息的连接关系正确。
  9. 循环结束后,新消息 msg 已经被插入到队列的合适位置,保持了消息队列的有序性。

总之,这段代码的目的是在消息队列中将新消息插入到适当的位置,以保持消息的时间顺序。在找到合适位置时,会根据一些条件来决定是否需要唤醒队列,这可能与队列的处理机制相关。

此外,如果当前消息队列被阻塞(mBlocked 为 true),则将标志 needWake 设置为 true。这是为了确保在需要唤醒队列以处理消息的情况下,能够在适当的时候执行唤醒操作。唤醒队列的操作可能涉及到一些底层机制,具体如何唤醒可能需要查看更多上下文代码。

总的来说,这段代码逻辑的目的是在特定条件下将新消息插入到消息队列的头部,并根据当前队列的阻塞状态决定是否需要唤醒队列以确保消息能够被及时处理。

主线程 取出 MSG

​ android.os.Looper#loop ->
​ android.os.MessageQueue#next ->
​ android.os.Handler#dispatchMessage ->
​ android.os.Handler#handleMessage

主线程中的 Loop

我们来看主线程的代码

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
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

// Install selective syscall interception
AndroidOs.install();

// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);

Environment.initForCurrentUser();

// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);

// Call per-process mainline module initialization.
initializeMainlineModules();

Process.setArgV0("<pre-initialized>");

Looper.prepareMainLooper();

// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

与 handler 相关的关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
...

Looper.prepareMainLooper();
...

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
···

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

准备 Looper

1
Looper.prepareMainLooper();

android.os.Looper#prepareMainLooper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. See also: {@link #prepare()}
*
* @deprecated The main looper for your application is created by the Android environment,
* so you should never need to call this function yourself.
*/
@Deprecated
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

android.os.Looper#prepare(boolean)

1
2
3
4
5
6
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

准备 Handler

1
2
3
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

android.app.ActivityThread#getHandler

1
2
3
4
@UnsupportedAppUsage
public Handler getHandler() {
return mH;
}

android.app.ActivityThread#mH

1
2
@UnsupportedAppUsage
final H mH = new H();

开启循环

1
Looper.loop();
重点:android.os.Looper#loop
#loop
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
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}

me.mInLoop = true;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);

me.mSlowDeliveryDetected = false;

for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}

这段代码是 Android 框架中 Looper 类的 loop() 方法的实现,负责在当前线程的消息循环中执行消息的分发和处理。

  1. 首先,通过 myLooper() 获取当前线程的 Looper 实例 me,如果没有 Looper 实例则抛出异常,表示未调用 Looper.prepare() 来准备 Looper。
  2. 检查 me.mInLoop,如果当前线程已经在消息循环中,则打印警告日志。
  3. me.mInLoop 标志设置为 true,表示当前线程正在消息循环中。
  4. 使用 Binder.clearCallingIdentity() 来清除当前线程的调用标识,然后再次调用它并将返回的标识 ident 保存下来。
  5. 获取一个可能的系统属性覆盖值 thresholdOverride,用于调整慢分发的阈值。
  6. me.mSlowDeliveryDetected 设置为 false,用于标记是否检测到慢投递。
  7. 进入一个无限循环,不断地执行消息分发和处理。
  8. 在循环中,调用 loopOnce(me, ident, thresholdOverride) 来执行一次消息分发。如果返回值为 false,表示没有更多的消息需要分发,退出循环。

总之,这段代码描述了 Android 中消息循环的核心逻辑。它会在一个无限循环中,不断地从消息队列中获取消息并执行消息分发和处理,直到没有更多的消息需要处理为止。在循环中,还会检查是否有慢分发阈值的系统属性覆盖,并根据需要清除调用标识。如果发现当前线程已经在消息循环中,则会打印警告信息。

#loopOnce
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
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (me.mSlowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
me.mSlowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
me.mSlowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();

return true;
}

这段代码是 Android 框架中 Looper 类的一个方法 loopOnce 的实现,主要负责处理消息队列中的消息分发过程。我会逐步解释代码的功能。

  1. 首先,通过 me.mQueue.next() 获取下一个消息,如果没有消息则表示消息队列正在退出,函数返回 false

  2. 如果获取到了消息,则会根据是否设置了日志打印,打印消息的分发信息。

  3. 获取当前的观察者对象和跟踪标签。

  4. 根据阈值覆盖值,更新慢分发和慢投递的阈值。

  5. 判断是否需要记录开始时间和结束时间,如果需要,则记录当前时间作为分发开始时间。

  6. 如果设置了跟踪标签且跟踪标签是启用的,则开始跟踪。

  7. 设置开始分发时间,并获取一个 token 以用于观察者的消息分发起始回调。

  8. 设置当前线程的工作源为消息的工作源 UID。

  9. 使用目标 Handler(Handler 是消息处理的目标)来分发消息。

  10. 如果设置了观察者,通知观察者消息已分发。

  11. 结束分发,记录结束时间。

  12. 如果发生异常,通知观察者分发过程中出现异常,并将异常抛出。

  13. 最终,无论是否发生异常,都会恢复线程的工作源,并根据跟踪标签是否启用,结束跟踪。

  14. 如果设置了慢投递日志,会根据条件判断是否记录慢投递日志。

  15. 如果设置了慢分发日志,会根据条件记录慢分发日志。

  16. 如果设置了日志打印,会打印消息分发完成信息。

  17. 最后,检查分发过程中线程标识是否被更改,如果发生改变,会打印警告日志。

  18. 回收消息对象并返回 true,表示成功分发消息。

总之,这段代码描述了 Android 中消息队列中消息的分发过程,涵盖了消息的跟踪、日志记录、观察者通知等多个方面,确保消息能够按照正确的顺序分发到目标处理程序。

重点:android.os.MessageQueue#next
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
@UnsupportedAppUsage
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

这段代码是一个 Android 消息循环(Message Loop)的一部分,它用于处理消息队列中的消息。消息循环是 Android 应用程序的核心部分,用于处理用户界面事件、线程间通信等。

以下是代码的主要功能和逻辑:

  1. 代码开头使用了一个注解 @UnsupportedAppUsage,这是 Android 的一个标记注解,用于标识不推荐使用的 API 或方法。

  2. next() 方法是一个消息循环的主要函数。它会循环从消息队列中获取消息,然后处理这些消息。

  3. 首先,代码会获取 mPtr,这是一个表示消息队列的指针。如果 ptr 为 0,表示消息队列已经被释放,函数会返回 null

  4. 接下来,进入一个无限循环 for (;;) {...},在循环内部做以下操作:

    • 如果 nextPollTimeoutMillis 不为 0,会刷新待处理的 Binder 命令(一种 Android 进程间通信机制)。

    • 调用 nativePollOnce(ptr, nextPollTimeoutMillis),它是一个本地方法,用于轮询获取下一个消息。

    • 进入同步块,开始处理消息队列中的消息。

    • 首先,获取当前时间 now,然后尝试获取下一个消息。如果消息的目标为 null,表示该消息是一个异步消息(可能由于障碍而被阻塞),会查找下一个异步消息。

    • 如果找到消息并且消息的触发时间在当前时间之后,会设置一个定时器来在消息准备好时唤醒循环。

    • 如果消息已经准备好(触发时间已到),会将消息从队列中移除,并返回该消息。

    • 如果没有找到消息,会将 nextPollTimeoutMillis 设为 -1,表示没有更多消息。

    • 如果 mQuitting 为 true,表示消息队列已经被停止,会调用 dispose() 释放资源,并返回 null

    • 如果是第一次空闲(没有消息待处理),会获取待运行的空闲处理器数量。

    • 如果没有空闲处理器需要运行,会将 mBlocked 设为 true,继续等待。

    • 如果有待运行的空闲处理器,会将它们放入 mPendingIdleHandlers 数组中。

    • 执行空闲处理器的 queueIdle() 方法,该方法用于处理空闲状态。

    • 如果 queueIdle() 返回 false,表示该空闲处理器不再需要运行,会将其从列表中移除。

    • 重置空闲处理器数量为 0,以防止重复运行。

    • 在处理完空闲处理器后,将 nextPollTimeoutMillis 设为 0,以便立即查找待处理的消息。

以上就是这段代码的主要逻辑。它负责不断地从消息队列中获取消息并处理,同时也处理了一些特殊情况,如消息队列已停止、空闲处理等。这种消息循环机制保证了 Android 应用程序的响应性和流畅性。

核心代码:

给定代码中最重要的部分是主循环,该循环处理消息并运行空闲处理程序,在Android消息循环系统中负责处理消息并及时有效地运行空闲处理程序。让我们逐步分解关键组成部分及其重要性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
for (;;) {
// ...
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// ...
if (msg != null) {
if (now < msg.when) {
// ...
} else {
// 收到一条消息。
mBlocked = false;
// 从队列中移除消息并返回。
// 这是消息处理的核心操作。
// 它允许根据其预定时间依次执行消息。
// 返回的“msg”对象包含用于处理的数据和指令。
msg.markInUse();
return msg;
}
} else {
// 没有更多消息。
nextPollTimeoutMillis = -1;
}

// 处理退出消息并处理空闲处理程序。
// ...

// 运行空闲处理程序。
// 该块遍历待处理的空闲处理程序并执行它们。
// 空闲处理程序用于在消息队列为空时执行任务。
for (int i = 0; i < pendingIdleHandlerCount; i++) {
// ...
}

// 重置空闲处理程序计数并检查是否有新消息。
// ...
}
}

关键部分及其重要性如下:

  1. nativePollOnce(ptr, nextPollTimeoutMillis);:这个本地方法用于轮询来自底层系统的传入消息和事件。它等待指定的超时时间以获取新的消息。
  2. 消息处理:
    • 循环检查是否有待处理的消息,并根据其预定时间(msg.when)对其进行处理。
    • 如果一条消息准备好执行,它会从队列中移除并返回(return msg;)。
    • 这是消息循环的核心,消息按照它们被发布的顺序逐个执行。
    • markInUse() 方法表示消息正在被处理。
  3. 退出消息和空闲处理程序:
    • 循环检查消息循环是否正在退出(mQuitting)并在需要时释放资源。
    • 还会处理空闲处理程序,这些程序在没有待处理消息时执行。
    • 空闲处理程序是在消息循环处于非活动状态时执行任务的函数。

总的来说,这段代码片段代表了Android消息循环的核心机制,它对于管理异步任务、UI交互和基于事件的编程在Android应用中至关重要。这个循环的正常运行对于维持响应迅速、交互式的用户体验至关重要。

android.os.Handler#dispatchMessage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

这段代码是一个 Android 消息分发方法,用于处理系统消息。在 Android 中,消息分发是消息循环的一部分,用于将消息传递给相应的处理方法或回调函数。

以下是代码的主要功能和逻辑:

  1. 方法名为 dispatchMessage,接受一个非空的 Message 对象作为参数,用于处理系统消息。

  2. 首先,代码检查 msg 中是否存在一个非空的回调函数 callback。如果存在,表示该消息具有一个回调函数,将调用 handleCallback(msg) 来处理该回调函数。

  3. 如果 msg 中没有回调函数,代码会进入下一步判断。

  4. 首先,检查是否存在一个非空的成员变量 mCallback,它是一个消息处理器的回调接口。如果存在 mCallback,代码会调用 handleMessage(msg) 来处理消息。如果 mCallback.handleMessage(msg) 返回 true,表示消息已经被处理,函数会直接返回,不会继续执行后续的 handleMessage(msg)

  5. 如果 mCallback.handleMessage(msg) 返回 false,表示 mCallback 没有处理该消息,代码会调用当前类的 handleMessage(msg) 方法来处理消息。

综合起来,这段代码实现了一个消息分发的逻辑,首先检查消息是否有回调函数,然后尝试使用回调函数处理消息。如果没有回调函数,它会检查是否存在一个全局的消息处理器 mCallback,尝试让它处理消息。如果都没有成功处理,最后会调用当前类的 handleMessage(msg) 方法来处理消息。

这种消息分发机制使得消息能够在不同的处理方法和回调函数之间进行传递和处理,提高了代码的灵活性和可扩展性。

android.os.Handler#handleMessage
1
2
3
4
5
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}

这段代码是一个方法声明,用于在子类中实现消息处理的逻辑。在 Android 中,消息处理是一种常见的模式,用于处理来自消息队列的不同类型的消息。

以下是代码的主要功能和逻辑:

  1. 方法名为 handleMessage,接受一个非空的 Message 对象作为参数,用于处理消息。

  2. 这是一个抽象方法,没有具体的实现,因此需要在子类中进行实现。

  3. 子类必须重写这个方法,并根据具体的业务逻辑来处理传递进来的消息。

  4. 通过在子类中实现不同的 handleMessage 方法,可以实现不同类型的消息处理,从而达到分离不同功能的目的。

在 Android 应用程序开发中,常常会使用这种消息处理机制来进行异步任务的处理、UI 更新、线程间通信等。子类可以根据不同的需求,实现不同的消息处理逻辑,使代码结构更加清晰和模块化。

使用 handler 通信机制由主线程向子线程发送消息

我们已经了解了主线程中会创建 Looper 和 handler,也明白了期运行逻辑
下面我们在子线程中创建这一套通信机制,用于主线程向子线程发送消息

创建一个 CustomLooperThread

手动创建子线程的 Looper 和关联的 Handler,以便在子线程中处理消息。

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
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

class CustomLooperThread extends Thread {
private Handler mHandler;
private Looper mLooper;

@Override
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();

mHandler = new Handler(mLooper) {
@Override
public void handleMessage(Message msg) {
// 在线程中处理消息
// ...
}
};

Looper.loop();
}

public void executeTask(Runnable task) {
if (mHandler != null) {
mHandler.post(task);
}
}

public Looper getLooper() {
return mLooper;
}

public void quit() {
if (mLooper != null) {
mLooper.quit();
}
}
}

方式二: 使用 HandlerThread 使用 HandlerThread 类封装了子线程的 Looper 和关联的 Handler 创建过程。

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
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;

public class CustomLooperThread extends HandlerThread {
private Handler mHandler;

public CustomLooperThread(String name) {
super(name);
}

@Override
protected void onLooperPrepared() {
mHandler = new Handler(getLooper()) {
@Override
public void handleMessage(Message msg) {
// 在子线程中处理消息
// ...
}
};
}

public void sendMessageToThread(int what) {
if (mHandler != null) {
Message message = mHandler.obtainMessage(what);
mHandler.sendMessage(message);
}
}
}
创建一个 MyThreadManager
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
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public class MyThreadManager {
private MyThread mThread;
private Handler mHandler;

public MyThreadManager() {
// 创建线程并启动
mThread = new MyThread();
mThread.start();

// 在线程的消息循环中创建 Handler
mHandler = new Handler(mThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 在线程中处理消息
// ...
}
};
}

// 发送消息到线程中
public void sendMessage(int what) {
if (mHandler != null) {
Message message = mHandler.obtainMessage(what);
mHandler.sendMessage(message);
}
}

// 执行任务在线程中
public void executeTaskInThread(Runnable task) {
if (mThread != null) {
mThread.executeTask(task);
}
}

// 关闭线程和消息循环
public void closeThread() {
if (mThread != null) {
mThread.quit();
mThread = null;
}
}
}
引出问题
方式一,自定义 LooperHandler 创建的 CustomLooperThread 中的 getLooper 有没有问题?
1
mThread.start();

是在子线程中执行的

1
2
3
4
5
6
7
8
// 在线程的消息循环中创建 Handler
mHandler = new Handler(mThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 在线程中处理消息
// ...
}
};thread.start()是在子线程中

是在主线程执行的
也就是说在 mThread.getLooper() 时,有可能 mThread.start() 还没执行完
所以这里有多线程并发问题

我们看一下 HandlerThread 中是怎么解决的

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
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}

boolean wasInterrupted = false;

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}

/*
* We may need to restore the thread's interrupted flag, because it may
* have been cleared above since we eat InterruptedExceptions
*/
if (wasInterrupted) {
Thread.currentThread().interrupt();
}


Thread 中的 Handler 和 MyThreadManager 中 Handler 有什么不同
  1. MyThread 类中的 HandlerMyThread 类中的 Handler 用于处理子线程 MyThread 中的消息。它负责监听并处理从 MyThread 类的消息循环中传递过来的消息。这个 Handler 是在 MyThread 类的 run() 方法中创建并与 Looper 关联,以便在 MyThread 线程中处理消息。
  2. MyThreadManager 类中的 HandlerMyThreadManager 类中的 Handler 用于将消息发送到 MyThread 线程。它负责将来自主线程或其他地方的消息发送给 MyThread 线程的消息循环。这个 Handler 负责与 MyThread 线程进行通信,从而实现了在主线程或其他线程中触发 MyThread 线程执行特定任务。

这里我们提到了消息循环,我们下一章说 MessageQueue

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)

Android Glide 三种池子

问题

1
2
3
4
5
6
7
Glide 中 LruPoolStrategy 是如何设计的

三个子类 SizeConfigStrategy AttributeStrategy SizeStrategy 的区别

从精细度管理的角度,对三种池子进行排序

如果图片都使用 `ARGB_8888` 应该使用哪个池子

LruPoolStrategy 接口设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 定义了在LRU(最近最少使用)缓存中管理可重用位图池的策略接口。
* 该接口的实现负责定义位图的存储、检索和根据缓存策略及位图的属性(如大小和配置)的逐出机制。
*/
internal interface LruPoolStrategy {

/**
* 将位图放入池中。实现应根据其大小、配置或其他属性决定如何将位图添加到池中。
*
* @param bitmap 要添加到池中的位图。
*/
fun put(bitmap: Bitmap)

/**
* 尝试从池中检索并返回与指定宽度、高度和配置匹配的位图。如果没有找到合适的位图,此方法返回null。
*
* @param width 请求的位图宽度。
* @param height 请求的位图高度。
* @param config 所需位图的Bitmap.Config配置。可以为null。
* @return 匹配请求属性的位图,如果没有合适的位图可用则返回null。
*/
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap?

/**
* 从池中移除并返回最近最少使用的位图。如果池为空,此方法返回null。
*
* @return 最近最少使用的位图,如果池为空则返回null。
*/
fun removeLast(): Bitmap?

/**
* 生成并返回指定位图属性的日志友好型字符串表示,如其大小和配置。
*
* @param bitmap 要为其生成日志字符串的位图。
* @return 位图属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(bitmap: Bitmap): String?

/**
* 根据指定的宽度、高度和配置生成并返回一个位图属性的日志友好型字符串表示。
*
* @param width 位图的宽度。
* @param height 位图的高度。
* @param config 位图的Bitmap.Config配置。可以为null。
* @return 指定属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String?

/**
* 返回指定位图的大小(以字节为单位)。实现应根据位图的尺寸和配置计算大小。
*
* @param bitmap 要计算大小的位图。
* @return 位图的字节大小。
*/
fun getSize(bitmap: Bitmap): Int
}

SizeConfigStrategy

SizeConfigStrategy 是一个实现了 LruPoolStrategy 接口的类,用于管理和重用 Bitmap 对象,其核心目标是通过控制 Bitmap 对象的尺寸和配置来优化内存使用。

这个策略通过将 Bitmap 对象分类存储在一个组织良好的结构中,并在需要时提供快速访问,以减少内存分配和回收的开销。下面是对这个类的关键部分的详细解析:

核心数据结构和方法

  • ARGB_8888_IN_CONFIGS

  • RGA_F16_IN_CONFIGS

  • RGB_565_IN_CONFIGS

  • ARGB_4444_IN_CONFIGS

  • ALPHA_8_IN_CONFIGS
    这些数组定义了在不同 Android 版本下,根据请求的 Bitmap.Config 所能接受的配置类型。例如,如果请求的是 ARGB_8888,那么可能接受的配置就包括了 ARGB_8888RGBA_F16(在支持的 Android 版本上)。

  • keyPool:
    KeyPool 实例,用于重用 Key 对象。每个 Key 对象代表一个 Bitmap 的尺寸和配置,这样可以减少内存分配。

  • groupedMap:
    GroupedLinkedMap<Key?, Bitmap> 实例,用于根据 Key 存储和检索 Bitmap 对象。这种结构支持快速查找、插入和删除操作。

  • sortedSizes:
    NavigableMap<Int, Int> 的映射,用于跟踪每种配置下不同大小的 Bitmap 数量。这对于找到最匹配的 Bitmap 尺寸非常有用。

方法

  • put(bitmap: Bitmap):
    Bitmap 添加到池中。这个方法计算 Bitmap 的字节大小,创建或获取一个对应的 Key,并更新 groupedMapsortedSizes

  • get(width: Int, height: Int, config: Bitmap.Config?):
    尝试根据提供的宽度、高度和配置从池中获取一个最匹配的 Bitmap。这涉及到查找一个尺寸合适、配置兼容的 Bitmap,如果找到,就对其进行重新配置并返回。

  • removeLast():
    移除并返回池中最后一个 Bitmap,这通常是最近最少使用的一个。这个方法还会更新 sortedSizes 以反映 变化。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):
    生成表示 Bitmap 尺寸和配置的字符串,用于日志记录和调试。

内部类

  • KeyPoolKey:
    这些类支持 Bitmap 尺寸和配置的高效存储和检索。KeyPool 用于管理 Key 对象的池,以减少创建新对象的需要。Key 对象表示一个 Bitmap 的尺寸和配置,用作 groupedMap 中的键。

整体设计思路

SizeConfigStrategy 的设计旨在通过细致管理 Bitmap 对象的存储和重用来优化内存使用。它通过精确匹配请求的 Bitmap 尺寸和配置,尽量减少创建新 Bitmap 对象的需要,从而降低了内存压力和提高了性能。这个策略特别适用于图片密集型的应用,比如图片浏览器或社交媒体应用,其中频繁地加载和显示图片。

AttributeStrategy

这段代码定义了一个名为 AttributeStrategy 的内部类,实现了 LruPoolStrategy 接口,用于管理位图(Bitmap)的缓存策略。这个策略通过位图的宽度、高度和配置(Bitmap.Config)来唯一标识和管理位图。下面是对这个类的主要组成部分和逻辑的解析:

类的主要组成部分

  1. KeyPool 类:一个用于管理 Key 对象池的内部类。它通过重写 create() 方法来创建新的 Key 对象,并提供了一个获取 Key 的方法,该方法接受位图的宽度、高度和配置作为参数,用于初始化 Key

  2. Key 类:一个内部类,实现了 Poolable 接口。每个 Key 对象包含位图的宽度、高度和配置属性。Key 类提供了 init 方法来设置这些属性,equalshashCode 方法被重写以确保 Key 对象可以根据其宽度、高度和配置被唯一地标识和比较。

  3. groupedMap:一个 GroupedLinkedMap 对象,用于根据 Key(位图的宽度、高度和配置)分组存储和管理位图。

类的主要方法

  • put(bitmap: Bitmap):将位图添加到缓存中。首先通过 keyPool 获取与位图尺寸和配置对应的 Key,然后将位图和 Key 添加到 groupedMap 中。

  • get(width: Int, height: Int, config: Bitmap.Config?):尝试获取一个符合指定尺寸和配置的位图。首先通过 keyPool 获取与指定尺寸和配置对应的 Key,然后从 groupedMap 中查找和返回相应的位图。

  • removeLast():移除并返回最近最少使用的位图。这是通过从 groupedMap 中移除最后一个位图来实现的。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):用于生成表示位图尺寸和配置的日志字符串。

  • getSize(bitmap: Bitmap):返回位图占用的字节大小。

特点和用途

AttributeStrategy 通过精确地考虑位图的尺寸和配置来管理位图缓存,使其能够更有效地利用内存并提高缓存的效率。通过使用对象池来管理 Key 对象,还可以减少内存分配和垃圾回收的压力。这种策略特别适用于需要存储和管理多种尺寸和配置位图的应用场景。

SizeStrategy

这段代码是一个用于管理位图(Bitmap)缓存策略的内部类 SizeStrategy,它实现了 LruPoolStrategy 接口。这个策略的核心是通过位图大小来管理和回收位图资源,以优化内存使用。下面是对这个类和它的主要组成部分的分析:

成员变量介绍

  • keyPool: 一个 KeyPool 对象,用于管理 Key 对象的池。每个 Key 对象都与一个特定大小的位图相关联。

  • groupedMap: 一个 GroupedLinkedMap<Key, Bitmap> 对象,用于根据 Key 分组存储 Bitmap 对象。这允许快速查找和回收特定大小的位图。

  • sortedSizes: 一个 NavigableMap<Int?, Int> 对象,存储每个大小的位图数量。这是一个 PrettyPrintTreeMap,可能是为了便于调试和打印。

    方法解析

  • put(bitmap: Bitmap): 将一个位图添加到缓存中。它计算位图的大小,获取或创建相应大小的 Key,将位图和 Key 添加到 groupedMap 中,并更新 sortedSizes 中对应大小的计数。

  • get(width: Int, height: Int, config: Bitmap.Config?): 尝试获取一个符合指定宽度、高度和配置的位图。它计算所需位图的大小,查找是否有足够大的可用位图,如果有,则从 groupedMap 中取出并返回该位图。

  • removeLast(): 移除并返回最近最少使用(LRU)的位图。这是通过从 groupedMap 中移除最后一个位图来实现的,并更新 sortedSizes 中的计数。

  • decrementBitmapOfSize(size: Int?): 减少特定大小的位图数量。如果该大小的位图只有一个,则从 sortedSizes 中移除该大小;否则,减少其计数。

    辅助类

  • KeyPool: 用于管理 Key 对象池的类。它重写了 create() 方法来生成新的 Key 对象,并提供了一个重载的 get(size: Int) 方法来获取或创建一个初始化了特定大小的 Key

  • Key: 实现了 Poolable 接口的类,表示与位图大小相关联的键。包含一个 size 属性和 init(size: Int) 方法来设置键的大小。重写了 equals()hashCode() 方法以支持正确的键比较和哈希操作。

常量和辅助方法

  • MAX_SIZE_MULTIPLE: 一个常量,定义了在查找时可以接受的最大位图大小倍数。
  • getBitmapString(bitmap: Bitmap)getBitmapString(size: Int): 辅助方法,用于生成表示位图大小的字符串。

这个类的设计目的是提高位图缓存的效率和灵活性,通过精细地管理不同大小的位图来优化内存使用。通过维护一个有序的大小映射和一个根据大小分组的位图映射,它可以快速地存取和回收位图资源。

SizeConfigStrategyAttributeStrategy、和SizeStrategy是Glide图像加载库用于位图缓存管理的三种不同策略,它们在位图的存储、查找和回收方式上各有特点。这些策略优化了内存使用,并改善了图像加载的性能。以下是它们的区别和各自适用的场景:

适用场景

SizeConfigStrategy

特点SizeConfigStrategy使用位图的大小(以字节为单位)和Bitmap.Config配置作为键来管理缓存。这种方法允许区分具有相同像素大小但不同像素配置的位图,如ARGB_8888RGB_565

适用场景:这种策略适用于需要根据位图的内存大小和配置精细管理缓存的应用。例如,如果应用中同时使用了不同配置的位图(以优化显示质量和内存使用),SizeConfigStrategy能有效地区分和管理这些位图。

AttributeStrategy

特点AttributeStrategy基于位图的宽度、高度和配置(Bitmap.Config)来识别和管理位图。这种方法提供了对缓存的精确控制,允许缓存系统区分尺寸相同但配置不同的位图。

适用场景:当应用需要在相同的尺寸下缓存不同配置的位图,且这些配置对位图的使用和性能有明显影响时,AttributeStrategy非常适用。它确保了即使是细微的配置差异也能被正确管理,适合对图像质量和性能有高要求的应用。

SizeStrategy

特点SizeStrategy仅基于位图占用的内存大小来管理缓存,不考虑位图的配置或尺寸。这种策略通过一种更简单的方式来回收和重用位图内存,忽略了位图的其他属性。

适用场景:对于那些不需要考虑位图配置差异,主要关注于减少内存占用和简化缓存管理的应用,SizeStrategy是一个理想的选择。它适合内存使用更为紧张,或者位图配置较为统一的场景。

总结

SizeConfigStrategyAttributeStrategy 提供了更细粒度的缓存管理,能够根据位图的具体特征(如配置和尺寸)进行优化,适合需要高度优化内存使用和图像质量的场景。

SizeStrategy 通过一个更简单的方法来管理缓存,适用于对缓存管理的要求相对简单,更关注于减少内存占用的应用。

选择哪种策略取决于应用的具体需求,包括对内存管理的敏感度、图像的多样性以及性能的要求。

三种池子的特点

SizeConfigStrategy

  • 内存大小:使用位图的内存大小作为缓存的关键因素之一。
  • 配置敏感:考虑了Bitmap.Config,区分了相同大小但配置不同的位图。
  • 精细管理:允许对缓存的位图进行更精细的管理,适用于内存和显示质量都很重要的场景。

AttributeStrategy

  • 尺寸配置:基于位图的宽度、高度和Bitmap.Config来管理位图。
  • 高度区分:能够精确区分尺寸相同但配置不同的位图。
  • 细节控制:提供对位图缓存的细节控制,适用于对图像显示细节有高要求的应用。

SizeStrategy

  • 简化内存:仅基于位图占用的内存大小来管理缓存,简化了缓存管理。
  • 统一处理:不区分位图的尺寸或配置,统一处理所有位图。
  • 内存优化:优先考虑内存使用效率,适用于内存敏感且配置统一的应用场景。

这些关键字概括了每种策略的核心特点和适用场景,有助于在实际开发中根据应用的需求选择最合适的位图缓存管理策略。

附件

SizeConfigStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class SizeConfigStrategy : LruPoolStrategy {
companion object {
private const val MAX_SIZE_MULTIPLE = 8
private val ARGB_8888_IN_CONFIGS: Array<Bitmap.Config?>

init {
var result = arrayOf(
Bitmap.Config.ARGB_8888, // The value returned by Bitmaps with the hidden Bitmap config.
null
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result = result.copyOf(result.size + 1)
result[result.size - 1] = Bitmap.Config.RGBA_F16
}
ARGB_8888_IN_CONFIGS = result
}

private val RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS
private val RGB_565_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.RGB_565)
private val ARGB_4444_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ARGB_4444)
private val ALPHA_8_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ALPHA_8)
fun getBitmapString(size: Int, config: Bitmap.Config?): String {
return "[$size]($config)"
}

private fun getInConfigs(requested: Bitmap.Config?): Array<Bitmap.Config?> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Bitmap.Config.RGBA_F16 == requested) { // NOPMD - Avoid short circuiting sdk checks.
return RGBA_F16_IN_CONFIGS
}
}
return when (requested) {
Bitmap.Config.ARGB_8888 -> ARGB_8888_IN_CONFIGS
Bitmap.Config.RGB_565 -> RGB_565_IN_CONFIGS
Bitmap.Config.ARGB_4444 -> ARGB_4444_IN_CONFIGS
Bitmap.Config.ALPHA_8 -> ALPHA_8_IN_CONFIGS
else -> arrayOf(requested)
}
}
}

private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
private val sortedSizes: MutableMap<Bitmap.Config?, NavigableMap<Int, Int>> = EnumMap(Bitmap.Config::class.java)
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size, bitmap.config]
groupedMap.put(key, bitmap)
val sizes = getSizesForConfig(bitmap.config)
val current = sizes[key!!.size]
sizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
val bestKey = findBestKey(size, config)
val result = groupedMap[bestKey]
if (result != null) {
decrementBitmapOfSize(bestKey!!.size, result)
result.reconfigure(width, height, config)
}
return result
}

private fun findBestKey(size: Int, config: Bitmap.Config?): Key? {
var result = keyPool[size, config]
for (possibleConfig in getInConfigs(config)) {
val sizesForPossibleConfig = getSizesForConfig(possibleConfig)
val possibleSize = sizesForPossibleConfig.ceilingKey(size)
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| if (possibleConfig == null) config != null else possibleConfig != config
) {
keyPool.offer(result)
result = keyPool[possibleSize, possibleConfig]
}
break
}
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize, removed)
}
return removed
}

private fun decrementBitmapOfSize(size: Int, removed: Bitmap) {
val config = removed.config
val sizes = getSizesForConfig(config)
val current = sizes[size]?: throw NullPointerException(
"Tried to decrement empty size"
+ ", size: "
+ size
+ ", removed: "
+ logBitmap(removed)
+ ", this: "
+ this
)
if (current == 1) {
sizes.remove(size)
} else {
sizes[size] = current - 1
}
}

private fun getSizesForConfig(config: Bitmap.Config?): NavigableMap<Int, Int> {
var sizes = sortedSizes[config]
if (sizes == null) {
sizes = TreeMap()
sortedSizes[config] = sizes
}
return sizes
}

override fun logBitmap(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size, bitmap.config)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
val sb = StringBuilder()
.append("SizeConfigStrategy{groupedMap=")
.append(groupedMap)
.append(", sortedSizes=(")
for ((key, value) in sortedSizes) {
sb.append(key).append('[').append(value).append("], ")
}
if (sortedSizes.isNotEmpty()) {
sb.replace(sb.length - 2, sb.length, "")
}
return sb.append(")}").toString()
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int, config: Bitmap.Config?): Key? {
val result = get()
result!!.init(size, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
private var config: Bitmap.Config? = null

@VisibleForTesting
constructor(pool: KeyPool, size: Int, config: Bitmap.Config?) : this(pool) {
init(size, config)
}

fun init(size: Int, config: Bitmap.Config?) {
this.size = size
this.config = config
}

override fun offer() {
pool.offer(this)
}

override fun toString(): String {
return getBitmapString(size, config)
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return if (size == other.size && config == null) other.config == null else config == other.config
}
return false
}

override fun hashCode(): Int {
var result = size
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}
}
}

AttributeStategy

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

import android.graphics.Bitmap
import androidx.annotation.VisibleForTesting
import com.max.hbbitmappool.utils.getBitmapByteSize

internal class AttributeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
override fun put(bitmap: Bitmap) {
val key = keyPool[bitmap.width, bitmap.height, bitmap.config]
groupedMap.put(key, bitmap)
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val key = keyPool[width, height, config]
return groupedMap[key]
}

override fun removeLast(): Bitmap? {
return groupedMap.removeLast()
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
return getBitmapString(width, height, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "AttributeStrategy:\n $groupedMap"
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Key? {
val result = get()
result?.init(width, height, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
private var width = 0
private var height = 0
private var config: Bitmap.Config? = null
fun init(width: Int, height: Int, config: Bitmap.Config?) {
this.width = width
this.height = height
this.config = config
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return width == other.width && height == other.height && config == other.config
}
return false
}

override fun hashCode(): Int {
var result = width
result = 31 * result + height
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}

override fun toString(): String {
return getBitmapString(width, height, config)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private fun getBitmapString(bitmap: Bitmap): String {
return getBitmapString(bitmap.width, bitmap.height, bitmap.config)
}

fun getBitmapString(width: Int, height: Int, config: Bitmap.Config?): String {
return "[" + width + "x" + height + "], " + config
}
}
}

SizeStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
internal class SizeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key, Bitmap>()
private val sortedSizes: NavigableMap<Int?, Int> = PrettyPrintTreeMap()
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size]
groupedMap.put(key, bitmap)
val current = sortedSizes[key.size]
sortedSizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
var key = keyPool[size]
val possibleSize = sortedSizes.ceilingKey(size)
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key)
key = keyPool[possibleSize]
}

val result = groupedMap[key]
if (result != null) {
result.reconfigure(width, height, config)
decrementBitmapOfSize(possibleSize)
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize)
}
return removed
}

private fun decrementBitmapOfSize(size: Int?) {
val current = sortedSizes[size]
if (current == 1) {
sortedSizes.remove(size)
} else {
sortedSizes[size] = current!! - 1
}
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "SizeStrategy:\n $groupedMap\n SortedSizes$sortedSizes"
}

// Non-final for mocking.
@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int): Key {
val result = super.get()!!
result.init(size)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
fun init(size: Int) {
this.size = size
}

override fun equals(o: Any?): Boolean {
if (o is Key) {
return size == o.size
}
return false
}

override fun hashCode(): Int {
return size
}

// PMD.AccessorMethodGeneration: https://github.com/pmd/pmd/issues/807
override fun toString(): String {
return getBitmapString(size)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private const val MAX_SIZE_MULTIPLE = 8
private fun getBitmapString(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size)
}

fun getBitmapString(size: Int): String {
return "[$size]"
}
}
}

三种池子精细化管理排序

  1. AttributeStrategy
  2. SizeConfigStrategy
  3. SizeStrategy

1. AttributeStrategy

  • 精细化等级:最高
  • 原因AttributeStrategy基于位图的宽度、高度和Bitmap.Config配置来管理位图,提供了最细致的控制。这允许它区分具有相同像素数量但不同尺寸或配置的位图,实现了对位图缓存的高度精细化管理。

2. SizeConfigStrategy

  • 精细化等级:中等
  • 原因SizeConfigStrategy结合了位图的内存大小和配置(如ARGB_8888RGB_565等)来管理位图。虽然它不如AttributeStrategy能够精确到位图的具体尺寸,但通过考虑配置信息,它在位图的管理上提供了比仅基于大小更精细的控制。

3. SizeStrategy

  • 精细化等级:最低
  • 原因SizeStrategy仅基于位图占用的内存大小来管理位图,完全忽略了位图的尺寸和配置信息。这种策略提供了最简单的管理方式,适合于那些内存使用效率是主要关注点、对位图的具体属性(如尺寸和配置)关注较少的场景。

总结来说,如果需要对缓存中的位图进行非常精细化的管理,优先选择AttributeStrategy
如果希望在精细化管理和简化逻辑之间取得平衡,SizeConfigStrategy是一个好的选择;
而如果主要关注简化缓存管理和优化内存使用,SizeStrategy将是最合适的策略。