前言
2022年已過1/4,時(shí)間過的真是快。近些年大Android的發(fā)展也很是迅速,尤其是遵循MVVM或者MVI架構(gòu)下懈叹,使用Jetpack + Kotlin + Corroutine + Flow的組合,大大提升了Android應(yīng)用的開發(fā)效率萝毛。然而项阴,類似的效率的提升往往是通過層層封裝,隱藏底層原理笆包,簡化調(diào)用,從而達(dá)到降低開發(fā)的上手門檻目的的略荡。作為一個(gè)有品位的開發(fā)者庵佣,又怎能滿足于只了解上層的API調(diào)用。本文我們就來通過實(shí)例來試著聊一聊Android經(jīng)典線程通信框架Handler的基本原理汛兜。
目的
通過實(shí)現(xiàn)一個(gè)簡單的Handler框架巴粪,試圖解釋其底層的工作原理。
背景知識
-
線程間通信
即不同線程之間交換信息粥谬,Java中常見的有:- Object
wait(), notify()/notifyAll() - LockSupport
park(), uppark() - Condition
await(), signal()/signalAll()
- Object
Android的Handler工作原理
簡言之肛根,重復(fù)執(zhí)行“發(fā)送消息-分發(fā)消息-處理消息”。
更準(zhǔn)確的問題描述應(yīng)該是:Android的基于Handler + Message + MessageQueue + Looper的消息機(jī)制是怎么工作的漏策?
Handler借助
handler#sendMessage(message)
方法派哲,把消息Message對象插入消息隊(duì)列MessageQueue對象中,同時(shí)Looper對象循環(huán)往復(fù)地從MessageQueue對象中取下一個(gè)消息Message對象分發(fā)給Looper對象所在線程去處理掺喻,如此循環(huán)往復(fù)芭届。
注意: 同一個(gè)線程中,Looper和MessageQueue有且只有一個(gè)實(shí)例感耙,能且只能在該線程內(nèi)部實(shí)例化褂乍。Handler可有多個(gè)實(shí)例,可在任意線程內(nèi)實(shí)例化即硼,但前需保證實(shí)例化動(dòng)作所在線程的Looper已被初始化逃片。另外,也可以通過調(diào)用帶Looper參數(shù)的Handler構(gòu)造器實(shí)例化只酥,以達(dá)到關(guān)聯(lián)指定線程(Looper所在)的目的褥实。
從上述描述中,我們可以得出幾個(gè)關(guān)鍵的點(diǎn):
消息對象:保存消息的數(shù)據(jù)結(jié)構(gòu)(會保存發(fā)送者的Handler對象引用)
發(fā)送消息:調(diào)用者通過Handler#sendMessage(message)
發(fā)送消息(發(fā)到哪里层皱?緩存MessageQueue)
緩存消息:MessageQueue保存由外部發(fā)送來的消息(怎么保存性锭?類似單向鏈表結(jié)構(gòu))
讀取消息:Looper#loop()
內(nèi)調(diào)用MessageQueue.next()
讀取消息(沒消息了怎么辦?阻塞)
分發(fā)消息:取到消息后叫胖,執(zhí)行消息對象持有的Handler對象的Handler#dispatchMessage(message)
方法草冈。(運(yùn)行在哪個(gè)線程?Looper對象所在的線程)
處理消息:Handler#dispatchMessage(message)
內(nèi)再調(diào)用Handler#handleMessage(message)
方法,后者便正是調(diào)用者常重寫的方法怎棱。
循環(huán)往復(fù):Looper#loop()
內(nèi)部是一個(gè)for(;;){...}
哩俭,一直執(zhí)行,提供循環(huán)往復(fù)的驅(qū)動(dòng)力拳恋。
線程內(nèi)Looper對象唯一性:依靠ThreadLocal保證每個(gè)線程內(nèi)部Looper唯一凡资,即只被實(shí)例化一次。
線程內(nèi)MessageQueue對象唯一性:被Looper實(shí)例化并持有谬运,間接保證了唯一性隙赁。
- Android主線程Handler使用
應(yīng)用啟動(dòng)時(shí),
ActivityThread.main(String[])
內(nèi)部會提前初始化Looper梆暖。故在主線程內(nèi)部伞访,可直接使用Handler的無參數(shù)構(gòu)造器實(shí)例化。
- Android子線程Handler使用
如上所述轰驳,子線程使用Handler需要先確保Looper已被初始化厚掷,但子線程默認(rèn)是沒有初始化Looper的,故需在子線程執(zhí)行的一開始主動(dòng)調(diào)用
Looper.prepare()
和Looper.loop()
级解。此后則可如在主線程一般任意實(shí)例化Handler冒黑。
- Android的HandlerThread原理
即封裝了Handler的Thread子類。
HandlerThread#start()
被調(diào)用后勤哗,該線程內(nèi)部會初始化Looper實(shí)例抡爹。其他線程可從該HandlerThread對象中取出Looper實(shí)例,并用它構(gòu)造出新的Handler對象俺陋。此后其他線程可借助該Handler對象豁延,調(diào)用Handler#sendMessage(message)
給HandlerThread的線程發(fā)送消息。
實(shí)現(xiàn)
思路
在了解了上述的Android的Handler工作原理后腊状,自己實(shí)現(xiàn)一個(gè)簡單的消息框架就有方向了诱咏,無非就是解決上述中提到的“消息對象”,“發(fā)送消息”缴挖,“緩存消息”……關(guān)鍵點(diǎn)即可袋狞。
線程內(nèi)Looper對象唯一性,線程內(nèi)MessageQueue對象唯一性和讀取消息是需要著重考慮的映屋。
編碼
Message
package com.custom.handler
data class Message(
val what: Int = 0,
val args: Any? = null,
val block: (() -> Unit)? = null,
) {
companion object {
val HEAD_MESSAGE: Message = Message()
}
var target: Handler? = null
var next: Message? = null
}
MessageQueue
package com.custom.handler
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
class MessageQueue {
private val queueLock = ReentrantLock()
private val queueCondition = queueLock.newCondition()
private val messageLock = ReentrantReadWriteLock()
private val messageReadLock = messageLock.readLock()
private val messageWriteLock = messageLock.writeLock()
private var currentMessage: Message = Message.HEAD_MESSAGE
get() = messageReadLock.withLock { field }
set(value) = messageWriteLock.withLock { field = value }
fun next(): Message {
while (true) {
queueLock.withLock {
if (currentMessage.next == null) {
println("[${Thread.currentThread().name}] Message queue is empty, then it is going to await...")
queueCondition.await()
println("[${Thread.currentThread().name}] Message queue wakes up.")
}
}
val nextMessage = currentMessage.next!!
nextMessage.next = null
currentMessage = nextMessage
return nextMessage
}
}
fun enqueue(msg: Message) {
messageWriteLock.withLock {
msg.next = null
currentMessage.next = msg
queueLock.withLock { queueCondition.signalAll() }
}
}
}
Looper
package com.custom.handler
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
class Looper private constructor(thread: Thread) {
internal val mMessageQueue = MessageQueue()
private val mThread = thread
private val quitFlagReentrantReadWriteLock = ReentrantReadWriteLock()
private val quitFlagReadLock = quitFlagReentrantReadWriteLock.readLock()
private val quitFlagWriteLock = quitFlagReentrantReadWriteLock.writeLock()
private var quitFlag: Boolean? = null
get() = quitFlagReadLock.withLock { field }
set(value) = quitFlagWriteLock.withLock { field = value }
companion object {
private val looperMapReentrantReadWriteLock = ReentrantReadWriteLock()
private val looperMapReadLock = looperMapReentrantReadWriteLock.readLock()
private val looperMapWriteLock = looperMapReentrantReadWriteLock.writeLock()
private val looperMap: MutableMap<Thread, Looper> = mutableMapOf()
val looper: Looper?
get() = looperMapReadLock.withLock { looperMap[Thread.currentThread()] }
fun prepare(): Looper {
require(looper == null) { "Looper.prepare() must be called once in each thread." }
val thread = Thread.currentThread()
val threadLooper = Looper(thread)
looperMapWriteLock.withLock { looperMap[thread] = threadLooper }
return threadLooper
}
fun loop() {
requireNotNull(looper) { "Looper.prepare() must be called before Looper.loop()" }
looper?.quitFlag = false
while (looper?.quitFlag == false) {
val me = looper!!.mMessageQueue
val msg = me.next()
kotlin.runCatching {
println("[${Thread.currentThread().name}] Dispatching message is started.")
msg.target?.dispatchMessage(msg)
println("[${Thread.currentThread().name}] Dispatching message is stopped. ")
}.onFailure {
it.printStackTrace()
}
}
}
}
fun quit() {
quitFlag = true
looperMap.remove(Thread.currentThread())
}
}
Handler
package com.custom.handler
open class Handler {
private var interceptor: Interceptor? = null
private lateinit var mLooper: Looper
constructor() {
val looper = requireNotNull(Looper.looper) { "No looper found in current thread." }
Handler(looper)
}
constructor(looper: Looper) {
mLooper = looper
}
interface Interceptor {
fun handleMessage(message: Message?): Boolean
}
open fun dispatchMessage(message: Message?) {
println("[${Thread.currentThread().name}] onDispatchMessage: message=$message")
message ?: return
if (message.block == null) {
if (interceptor?.handleMessage(message) != true){
handleMessage(message)
}
return
}
message.block.invoke()
}
open fun handleMessage(message: Message?) {
println("[${Thread.currentThread().name}] onReceiveMessage: message=$message")
}
fun sendMessage(message: Message?) {
println("[${Thread.currentThread().name}] onSendMessage: message=$message")
message?.target = this
mLooper.mMessageQueue.enqueue(message ?: return)
}
fun quit(){
mLooper.quit()
}
}
[擴(kuò)展] HandlerThread
import com.custom.handler.Looper
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class HandlerThread(name:String):Thread(name) {
private val lock = ReentrantLock()
private val condition = lock.newCondition()
private var mLooper: Looper? = null
val looper: Looper
get() {
if (mLooper == null) {
lock.withLock { condition.await() }
}
return requireNotNull(mLooper)
}
override fun run() {
super.run()
mLooper = Looper.prepare()
lock.withLock { condition.signalAll() }
Looper.loop()
}
}
[測試] Main
由于HandlerThread已經(jīng)處理了子線程初始化Looper的操作苟鸯,因此,使用該類直接測試即可棚点。
import com.custom.handler.Handler
import com.custom.handler.Message
fun main(args: Array<String>) {
testHandler()
}
private fun testHandler(){
val handlerThread = HandlerThread("Thread-1").apply { start() }
val handler = object : Handler(handlerThread.looper) {
override fun handleMessage(message: Message?) {
super.handleMessage(message)
}
}
for (i in 0..3){
if (i == 3) {
handler.quit()
return
}
Thread.sleep(3000)
println("\n")
handler.sendMessage(Message(100, "message from Main-Thread"))
}
}
// console output
[Thread-1] Message queue is empty, then it is going to await...
[main] onSendMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Message queue wakes up.
[Thread-1] Dispatching message is started.
[Thread-1] onDispatchMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] onReceiveMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Dispatching message is stopped.
[Thread-1] Message queue is empty, then it is going to await...
[main] onSendMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Message queue wakes up.
[Thread-1] Dispatching message is started.
[Thread-1] onDispatchMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] onReceiveMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Dispatching message is stopped.
[Thread-1] Message queue is empty, then it is going to await...
[main] onSendMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Message queue wakes up.
[Thread-1] Dispatching message is started.
[Thread-1] onDispatchMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] onReceiveMessage: message=Message(what=100, args=message from Main-Thread, block=null)
[Thread-1] Dispatching message is stopped.
Process finished with exit code 0
Q&A
- Handler是怎么實(shí)現(xiàn)線程切換的早处?
就個(gè)人理解而言,所謂“線程切換”瘫析,即就是實(shí)現(xiàn)線程間的通信即可砌梆。此處默责,我們姑且把“線程”理解為一個(gè)無限循環(huán)。外界的消息/操作要想插入到這個(gè)“線程”咸包,可想象為把消息/操作插入到無限循環(huán)中桃序,如此以來便完成了“線程切換”。
消息插入無限循環(huán):即為存在競爭關(guān)系的變量的同步問題烂瘫。線程外部修改變量媒熊,線程內(nèi)部讀取變量》乇龋可通過背景知識中提到的各種線程間通信的方法來實(shí)現(xiàn)芦鳍。
操作插入無限循環(huán):即為注冊回調(diào)問題。
發(fā)送消息時(shí)葛账,調(diào)用
Handler#sendMessage(message)
怜校,該方法運(yùn)行在發(fā)生調(diào)用動(dòng)作所在的線程(簡稱,調(diào)用線程)注竿;處理消息時(shí),會先后調(diào)用Handler#dispatchMessage(messge)
魂贬、Handler#handleMessage(message)
巩割,此兩方法運(yùn)行在Looper.loop()
的無限循環(huán)中,即Looper的實(shí)例對象所屬的線程(簡稱付燥,處理線程)宣谈。而發(fā)送消息的本質(zhì)是:調(diào)用線程向處理線程的Looper內(nèi)的消息隊(duì)列MessageQueue中插入一個(gè)消息對象;處理消息的本質(zhì)是:處理線程取出消息再調(diào)用消息實(shí)例對象持有的Handler實(shí)例的Handler#handleMessage(message)
键科,參數(shù)即為當(dāng)前取出的消息實(shí)例對象闻丑。
- 無消息時(shí),MessageQueue#next()如何被阻塞勋颖?
方法內(nèi)部會先調(diào)用android.os.MessageQueue#nativePollOnce(ptr, timeoutMillis)
方法嗦嗡。若當(dāng)前沒有消息,則nativePollOnce(ptr, timeoutMillis)會從native層使用epoll的epoll_wait
機(jī)制阻塞當(dāng)前線程饭玲,至此MessageQueue.next()
也就被阻塞侥祭。
Message next () {
...
for (;;) {
...
android.os.MessageQueue#nativePollOnce
...
return nextMsg
}
}
- 有新消息時(shí),MessageQueue#next()如何被喚醒茄厘?
當(dāng)消息隊(duì)列被插入新消息時(shí)矮冬,MessageQueue#enqueueMessage(msg, when)
會被調(diào)用,消息插入完成后次哈,其內(nèi)部android.os.MessageQueue#nativeWake(ptr)
會被調(diào)用胎署,此時(shí)原本被android.os.MessageQueue#nativePollOnce(ptr, timeoutMillis)
阻塞的線程就會喚醒,并開始執(zhí)行取消息和處理消息的流程窑滞。
boolean enqueueMessage (message, when) {
...
//insert message
...
nativeWake(mPtr)
}
寫在最后
文中提到的ThreadLocal琼牧,簡言之恢筝,就是JDK提供的一種存儲“線程內(nèi)部”變量的手段。Android的基于Handler的消息機(jī)制障陶,也正是借助這一點(diǎn)來保存各個(gè)線程內(nèi)部Looper的滋恬。當(dāng)然,若在不考慮性能優(yōu)化等各種因素條件下抱究,不使用ThreadLocal也是可以實(shí)現(xiàn)類似功能的恢氯,如本文所實(shí)現(xiàn)的消息框架則是使用了Map的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)的。 當(dāng)然鼓寺,本文的簡單實(shí)現(xiàn)也旨在解釋清楚Android消息機(jī)制的原理勋拟。
附:
源碼地址: https://github.com/heychinje/Simple-Handler
關(guān)于Android中的epoll:https://stackoverflow.com/questions/38818642/android-what-is-message-queue-native-poll-once-in-android