一廊遍、提出問題
面試時常被問到的問題:
簡述 Android 消息機制
Android 中 Handler璃谨,Looper蚀瘸,MessageQueue芽狗,Message 有什么關(guān)系绢掰?
這倆問題其實是一個問題,其實只要搞清楚了 Handler童擎,Looper滴劲,MessageQueue,Message 的作用和聯(lián)系顾复,就理解了 Android 的 Handler 消息機制班挖。那么再具體一點:
為什么在主線程可以直接使用 Handler?
Looper 對象是如何綁定 MessageQueue 的芯砸?
MessageQueue 里的消息從哪里來萧芙?Handler是如何往MessageQueue中插入消息的?
Message 是如何綁定 Handler 的假丧?
Handler 如何綁定 MessageQueue双揪?
關(guān)于 handler,在任何地方 new handler 都是什么線程下包帚?
Looper 循環(huán)拿到消息后怎么處理渔期?
二、解決問題
從主消息的消息機制開始分析
1.1 主線程Looper的創(chuàng)建和循環(huán)
Android應用程序的入口是main函數(shù),主線程Looper的創(chuàng)建也是在這里完成的.
ActivityThread ---> main()函數(shù)
Looper.prepareMainLooper():用來創(chuàng)建主線程的Looper對象
1.1.1 創(chuàng)建主線程Looper
looper ---> prepareMainLooper()
prepareMainLooper()方法:主要是使用prepare(false)創(chuàng)建當前線程的Looper對象,再使用myLooper()方法來獲取當前線程的Looper對象.
// step1 Looper() ---> prepare()
prepare()方法中用ThreadLocal來保存主線程的Looper對象.
ThreadLocal:用來存儲數(shù)據(jù)的類,存放屬于當前線程的變量
ThreadLocal提供了get/set方法分別用來獲取和保存變量. ? 比如主線程通過prepare()方法來創(chuàng)建Looper對象,并使用sThreadLocal.set(new Looper(quitAllowed))來保存主線程的Looper對象.那么主線程調(diào)用myLooper()[實際上是調(diào)用了sThreadLocal.get方法].通過ThreadLocal來獲取主線程的Looper對象.
如果在子線程調(diào)用這些方法就是通過ThreadLocal保存和獲取屬于子線程的Looper對象
// step2: Looper ---> myLooper()
myLooper()方法 == ThreadLocal.get()方法 獲取當前線程ThreadLocal保存的Looper對象
附加問題
問題一:為什么在主線程可以直接使用Handler渴邦?
主線程已經(jīng)創(chuàng)建了Looper對象并開啟了消息循環(huán)
問題二:Looper對象是如何綁定MessageQueue的/Looper對象創(chuàng)建MessageQueue的過程
Looper有一個成員變量Queue,它就是Looper對象默認保存的MessageQueue.
上述代碼中Looper自帶一個構(gòu)造器,新建Looper對象時會直接創(chuàng)建MessageQueue并且賦值給Queue.
1.1.2 開始循環(huán)處理消息
回到最開始的main()函數(shù),在創(chuàng)建了Looper對象以后就調(diào)用了Looper.loop()來循環(huán)處理消息
Looper ---> loop()
step1:myLooper():獲取當前線程的對象[ ThreadLocal在哪個線程使用就獲取哪個線程的Lopper對象]
step2:me.mQueue: Looper對象創(chuàng)建時新建的MessageQueue變量
step3: 一個for循環(huán),首先通過 queue.next() 來提取下一條消息
step4:msg.target.dispatchMessage(msg) 這個方法最終會調(diào)用 Handler的handlerMessage(msg)方法
附加問題
msg.target是何時被賦值的/Messge是如何綁定Handler的? (那我們來看看dispatchMessage()這個方法)
可以看到最后執(zhí)行了handlerMessage()方法 這是一個空方法也就是需要我們覆寫并實現(xiàn)的
dispatchMessage附加問題
消息分發(fā)的優(yōu)先級
????·Message的回調(diào)方法:message.callback.run()優(yōu)先級最高;
????·Handler的回調(diào)方法:mCallback.handleMessage(msg)優(yōu)先級次于上方;
????·Handler的回調(diào)方法:handleMessage()優(yōu)先級最低;
Looper循環(huán)通過Handler發(fā)送消息一個整體的流程.接下來分析Handler在消息機制中的主要作用以及和Looper疯趟、Message的關(guān)系.
1.2 Handler的創(chuàng)建和作用
上面說到loop()方法不斷從消息隊列MessageQueue中取出消息(queue.next()方法),如果沒有消息則阻塞,反之交給Message綁定的Handler處理
需要解決的問題
????· MessageQueue里的消息從哪里來的?
????· Handler是如何往MessageQueue中插入消息的?
? ? · " msg.target "是何時被賦值的?/Message是如何綁定Handler的?
1.2.1 Handler發(fā)送消息
Handler ---> sendMessage(Message msg)
可以看到sendMessage(Message msg) 方法最終會調(diào)用enqueueMessage()方法,這個方法主要有兩個作用
? ? ? ? ? ? 1> 賦值Message對象的target
? ? ? ? ? ? ? ? ? ? msg.target = this 把發(fā)送消息的Handler賦值給msg的target.
? ? ? ? ? ? 2> 消息隊列插入消息
? ? ? ? ? ? ? ? ? ? queue.enqueueMessage(msg,uptimeMillis) queue是MessageQueue的一個實例
????????????????????queue.enqueueMessage(msg,uptimeMillis) 是執(zhí)行 MessageQueue的enqueueMessage方法來插入消息
1.2.2 Handler的創(chuàng)建
下面是Handler無參構(gòu)造器和主要構(gòu)造器,另外幾個重載的構(gòu)造器是有些通過傳遞不同的參數(shù)調(diào)用包含兩個參數(shù)的構(gòu)造器
兩個參數(shù)構(gòu)造器的第一個參數(shù)為callback回調(diào),第二個函數(shù)用來標記消息是否異步
step1: 調(diào)用myLooper()方法,該方法是使用sThreadLocal對象獲取當前線程的Looper對象
如果獲取的Looper對象為NULL,說明沒有執(zhí)行Looper.prepare()為當前線程保存Looper變量,就會拋出RuntimeException異常.
這里又說明了Handler必須再有Looper的線程中使用.報錯先放一邊,沒有Looper就無法綁定MessageQueue對象也就無法進行更多有關(guān)操作
step2: mQueue = mLooper.mQueue; 說明了 Handler的MessageQueue對象是由當線程Looper的MessageQueue賦值.
由于Handler和Looper可以看作使用的是一個HandlerMessageQueue對象,所以Handler和Looper共享消息隊列MessageQueue.
? ? ? ? ? ? ·Handler發(fā)送消息(用mQueue往消息隊列插入消息)
? ? ? ? ? ? ·Looper可以方便的循環(huán)使用mQueue查詢消息.如果查詢到消息,就可以用Message對象綁定的Handlertarget去處理消息,反之則阻塞.
補充問題
關(guān)于handler,在任何地方new handler都是什么線程下?(這個問題要分是否傳遞Looper對象來看)
? ? ? ? ·傳遞Looper對象創(chuàng)建Handler: Handler handler = new Handler(looper);?
? ? ·不傳遞 Looper 創(chuàng)建 Handler:Handler handler = new Handler() 上文就是 Handler 無參創(chuàng)建的源碼,可以看到是通過 Looper.myLooper() 來獲取 Looper 對象谋梭,也就是說對于不傳遞 Looper 對象的情況下信峻,在哪個線程創(chuàng)建 Handler 默認獲取的就是該線程的 Looper 對象,那么 Handler 的一系列操作都是在該線程進行的瓮床。
可以看出來傳遞Looper對象Handler就直接使用了.所以對于傳遞Looper對象創(chuàng)建Handler的情況下,傳遞的Looper是哪個線程的,Handler綁定的就是該線程
到這里Looper和Handler就有應該大概的流程了,我們看一下應該簡單的子線程Handler的使用例子
step1:調(diào)用Looper.prepare() 為當前線程創(chuàng)建Looper對象,同時也就創(chuàng)建了MessageQueue對象[創(chuàng)建Looper對象的同時,Looper會自帶MessageQueue對象]盹舞。之后將該線程的Looper對象保存在ThreadLocal中.注意這里的一切操作都在子線程中完成,如果不調(diào)用Looper.prepare方法就會導致Handler報錯.
step2:Handler handler = new Handler() 創(chuàng)建Handler對象,覆寫handlerMessage處理消息,等待該Handler發(fā)送的消息處理時會調(diào)用該方法.
step3:handler.sendEmpteMessage(1) 使用handler發(fā)送消息,這里只是單例. 發(fā)送的過程中會將自己賦值給msg.target,然后再將消息插入到Looper綁定的MessageQueue()中;
step4:Looper.loop() 首先獲取當前線程的Looper,根據(jù)Looper對象可以拿到Looper保存在MessageQueue中的對象mQueue.有了MessageQueue對象就可以for循環(huán)它保存的消息Message對象,如果沒有消息就返回null導致阻塞. 然后用Message中保存的Handler:msg.target來處理消息,最終調(diào)用handlerMessage覆寫的方法來處理消息
step5:Looper.myLooper.quit() 邏輯處理完畢后,需要調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待狀態(tài),而如果子線程處于等待狀態(tài)退出Looper以后,這個線程就會被立刻終止产镐。[因此建議不需要使用Handler的時候終止Looper]
三、總結(jié)和其他小知識點
2.1 Handler矾策、Looper磷账、MessageQueue、Message
? ? ·Handler用來發(fā)送消息,創(chuàng)建時先獲取默認或傳遞來的Looper對象,并持有Looper對象包含的MessageQueue對象,發(fā)送消息的時候該MessageQueue對象來插入消息并把自己封裝到具體的Message中;
? ? ·Looper用來為某個線程作消息循環(huán),.Looper持有一個MessageQueue對象mQueue,這樣就可以通過循環(huán)來獲取MessageQueue所維護的Message.如果獲取MessageQueue沒有消息時,便阻塞Loop的queue.next()中的nativePollOnce()方法,隨后喚起主線程繼續(xù)工作,之后便用Message封裝的handler對象進行處理.
? ? ·MessageQueue是一個消息隊列.他不直接添加消息,而是通過Looper關(guān)聯(lián)的Handler對象來添加消息.
? ? ·Message包含了要傳遞的數(shù)據(jù)和信息
2.2.1 Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死?
(簡單做法就是可執(zhí)行代碼是一直能執(zhí)行下去的,死循環(huán)便能保證不會被退出) 通過創(chuàng)建新線程的方式
為這個死循環(huán)準備了一個新的線程(在進入死循環(huán)之前便創(chuàng)建了新的binder[Binder詳細講解]線程,在代碼ActivityThread.main()中)
thread.attach(false):創(chuàng)建一個Binder線程[具體是指ApplicationThread,Binder的服務器,用于接收系統(tǒng)服務AMS發(fā)送來的事件]該binder線程通過Handler將Message發(fā)送給主線程
主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?
其實不是的,這里涉及到Linux pipeLinux pipe詳細教學/epoll機制Epoll機制詳細教學(Linux pipe與Epoll的區(qū)別).簡單的說在主線程的MessageQueue沒有消息時,便阻塞loop的quque.next().所以說大多數(shù)時候都處于休眠狀態(tài),并不會消耗大量CPU資源.
Activity的生命周期是怎么實現(xiàn)在死循環(huán)體外能夠執(zhí)行起來的?
類H繼承了Handler,在主線程創(chuàng)建時就創(chuàng)建了Handler用于處理Binder線程發(fā)送來的消息
Activity的生命周期都是依靠主線程的Looper.loop贾虽,當收到不同Message時則采用相應措施:
在H.handleMessage(msg)方法中逃糟,根據(jù)接收到不同的msg,執(zhí)行相應的生命周期蓬豁。
比如收到msg=H.LAUNCH_ACTIVITY绰咽,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制地粪,創(chuàng)建Activity實例取募,然后再執(zhí)行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY蟆技,則調(diào)用ActivityThread.handlePauseActivity()方法玩敏,最終會執(zhí)行Activity.onPause()等方法。 上述過程质礼,我只挑核心邏輯講旺聚,真正該過程遠比這復雜。
3.3 Handler使用造成內(nèi)存泄漏
? ? ? ?? · 有延時消息,要在Activity銷毀的時候移除Messages
? ? ? ?? · 匿名內(nèi)部類導致的泄漏改為匿名靜態(tài)內(nèi)部類,并且對上下文或者Activity使用弱引用
4.1Handler原理
handler的構(gòu)造方法內(nèi)部通過Looper.myLooper創(chuàng)建一個Looper,然后通過Looper去創(chuàng)建一個消息隊列
這個時候的handler,looper與messagequeue就捆綁到一起了這個時候SendEmptyMessage()與sendEmptyMessageDelayed()最終都會到enqueueMessage()方法當中.
這個方法是將發(fā)送的消息添加到消息隊列中.在這個方法中會把當前的handler對象綁定到message當中的一個target變量中.這樣就完成了handler向消息隊列存放消息的過程.
4.2 Handler的應用場景
因為Android中,主線程不建議做耗時操作,子線程不建議更新UI.
但Android開發(fā)其實就是搭建好頁面,將服務器的數(shù)據(jù)展示到頁面上.所以網(wǎng)絡請求會使用的很頻繁
而網(wǎng)絡請求屬于耗時操作,需要放到子線程中完成.
但一般情況下也不會通過子線程更新UI.需要將請求成功的數(shù)據(jù)發(fā)送到主線程進行UI更新
所以一般使用handler
4.3 ThreadLocal
1>定義:ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲類
4.4 HandlerThread
1>定義:是一個Android已經(jīng)封裝好的輕量級異步類
2>作用: 實現(xiàn)多線程/異步通信眶蕉、消息傳遞
3>優(yōu)點:方便實現(xiàn)異步通信
4.5 內(nèi)部原理=Thread類+Handler機制
補充:
Looper:輪詢消息,獲取到消息后發(fā)送給Handler
? ? a.取消息
? ? b.消息給到Handler
? p?rpare() 初始化Looper,只有一個
? Looper() 構(gòu)造 初始化了消息隊列
? loop() 輪詢方法(取消息 將消息給到handler)
Handler
? ? a.發(fā)消息
? ? b.處理消息
創(chuàng)建Handler保證當前線程持有Looper對象
sendMessage() 這個方法最終會走enqueueMessage()方法 在這個方法中會把當前的handler對象綁定到message當中的一個target變量中.這樣就完成了handler向消息隊列存放消息的過程.
MessageQueue
? ? 存儲消息(排隊)
Message????????
? ? 數(shù)據(jù)的載體
ThreadLocal
? ? 一個存儲線程對象的容器
口述
首先我們Handler內(nèi)部構(gòu)造通過Looper.myLooper創(chuàng)建了一個Looper對象,Looper對象自己攜帶構(gòu)造器創(chuàng)建了MessageQueue對象,這樣Handler砰粹、Looper、MessageQueue綁定到了一起 Looper:它是用來輪詢消息,獲取到消息交給Handler Handler呢負責發(fā)送消息 處理消息 MessageQueue:是一個存儲消息的隊列.我們先走到Looper里面的p?rpare()方法這個方法只能調(diào)用一次,它是用來創(chuàng)建Looper對象的,在這個方法里面有一個ThreadLocal對象,它是用來存儲Looper對象的.用這個方法來保證一個線程只能有一個Looper實例.然后我們看一下Looper的構(gòu)造,里面初始化了MessageQueue對象并且保存了當前線程.消息是如何傳進Handler里面的 看一下Loop.loop() 首先里面先調(diào)用了一個myLooper() 它把我們剛開始通過p?rpare()方法創(chuàng)建的Looper對象取了出來 如果為Looper為空則拋出異常 Looper不為空的話將messageQueue對象獲取 進行死循環(huán)遍歷 通過queue.next()方法取出來消息 如果消息為null則返回如果消息不為空的話調(diào)用msg.target.dispatchMessage(msg)方法這個方法就是將消息傳遞給handler的[target就是handler的一個對象] 在dispatchMessage()這個方法 通過他處理消息 這個方法里面可以看到 當mag的callBack對象是null的時候它會走一個handleMessage(Message msg) 是一個空方法需要我們覆寫? 我們Handler通過sendMessage()這個方法發(fā)送消息 隨后走到SendEmptyMessage()與sendEmptyMessageDelayed() 這兩個方法最終調(diào)用 enqueueMessage()這個方法 它會通過 msg.target =this; 把發(fā)送消息的Handler賦值給msg的target. 這樣就完成了handler向消息隊列存放消息的過程
為什么主線程不需要調(diào)用Looper.prepare()造挽? 因為系統(tǒng)已經(jīng)幫我們創(chuàng)建過了