ANR案例
ANR案例
ANR 案例
android.os.MessageQueue.nativePollOnce
一周内 top 几的 ANR:
其中 nativePollOnce
的 ANR 排名第一:
SP apply ANR
- SharedPreferences ANR 总结
- 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待
- SharedPreferences ANR问题分析 & Android8.0的优化
主线程加载大文件会 ANR
Sp 主线程 getXX 方法会 ANR
Sp 主线程调用 commit 方法 ANR
SharedPreferences apply
案例场景复现:连送礼物过程中频繁更新免费礼物剩余个数、钻石余额,均存储于 SharedPreference,每个 put 操作并没有合并,都是一次完整的文件 IO,即使从 commit 改成 apply,也只是把写文件操作放到队列 QueuedWork 里,在 ActivityThread 的 handlePauseActivity、handleStopActivity 等方法中会等待该队列执行完毕,导致连送大量礼物过程中切 App 触发 ANR(优化为连送中更新内存,结束才更新文件)
难点:ANR 报错都是在系统路径。可以看到调用了 QueueWork.java
SP ANR 解决
memory Sp
用于解决 sp 在主线程写,activity/service start/stop 生命周期回调时需要等待,导致的 ANR 问题
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
/**
* https://gist.github.com/tprochazka/d91d89ec54bd6c3c1cb46f62faf3c12c
*
* ANR free implementation of SharedPreferences.
*
* Fast fix for ANR caused by writing all non written changes on main thread during activity/service start/stop.
*
* Disadvantage of current implementation:
* - OnSharedPreferenceChangeListener is called after all changes are written to disk.
* - If somebody will call edit() apply() several times after each other it will also several times write whole prefs file.
*
* Usage:
*
* Override this method in your Application class.
*
* public SharedPreferences getSharedPreferences(String name, int mode) {
* return NoMainThreadWriteSharedPreferences.getInstance(super.getSharedPreferences(name, mode), name);
* }
*
* You need to override also parent activity, because if somebody will use activity context instead
* of the application one, he will get a different implementation, you can do something like
*
* public SharedPreferences getSharedPreferences(String name, int mode) {
* return getApplicationContext().getSharedPreferences(name, mode);
* }
*
* @author Tomáš Procházka (prochazka)
*/
@RequiresApi(11)
class NoMainThreadWriteSharedPreferences private constructor(
private val sysPrefs: SharedPreferences,
val name: String
) :
SharedPreferences {
private val preferencesCache: MutableMap<String, Any?> = HashMap()
companion object {
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
private val INSTANCES: MutableMap<String, NoMainThreadWriteSharedPreferences> = HashMap()
@JvmStatic
fun getInstance(sharedPreferences: SharedPreferences, name: String): SharedPreferences {
return INSTANCES.getOrPut(
name,
{ NoMainThreadWriteSharedPreferences(sharedPreferences, name) })
}
/**
* Remove all instances for testing purpose.
*/
@VisibleForTesting
@JvmStatic
fun reset() {
INSTANCES.clear()
}
}
init {
/**
* I will think about it if there is no synchronization issue. But generally, I think that it will bring no difference. Because system shared preference itself loading whole properties file to memory anyway. So preferencesCache.putAll(sysPrefs.all) is just an in-memory operation that will be much faster than loading and parsing files from the storage.
*/
preferencesCache.putAll(sysPrefs.all)
}
override fun contains(key: String?) = preferencesCache[key] != null
override fun getAll() = HashMap(preferencesCache)
override fun getBoolean(key: String, defValue: Boolean): Boolean {
return preferencesCache[key] as Boolean? ?: defValue
}
override fun getInt(key: String, defValue: Int): Int {
return preferencesCache[key] as Int? ?: defValue
}
override fun getLong(key: String, defValue: Long): Long {
return preferencesCache[key] as Long? ?: defValue
}
override fun getFloat(key: String, defValue: Float): Float {
return preferencesCache[key] as Float? ?: defValue
}
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
@Suppress("UNCHECKED_CAST")
return preferencesCache[key] as MutableSet<String>? ?: defValues
}
override fun getString(key: String, defValue: String?): String? {
return preferencesCache[key] as String? ?: defValue
}
override fun edit(): SharedPreferences.Editor {
return Editor(sysPrefs.edit())
}
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
sysPrefs.registerOnSharedPreferenceChangeListener(listener)
}
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
sysPrefs.unregisterOnSharedPreferenceChangeListener(listener)
}
inner class Editor(private val sysEdit: SharedPreferences.Editor) : SharedPreferences.Editor {
private val modifiedData: MutableMap<String, Any?> = HashMap()
private var keysToRemove: MutableSet<String> = HashSet()
private var clear = false
override fun commit(): Boolean {
submit()
return true
}
override fun apply() {
submit()
}
private fun submit() {
synchronized(preferencesCache) {
storeMemCache()
queuePersistentStore()
}
}
private fun storeMemCache() {
if (clear) {
preferencesCache.clear()
clear = false
} else {
preferencesCache.keys.removeAll(keysToRemove)
}
keysToRemove.clear()
preferencesCache.putAll(modifiedData)
modifiedData.clear()
}
private fun queuePersistentStore() {
try {
executor.submit {
sysEdit.commit()
}
} catch (ex: Exception) {
Log.e(
"NoMainThreadWritePrefs",
"NoMainThreadWriteSharedPreferences.queuePersistentStore(), submit failed for $name"
)
}
}
override fun remove(key: String): SharedPreferences.Editor {
keysToRemove.add(key)
modifiedData.remove(key)
sysEdit.remove(key)
return this
}
override fun clear(): SharedPreferences.Editor {
clear = true
sysEdit.clear()
return this
}
override fun putLong(key: String, value: Long): SharedPreferences.Editor {
modifiedData[key] = value
sysEdit.putLong(key, value)
return this
}
override fun putInt(key: String, value: Int): SharedPreferences.Editor {
modifiedData[key] = value
sysEdit.putInt(key, value)
return this
}
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
modifiedData[key] = value
sysEdit.putBoolean(key, value)
return this
}
override fun putStringSet(
key: String,
values: MutableSet<String>?
): SharedPreferences.Editor {
modifiedData[key] = values
sysEdit.putStringSet(key, values)
return this
}
override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
modifiedData[key] = value
sysEdit.putFloat(key, value)
return this
}
override fun putString(key: String, value: String?): SharedPreferences.Editor {
modifiedData[key] = value
sysEdit.putString(key, value)
return this
}
}
}
在 Application 和 BaseActivity 中重写 getSharedPreferences
1
2
3
override fun getSharedPreferences(name: String?, mode: Int): SharedPreferences {
return NoMainThreadWriteSharedPreferences.getInstance(super.getSharedPreferences(name, mode), name!!)
}
反射将 QueueWork
清空
MMKV 替换 SP
binder 调用 ANR
涉及到了 binder 通信的。
- 获取进程名,参考:[[多进程#获取进程名]],减少 ANR 触发几率
- 判断 App 进程在前台
- 启动阶段广播
- 启动阶段 跨进程的 API
本文由作者按照 CC BY 4.0 进行授权