0%

人际交往的基本技巧

1. 如果想采蜂蜜,就不要捅蜂窝

动物对那些它们认为是正确的行为的学习会十分迅速,而且记得更加牢固。这远比通过惩罚它们的错误行为来教育它们更为迅速有效。

人们厌恶批评就如同渴望认可一样。

批评带来的怨恨会打击员工的积极性,影响你和家人、朋友的感情。同时,你所批评的境况仍得不到改善。

己所不欲,勿施于人。

2. 每个人都渴望得到赞美

所有和我们共事的人都是普普通通的人,他们都渴望得到肯定。每个灵魂都渴望获得赞美。

普天之下,只有一个方法可以让别人做事,那就是让别人想做这件事。

每个人对赞美的渴望,正如同对美食的渴望一样强烈。

我们日常最容易忽略的一种美德就是赞赏别人。

真心地给予别人肯定,不要吝啬你的赞美。人们会为你的肯定和赞美而欢呼雀跃,并且会用心铭记你的话语,直到永远。

3. 激起别人急切的欲望

如果成功有什么秘诀的话,那就是要先去了解对方的需求,从他们的角度来思考问题。首先激起对方急切的欲望。能做到这点的人,将掌握世界;不能做到这点的人,将四处碰壁。

唯一能影响别人的方法就是跟他们谈论那些他们想要的东西,并让他们知道怎么样才能得到它。

让别人喜欢你的六种方法

1. 用热情和真诚的态度对待他人

一个人可以通过真诚地关心他人来赢得关注、时间,甚至是合作。

如果你希望别人喜欢你,如果你希望赢得真正的友情,如果你希望在帮助别人的同时帮助自己,请牢记这条原则:真诚地关心别人。

2. 微笑的价值

喜欢微笑的人,在管理、教育和推销方面都比较成功,并且教育出来的孩子也更加开朗、乐观。

笑容比起皱眉更能传情达意,鼓励比惩罚更有效。

一个人脸上的神情,要远远比她身上所穿的衣服重要得多。

微笑会让人觉得你非常友善,会让你感受到一种温暖,它表示“我喜欢你,你令我非常开心,很高兴能见到你”。

微笑是你传播善意的信使,你的笑容可以照亮每一个看到它的人。对于那些看惯了皱眉、愁容满面,或是转过脸避开别人目光的人,你的笑容就如同阳光驱散雾霾般照进他们的心里。

3. 记住他人的姓名

姓名对于一个人来说是极为特殊、极为重要的。名字把人跟人区别开,使得一个人成为世上独一无二的个体。记得一个人的姓名,在人际交往上同样重要。

记住一个人的姓名,并轻易地叫出来,对这个人来说就是一种巧妙而有效的恭维。

4. 成为一名出色的听众

千万别忘记,那个与你交谈的人,他对于自己的需求和问题,比起对你以及你的问题的感兴趣程度要超过百倍。

认真地倾听别人谈话,是我们能给对方的一种最大的恭维。很少有人能够拒绝那种带有恭维性的认真聆听。

如果你想要成为一位出色的谈论家,一个善于倾听的人,想要别人对你感兴趣,那么你首先需要对他人感兴趣,提出一些别人愿意作答的问题,鼓励对方多谈论自己,以及他们的成就。

5. 让别人觉得自己重要

生活中,存在着一个举足轻重的原则,如果我们遵从这条原则,就永远也不会遇到麻烦;要是不遵从它,就会陷入无尽的麻烦。这个原则就是:让别人感受到他们自己的重要性。

人类本性中最深层的渴望,就是试图让自己变得重要。

主要让别人觉得自己重要,那么很多人的命运就很有可能因此而改变。

几乎每个你遇到的人都会认为他在某些地方比你强,这是一条永恒不变的真理。赢得这种人心的办法就是,以一种微妙的方式让他们意识到了你了解他们的重要性,并真诚地认同他们。

三人行,必有我师焉。要会向别人学习。

走出孤独忧虑的人生

1. 让自己忙起来

让自己不停地忙着。忧虑的人一定要让自己沉浸在工作中,否则只有在绝望中挣扎。

2. 不要为小事烦恼

不要让自己因为一些应该抛弃和忘记的小事而忧虑。要记住:生命如此短暂,不要再为小事而烦恼。

3. 不要自寻烦恼

要使我们能够停止忧虑,就可以根据事情发生的概率来评估我们的忧虑究竟值不值,这样,我想我们应该可以减少99%的忧虑。

让我们看看以前的记录,让我们根据概率问问自己,现在担心会发生的事情,到底真正发生的有多少?

4. 适应不可避免的事实

不要为月亮哭泣,也不要因事而后悔。

顺应时势,就是你在踏上人生旅途时最重要的一件事。

很显然,环境本身并不能使我们快乐或不快乐,只有我们对周围环境的反应才决定了我们的感受。

在必要的时候,我们都应该经受得住灾难和悲剧,甚至要战胜它们。也许我们会认为自己办不到,但事实上,我们内在的力量却强大得惊人,只要我们愿意利用,他就能帮助我们克服一切困难。

当我们不再反抗那些不可避免的事实之后,我们就可以节省精力,创造更丰富的生活。

5. 不要做无用功

在避免忧虑方面,没有比“船到桥头自然直”和“不要为打翻的牛奶哭泣”更基本,也更有用的话。

让过去的错误产生价值的唯一方法,就是平静地分析过去的错误,并从中吸取教训,然后再忘记错误。

不要试着去锯木屑。

不要为工作和金钱而烦恼

1. 做自己喜欢的工作

一定要记住,在做你生命中最重要而且影响最深远的决定之前,请务必多花点时间了解真实的真相。如果不这么做,那么你的决定将可能让你痛苦不已。

如果人们从事他们自己无限热爱的工作,他们都可以获得成功。

避免选择那些早就很激烈并且拥挤的职业和行业;避免选择只有10%的生存机会的行业。

克服“你只适合一项职业”的错误认知。每个人都可以在多项职业上取得成功。

2. 处理好金钱引发的烦恼

人们的大多数烦恼都和金钱有关。

使大多数人烦恼的并不是他们没有足够的金钱,而是他们不知道如何支配手中已有的金钱。

结构化并发

让我们先将思路转为日常业务开发中,比如在某某业务中,可能存在好几个需要同时处理的逻辑,比如同时请求两个网络接口,同时操作两个子任务等。我们暂且称上述学术化概念为 多个并发操作。

而每个并发操作其实都是在处理一个单独的任务,这个任务中,可能还存在子任务 ; 同样对于这个子任务来说,它又是其父任务的子单元。每个任务都有自己的生命周期,子任务的生命周期会继承父任务的生命周期,比如如果父任务关闭,子任务也会被取消。而如果满足这样特性,我们就称其就是 结构化并发。

异常传递流程

默认情况下,任意一个协程发生异常时都会影响到整个协程树,而异常的传递通常是双向的,也即协程会向子协程与父协程共同传递。

整体流程如下:

  1. 先 cancel 子协程
  2. 取消自己
  3. 将异常传递给父协程
  4. (重复上述过程,直到根协程关闭)

异常传递形式

  • launch : 层层向上传递,在launch外部try-catch无效

  • async : 根协程启动或加SupervisorJob启动,会优先当前暴露,可在await()捕获;否则会将异常传递给父协程,导致异常没有在调用处暴露。

异常处理

是否是根协程启动

async + 是否是根协程启动

  • async 时内部也是新的作用域,如果 async 对应的是根协程,那么我们可以在 await() 时直接捕获异常
  • async 时如果不是根协程启动,则会将异常传递给父协程,导致异常没有在调用处暴露。可以在async时声明SupervisorJob,此时即可在 await() 时捕获异常

launch + 是否是根或父协程自带异常处理器

  • 在launch外部try-catch无用
  • launch自带的异常处理器不会处理当前协程发生的异常

SupervisorJob

根协程自带的SupervisorJob,子协程会继承

非根协程的父协程声明的SupervisorJob,子协程不会继承: 子协程在 launch 时会创建新的协程作用域,其会使用默认新的 Job 替代父协程传递 SupervisorJob ,

所以导致我们传递的 SupervisorJob 被覆盖。所以如果我们想让子协程不影响父协程或者其他子协程,此时就必须再显示添加 SupervisorJob。

场景实践

严格意义上来说,所有异常都可以用 tryCatch 去处理

严格意义上来说,所有异常都可以用 tryCatch 去处理,只要我们的处理位置得当。但这并不是所有方式的最优解,特别是如果你想更优雅的处理异常时,此时就可以考虑 CoroutineExceptionHandler 。

什么时候该用 SupervisorJob ,什么时候该用 Job?

引用官方的一句话就是:想要避免取消操作在异常发生时被传播,记得使用 SupervisorJob ;反之则使用 Job。

SupervisorJob + tryCatch

我们有两个接口 A,B 需要同时请求,当接口A异常时,需要不影响B接口的正常展示,当接口B异常时,此时界面展示异常信息

SupervisorJob + CoroutineExceptionHandler

如果你有一个顶级协程,并且需要自动捕获所有的异常,则此时可以选用上述方式

参考

普通callback实现

与协程异步代码同步写法对比

callback实现代码

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
//CallbackImpl.kt‭​​‌‌‎‭‬​
abstract class CallbackImpl(val callBack: CallBack<Any>): CallBack<Any> {‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
override fun onResult(result: Any?) {‭​​‌‌‎‭‬​
val cb = callBack‭​​‌‌‎‭‬​
while (true) {‭​​‌‌‎‭‬​
val outcome = invoke(result)‭​​‌‌‎‭‬​
if (outcome === CALLBACK_FLAG) {‭​​‌‌‎‭‬​
return‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
cb.onResult(outcome)‭​​‌‌‎‭‬​
return‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
protected abstract fun invoke(result: Any?): Any?‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
val CALLBACK_FLAG = CoroutineSingletons.CALLBACK‭​​‌‌‎‭‬​
enum class CoroutineSingletons { CALLBACK }‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
interface CallBack<T> {‭​​‌‌‎‭‬​
fun onResult(result: T?)‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
object UserInfoHelper {‭​​‌‌‎‭‬​
fun launch() {‭​​‌‌‎‭‬​
Log.e("MainActivity", "launch run")‭​​‌‌‎‭‬​
requestUserCallback(object : CallBack<Any> {‭​​‌‌‎‭‬​
override fun onResult(result: Any?) {‭​​‌‌‎‭‬​
Log.e("MainActivity", "launch result: $result")‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
})‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
val handler = Handler(Looper.getMainLooper())‭​​‌‌‎‭‬​
private fun delay(timeMillis: Long, callBack: CallBack<Any>): Any {‭​​‌‌‎‭‬​
handler.postDelayed({‭​​‌‌‎‭‬​
callBack.onResult(Unit)‭​​‌‌‎‭‬​
}, timeMillis)‭​​‌‌‎‭‬​
return CALLBACK_FLAG‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
//正常情况下应该保持到协程的context中,这样写为了简单处理‭​​‌‌‎‭‬​
private var userCallback:UserCallback? = null‭​​‌‌‎‭‬​
abstract class UserCallback(callBack: CallBack<Any>) : CallbackImpl(callBack) {‭​​‌‌‎‭‬​
var result: Any? = null‭​​‌‌‎‭‬​
var flag: Int = 0‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
fun requestUserCallback(callBack: CallBack<Any>): Any {‭​​‌‌‎‭‬​
val cb = if (userCallback == null) {‭​​‌‌‎‭‬​
val c = object : UserCallback(callBack) {‭​​‌‌‎‭‬​
override fun invoke(result: Any?): Any? {‭​​‌‌‎‭‬​
this.result = result‭​​‌‌‎‭‬​
++flag‭​​‌‌‎‭‬​
return requestUserCallback(this)‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
userCallback = c‭​​‌‌‎‭‬​
c‭​​‌‌‎‭‬​
}else {‭​​‌‌‎‭‬​
userCallback!!‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
var returnResult = cb.result‭​​‌‌‎‭‬​
when (cb.flag) {‭​​‌‌‎‭‬​
0 -> {‭​​‌‌‎‭‬​
if (requestFriendListCallback(cb) == CALLBACK_FLAG) {‭​​‌‌‎‭‬​
return CALLBACK_FLAG‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
1 -> {‭​​‌‌‎‭‬​
returnResult = cb.result‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
return "user: $returnResult"‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
//正常情况下应该保持到协程的context中,这样写为了简单处理‭​​‌‌‎‭‬​
var requestFriendListCallback: RequestFriendListCallback? = null‭​​‌‌‎‭‬​
abstract class RequestFriendListCallback(callBack: CallBack<Any>) : CallbackImpl(callBack) {‭​​‌‌‎‭‬​
var result: Any? = null‭​​‌‌‎‭‬​
var flag: Int = 0‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
fun requestFriendListCallback(callBack: CallBack<Any>): Any {‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
val cb = if ( requestFriendListCallback == null) {‭​​‌‌‎‭‬​
val c = object : RequestFriendListCallback(callBack) {‭​​‌‌‎‭‬​
override fun invoke(result: Any?): Any? {‭​​‌‌‎‭‬​
this.result = result‭​​‌‌‎‭‬​
++flag‭​​‌‌‎‭‬​
return requestFriendListCallback(this)‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
requestFriendListCallback = c‭​​‌‌‎‭‬​
c‭​​‌‌‎‭‬​
} else {‭​​‌‌‎‭‬​
requestFriendListCallback!!‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
when (cb.flag) {‭​​‌‌‎‭‬​
0 -> {‭​​‌‌‎‭‬​
if (delay(3000, cb) == CALLBACK_FLAG) {‭​​‌‌‎‭‬​
return CALLBACK_FLAG‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
1 -> {‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
 ‭​​‌‌‎‭‬​
return "friendList"‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​
}‭​​‌‌‎‭‬​

协程反编译字节码

1
2


1. suspendCoroutine作用及用法

suspendCoroutine 是 Kotlin 中用于挂起协程并与外部世界进行交互的函数之一。它的主要作用是允许你在协程中将异步非挂起的操作转化为挂起操作,从而可以方便地与协程上下文集成。具体来说,suspendCoroutine 的主要作用包括以下几点:

  1. 将回调式的异步操作转化为挂起操作:当你需要在协程中执行某些异步操作,例如调用回调函数,处理回调式 API,或等待某个事件发生时,suspendCoroutine 允许你将这些异步操作封装为挂起操作,使得你可以像调用普通的挂起函数一样调用它们,从而简化了异步编程的代码。
  2. 挂起当前协程:suspendCoroutine 在执行时会挂起当前协程,这意味着协程的执行会暂停,直到回调操作完成。这有助于防止阻塞线程,并允许其他协程在执行。
  3. 传递回调函数:suspendCoroutine 接受一个 Lambda 表达式,该 Lambda 表达式需要传递一个函数参数,通常是一个回调函数,该回调函数将在异步操作完成时被调用。在 Lambda 表达式内部,你可以使用 resume 来恢复协程的执行并传递结果,或者使用 resumeWithException 来传递异常,以便协程可以处理结果或错误。

suspendCoroutine案例

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
suspend fun main() {  
withContext(Dispatchers.IO) {
val result = async { getResult() }.await()
println("result=$result")
}
}

suspend fun getResult() = suspendCoroutine<Boolean> {
val callback = object : Callback {
override fun onSuccess() {
it.resume(true)
}

override fun onFail() {
it.resumeWithException(Exception("fail"))
}

override fun onCancel() {
it.resume(false)
}
}
handleCallback(callback)
}

fun handleCallback(callback: Callback) {
callback.onFail()
}

interface Callback {
fun onSuccess()
fun onFail()
fun onCancel()
}

suspendCancellableCoroutine作用和用法

suspendCancellableCoroutine 是 Kotlin 中的一个函数,它允许你在一个协程中创建一个可取消的挂起操作,通常用于实现自定义的挂起函数或处理异步操作。基本功能和suspendCoroutine一样,只是多了一个支持监听外部协程取消和取消内部协程执行的能力。

suspendCancellableCoroutine案例

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
// 自定义的可取消挂起函数  
suspend fun customCancelableOperation(): String {
return suspendCancellableCoroutine { continuation ->
val job = GlobalScope.launch {
try {
// 模拟异步操作
delay(1000)
// 如果操作成功,恢复挂起操作并传递结果
continuation.resume("Operation completed successfully")
} catch (e: CancellationException) {
// 如果操作被取消,处理取消操作
continuation.resumeWithException(e)
} catch (e: Exception) {
// 如果操作失败,传递异常
continuation.resumeWithException(e)
}
}

// 当外部协程取消时,取消内部协程
continuation.invokeOnCancellation {
job.cancel()
}
}
}

fun main() = runBlocking {
val job = launch {
val result = customCancelableOperation()
println(result)
}

delay(500) // 等待一段时间
job.cancel() // 取消外部协程
job.join()
}

处理resumeWithException的通用方案

除了通过try catch的方式来捕获异常,也可以在launch协程的context中添加exceptionHandler来统一处理异常。 例如:

1
2
3
4
5
6
7
8
9
10
11
fun main() = runBlocking {  
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught exception: $exception")
}

val job = GlobalScope.launch(exceptionHandler) {
println(data)
}

job.join()
}

其他

在日常开发中,在协程上下文将异步非挂起的操作转化为挂起操作时遇到过多次回调完成的情况,导致一个continuation被多次resume,从而出现异常java.lang.IllegalStateException: Already resumed...

这时,可以通过使用suspendCancellableCoroutine并通过判断其状态决定是否需要真正resume来避免重复多次resume。

1
2
3
4
5
6
7
8
9
fun <T> Continuation<T>.safeResume(value: T) {  
if (this is CancellableContinuation) {
if (isActive) {
resume(value)
} else {
CapaLog.d("Continuation", "continuation is not active")
}
} else throw Exception("Must use suspendCancellableCoroutine instead of suspendCoroutine")
}

本文记录自定义ViewGroup实践中提炼出来的经验。

ViewGroup是啥?

ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity),当然还有margin等;于是乎,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。

2、View的职责是啥?
View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。

3、ViewGroup和LayoutParams之间的关系?
大家可以回忆一下,当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在RelativeLayout中的childView有layout_centerInParent属性,却没有layout_gravity,layout_weight,这是为什么呢?这是因为每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和gravity的身影。

2、View的3种测量模式
上面提到了ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:

EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;

AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;

UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。

注:上面的每一行都有一个一般,意思上述不是绝对的,对于childView的mode的设置还会和ViewGroup的测量mode有一定的关系;当然了,这是第一篇自定义ViewGroup,而且绝大部分情况都是上面的规则,所以为了通俗易懂,暂不深入讨论其他内容。

3、从API角度进行浅析
上面叙述了ViewGroup和View的职责,下面从API角度进行浅析。

View的根据ViewGroup传人的测量值和模式,对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。

ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。

摘自《认知觉醒:开启自我改变的原动力》荐读笔记

读完《认知觉醒》2年,来交一份答卷。2021年9月9日,我在微信读书遇到《认知觉醒》,和大部分人一样,翻开书就被吸引了。
那时的我,32岁,大厂工作,但不太顺利。公司内斗,行业衰退,应该算是北上广的“中年危机”吧。站在那个时间点上,我很迷茫,很焦虑,好像被溺到深水里,拼命挣扎,渴望能探出水面,但又十分徒劳。
在人生的某些时刻,我曾有过同样的感受,比如大学毕业找不到工作,从小城市来北京后遭遇失业,只是前两次危机的顺利解决,让我很快松懈下来,并没有思考问题背后的本质。
以至于30岁的这次危机再次如期而至。当时读书很少,对人生、工作、生活的理解都很浅薄,所以也不知道应该怎么解决,只能闷着头走路,探索。
恰好遇到这本书,周岭老师在书中提到,「早读冥写跑」人生五件套,是每个人最基础的成长之道。
这给我一些启发,既然没什么方向,索性就从人生五件套开始改变自己吧,没想到竟然一路坚持下来,还慢慢养成了7个好习惯,形成了我自己的人生七件套。
所以,站在两年的这个节点,我想分享一下,读完这本书后自己的改变。
一来是对本书的反馈,我认为这是一种很好的习惯,书籍也好,文章也罢,绝大部分作者都渴望收到读者的回音。说到底,作者和读者是一种互相“驯化”的关系,如果没有回馈,这种关系就不会牢固,总会觉得缺点什么。
二来,是最近进入了新的平台期,感觉没什么动力。所以,想做个总结,看看这一路坚持而积累的成果,希望能激励自己,再出发。当然,如果你能看到这篇书评,并有幸也能从中获得一些力量,那这些文字就又多了一层意义。
我的人生七件套,「早读冥写跑复记」。
早:
这两年,我在坚持早起,每天早起1.5小时,累计早起516天,粗略估算一下,这让我多出了750+小时。我每天早起后会读书,几乎大部分时候都会进入心流状态,每次还没读过瘾,1个小时就过去了。如果按10个小时读完一本书的话,我大概利用早起的时间读了75本书。
开始早起前,我每天会睡到8点多,晚上熬夜,所以每次起床都很挣扎,起床后为了避免迟到,又急匆匆的挤地铁上班,一天下来迷迷糊糊,没有精气神。开始早起后,睡的也早了,我可以从容的起床、洗漱、吃早饭,还有1个多小时的读书学习时间,这种感觉让我每天都动力满满。
很多人觉得早起痛苦,但其实只需要一个转念。
你想,在每个清晨,阳光刚透过窗子,大部分人还在睡懒觉的时候,你已经早起洗漱完毕,倒上一杯热茶或咖啡,坐在书桌前,开始了一天的学习,这是一件多有意义的事情!
读:
这两年,我几乎每天都在阅读,累计阅读668天,读完了140+本,听完了100+本。一开始读的很慢,但渐渐越读越快,2021读了20多本,2022读了50本,今年的目标是102本,目前已经读了一多半。这些书都化成了我的养料,以各种各样的方式,支撑我前行。
毫不夸张的说,是阅读重塑了我,正是有了阅读,我才会打开认知,才会有了这两年持续的改变。
冥:
最近1年,我也开始学着冥想,累计冥想249天。冥想不是神秘的宗教仪式,而是一种锻炼专注能力、元认知能力的方法。练习冥想前,我的觉知能力很差,很容易生气,很容易愤怒,很容易沉浸在负面情绪里。
但练习冥想后,我的觉察能力大幅提升,现在一有负面情绪,就能很快意识到,并开始分析,这个情绪哪儿来的?
写:
这两年,我开始坚持写作,累计写作487天,写了100万+字,2021写了几万字,2022写了60万字,今年已经写了40多万字。每读完一本书就会写一篇书评,已经写了82篇。
一开始写的很烂、很慢,一篇2000字的文章要写4个小时,但慢慢就越写越快、越写越好了,现在一篇2000字的文章1个多小时就能写完。
而且,我明显感觉到,我的学习能力、思考能力、分析能力、表达能力都大幅提高。
更重要的是,我发现我很喜欢写作的感觉,虽然也有写不下去的痛苦,但更多时候会进入心流状态,内心被激情和愉悦填满。
周岭老师说,我们要拥有自己的作品,带着作品和别人社交,这是一种更高级的方式。你无需考虑如何说话,如何取悦他人,只要你持续写出有价值的作品,就会不断有人被你吸引,和你交朋友。
我开了公号,今年有了1万+粉丝,微读也有了1.3万关注,这就是持续写作的魅力。
跑:
这两年,也开始坚持运动了,累计运动472天。每天晚上我会做10分钟力量锻炼,俯卧撑、仰卧起坐、蹲起等等,没想到竟然练出了腹肌、胸肌和肱二头肌,虽然不是爆炸性的,但我感觉很好,再小的事情,只要坚持做,就会回报你结果,这也让我越来越有耐心。
我也开始跑步,2022跑了200公里,2023计划跑500公里,目前已完成350公里。一开始跑3公里我就会很喘,现在跑10公里都不觉得很累,身体素质明显变强了。更重要的是,感觉意志力也变强了,以前做事耐性很差,现在越来越能坚持了。
复:
这两年,也开始学会做每日复盘,已经累计复盘532天。2021复盘了81天,2022复盘了279天,今年已经坚持复盘200多天。
每天总结一下工作情况,写成功日记和感恩日记,在重要事件上进行深入反思。复盘给我带来最大的改变是,以前总喜欢抱怨别人,总觉得自己没问题,但现在一遇到事情,就会下意识思考,自己能做点什么。
人其实没办法改变别人,只能改变自己,向外求是不会有结果的,最好的办法是反求诸己。思考自己最终想要的结果是什么,然后承担责任,解决问题。
记:
这两年,也在坚持做时间记录,已经累计记录392天。每天记录时间开销,什么时间,做了什么事情,到晚上再做一个复盘总结。今天的时间都去哪儿了?有没有浪费时间?有没有使用不合理的地方?明天需要怎么调整。
这倒不是让自己成为一个工作狂魔,时间怎么分配由我自己决定,但不要让时间在自己毫不知情的情况下,被娱乐、短视频、资讯吸走。
做时间记录前,我对时间花到哪儿了,完全没有概念,但开始记录后,我对时间就敏感了很多,即使我被短视频吸走了,也能很快觉醒出来,而不会沉浸在里面一两个小时。或者说,是多了一种紧迫感,知道有更重要的事情等着我去做。
以上,就是我的人生七件套。现在回想,能有这些的改变,多亏了有阅读。
如果没有读《认知觉醒》,我就不会知道,阅读量<思考量<行动量<改变量,就不会通过阅读、思考和行动去改变自己,而仍旧沉浸在读完一本接一本书的快感里。
如果没有读《早起的奇迹》《把时间当作朋友》,我就不会知道,那些厉害的人都在早起学习,也就不会通过早起修炼自己,而是每天依旧在睡懒觉。
如果没有读《学会写作》《写作法宝》,我就不会知道,普通人改变自己命运最好的方式是写作,也就不会通过写作深度思考,而是每天仍旧过得浑浑噩噩。
如果没有读《运动改造大脑》《跑步治愈》,我就会不知道,坚持跑步有那么多意义,也就不会通过跑步锻炼身体,而是每天继续消耗自己。
如果没有读《十分钟冥想》《冥想》,我就不会知道,冥想能够培养人的觉察能力,也就不会通过冥想锻炼自己的大脑,而是继续被负面情绪控制。
如果没有读《复盘》《好好学习》我就不会知道,复盘能让人在犯错中成长,也就不会通过复盘反求诸己,而是仍旧在怨天尤人……
当然,这一切的起点都源于《认知觉醒》,在我最困难,最无助,最彷徨的时候,是这本书指引我走上改变之路。
所以,如果你想改变自己,不妨从阅读开始,从读《认知觉醒》开始,从“早读冥写跑复记”开始。
最后,假如你也和曾经的我一样,此时此刻刚好陷入困境,正饱受折磨,那么我想告诉你:“尽管眼下十分艰难,可日后这段经历说不定就会开花结果。”
这个世界有不少类似“缝隙”的地方,只要走运,找到适合自己的“缝隙”,就好歹能生存下去。
以前,我总想试图改变自己的命运,但这实在异常艰难。后来我想,一个人如果能找到自己的“缝隙”,以自己喜欢的方式生活,就已经是上天巨大的恩赐了。
因此,无论如何,请奋力前行。

Handler机制组成元素之间的关系

Handler机制主要有Handler、MessageQueue、Message、Looper几个元素构成。
它们之间的关系是:

  • 一个线程只有一个Looper实例
  • Looper中持有队列mQueue
  • Handler持有队列mQueue和Looper对象。在构造Handler实例时如果没有Looper入参,那就默认使用当前线程的Looper,ThreadLocal<Looper>.get()
  • Message中持有handler和next message
  • MessageQueue中持有当前message
  • 生产消息:Handler发送的信息通过MessageQueue.enqueueMessage将消息入队
  • 消费消息:Looper.loopOnce中将MessageQueue的消息取出,调用Message.target.dispatchMessage,target属性就标记了消息最终交给哪个Handler处理,所以这里的含义是在生产Msg的Handler中执行处理逻辑;如果MessageQueue信息为空,就会执行被挂起的IdleHandler。

dispatchMessage方法分析

dispatchMessage方法关系到消息队列中消息所对应的处理逻辑最终在哪如何被处理

title:android.os.Handle.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);
}
}

方法有三个逻辑分支,都是处理MessageQueue抛出的Msg:

  1. msg.callback不为空
    message.callback.run() Message的callback成员是一个Runnable对象
  2. Handler.mCallback不为空
    由Handler.Callback的接口实现来处理
  3. msg.callback和Handler.mCallback都为空
    由Handler.handleMessage方法处理,子类没重写则默认不处理

Handler处理并发实现

title:android.os.MessageQueue.enqueueMessage消息入队
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
boolean enqueueMessage(Message msg, long when) {  
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 因为可能在任意对象操作入队,而只会在looper所绑定的线程出队,所以这里加对象锁,保证入队出队操作是线程安全的
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;
}
title: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
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); // 用来检查消息队列中是否有新的消息要处理,当队列为空时,`nativePollOnce` 会使线程等待直到:1. 有新消息到达。2. 被唤醒去处理其他任务(例如,定时事件、输入事件等)。3. 明确使用 `wakeUp()` 方法唤醒。

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;
}
}
title:android.os.Looper
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
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;
}

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
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;
}
}
}

Handler的并发处理其实就是消息入队和出队被处理的过程

  1. 可以在任意线程将消息入队,具体线程由Handlder.sendMsg的方法栈决定
  2. 只会在Looper.loop方法中执行出队,而Looper.loop只会在指定的一个线程中执行的,也就是消息最终被处理的线程
  3. 可以看出入队和出队可能是在不同的线程中执行的,在MessageQueue中通过对象锁来保证线程安全

Hanlder与ANR的关系

消息阻塞机制

当主线程阻塞超过5s之后,就会触发ANR;前面我们知道,在Looper开启死循环取消息的时候,如果消息队列中没有消息的时候,就可能会被block,调用了nativePollOnce,那么为什么没有阻塞主线程呢?

其实应该把这分为两件事来看,looper.loop是用来处理消息,当没有消息的时候,主线程就休息了,不需要干任何事;像input事件,其实就是一个Message,当它加入到消息队列的时候,会调用nativeWake唤醒主线程,主线程来处理这个消息,只有处理这个消息超时,才会发生ANR,而不是死循环会导致ANR。

案例分析

title:"线程休眠"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7185b6a8 self=0xb400007375b4bbe0
| sysTid=3433 nice=0 cgrp=default sched=0/0 handle=0x749c9844f8
| state=S schedstat=( 800801640 66783841 881 ) utm=60 stm=19 core=0 HZ=100
| stack=0x7fc20cb000-0x7fc20cd000 stackSize=8192KB
| held mutexes=
native: #00 pc 000000000009ca68 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
native: #01 pc 0000000000019d88 /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
native: #02 pc 0000000000019c68 /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
native: #03 pc 0000000000112194 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loop(Looper.java:183)
at android.app.ActivityThread.main(ActivityThread.java:7723)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)

在我们分析ANR日志时,经常会看到这样表现,结合上面我们对于Handler的了解,这个时候其实就是没有消息了,我们看已经调用了nativePollOnce方法,此时主线程就休眠了,等待下一个消息到来。

title:"ANR堆栈"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7185b6a8 self=0xb400007375b4bbe0
| sysTid=3906 nice=-10 cgrp=default sched=0/0 handle=0x749c9844f8
| state=S schedstat=( 2591708189 61276010 2414 ) utm=220 stm=38 core=5 HZ=100
| stack=0x7fc20cb000-0x7fc20cd000 stackSize=8192KB
| held mutexes=
// ......
- waiting to lock <0x0167ghe6d> (a java.lang.Object) held by thread 5
// ...... 方法调用
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7723)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)

在这段堆栈中,我们看到主线程已经是出问题了,处于Blocked的状态,那么在Handler调用dispatchMessage方法的时候,是调用了handleCallback,说明此时是调用了post方法,在post方法中,主线程一直想要获取其他线程持有的一把锁,导致了超时产生了ANR。

日常开发中很多地方都见到了LayoutInflater.from().inflate()方法去将一个布局文件的内容填充为一个View,特别是inflate()这个方法,这个方法的参数有布局文件id,root和attachToRoot,那么这个root和attachToRoot参数有什么作用呢?

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
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {  
synchronized (mConstructorArgs) {

View result = root;

try {
advanceToRootNode(parser);
final String name = parser.getName();

if (TAG_MERGE.equals(name)) {
// merge标签处理
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}

rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

} catch (XmlPullParserException e) {

} catch (Exception e) {

} finally {
// Don't retain static reference on context.

}

return result;
}
}

分析root与attachToRoot的四种组合情况,可以得出以下结论:

root null null not null not null
attachToRoot true false true false
返回创建的不会带params的view,不作为root的子view 创建出来的View没有设置params就使用,所以被创建出来的布局,最外层设置的宽高是无效的,并最终返回了创建出来的View对象 view带param添加到root中,返回root 返回创建的带params的view,不作为root的子view
  • root = null,无论attachToRoot取什么值都是一样的效果