Handler詳解
1、handler是什么?
2、為什么要用handler?
3、handler怎么用呢?
4、android 為什么要設(shè)計(jì)只能通過handler機(jī)制更新UI?
5、handler的原理是什么?
6、使用handler時候遇到的問題?
7、如何實(shí)現(xiàn)一個與線程相關(guān)的Handler?
8、HandlerThread又是什么?
9、如何在主線程給子線程發(fā)送消息呢?
10、android中更新UI的幾種方式?
11、非UI線程真的不能更新UI嗎?
12、使用handler時遇到的問題?
一、handler是什么?
Handler主要用于異步消息的處理: 有點(diǎn)類似輔助類,封裝了消息投遞、消息處理等接口。當(dāng)發(fā)出一個消息之后,首先進(jìn)入一個消息隊(duì)列,發(fā)送消息的函數(shù)即刻返回,而另外一個部分在消息隊(duì)列中逐一將消息取出,然后對消息進(jìn)行處理,也就是發(fā)送消息和接收消息不是同步的處理。 這種機(jī)制通常用來處理相對耗時比較長的操作。
2、為什么要用handler?
為什么要用handler?不用這種機(jī)制行不行?不行!android在設(shè)計(jì)的時候,就封裝了一套消息的創(chuàng)建、傳遞、處理機(jī)制,如果不遵循這種機(jī)制,就沒有辦法更新UI信息,就會拋出異常信息。
3、handler怎么用呢?
1)handler.post()
2)handler.postDelayed()
3)handler.sendMessage();
4)handler.sendEmptyMessage();
5)handler.removeCallback(runnable)
1)主線程創(chuàng)建handler,在子線程中通過handler的post(Runnable)方法更新UI信息。
2)主線程創(chuàng)建handler,通過handler.postDelayed(myRunnable,1000)
1、main中的handler.postDelayed(myRunnable,1000)是給ImageView設(shè)置Image,只執(zhí)行一次
2、main中改成handler.post(myRunnable)也可以,直接加載Image,不會等待1000ms后再加載
3、MyRunnable中的handler.postDelayed(myRunnable,1000)是為了輪播圖片,每隔1000ms后執(zhí)行一次run方法,實(shí)現(xiàn)輪播
3)handler.sendMessage();
4)handler.sendEmptyMessage();
5)handler.removeCallback(runnable)
4、android 為什么要設(shè)計(jì)只能通過handler機(jī)制更新UI?
最根本的目的是解決多線程并發(fā)溫問題:
假設(shè)如果在一個activity當(dāng)中,有多個線程去更新UI,并且都沒有加鎖機(jī)制,那么會什么樣子的問題?更新界面混亂。
如果對更新UI的操作都進(jìn)行加鎖處理的話又會產(chǎn)生什么樣子的呢?性能下降。
出于對以上問題的考慮,Android給我們提供了一套更新UI的機(jī)制,我們只要遵循這個機(jī)制就可以了,根本不用去關(guān)心多線程并發(fā)的問題,所有的更新UI的操作,都是在主線程的消息隊(duì)列中去輪訓(xùn)處理的。
5、handler的原理是什么?
一、Handler封裝了消息的發(fā)送 (主要包括消息發(fā)送給誰)
Looper
1、內(nèi)部包含一個消息對列,也就是MessageQueue,所有的handler發(fā)送的消息都走向這個消息對列。
2、Loopler.looper方法,就是一個死循環(huán),不斷地從MessageQueue取消息,如果有消息就處理消息,沒有消息就阻塞
二、MessageQueue,就是一個消息對列,可以添加消息,并處理消息
三、handler也很簡單,內(nèi)部會跟Looper進(jìn)行關(guān)聯(lián),也就是說在handler的內(nèi)部可以找到Looper,找到Looper也就找到了MessageQueue,在handler中發(fā)送消息,其實(shí)就是向MessageQueue隊(duì)列中發(fā)送消息
總結(jié):handler負(fù)責(zé)發(fā)送消息,Looper負(fù)責(zé)接收handler發(fā)送的消息,并直接把消息回傳給handler自己。MessageQueue就是一個存儲消息的容器
7、如何實(shí)現(xiàn)一個與線程相關(guān)的Handler?
打印的log日志如下:
8、HandlerThread又是什么?
當(dāng)主線程handler中傳入子線程的looper時,程序直接奔潰,報(bào)空指針錯誤,原因時存在多線程并發(fā)的問題,當(dāng)兩個線程在切換的時候,在程序運(yùn)行到主線程中handler的創(chuàng)建時,傳入了子線程的looper,而此時子線程中的looper還沒有創(chuàng)建出來,所以會拋出空指針異常,那么這個問題怎么避免呢,就用到了HandlerThread。報(bào)錯信息見下圖:
HandlerThread用法:
通過HandlerThread的getLooper()方法可以很好地解決多線程并發(fā)產(chǎn)生的空指針異常的問題,日志打印如下:
1)、HandlerThread的getLooper()源碼:
2)、HandlerThread的run()方法源碼:
通過兩段源碼可以發(fā)現(xiàn),當(dāng)mLooper為null時,wait等待,什么時候結(jié)束等待,喚醒呢?看HandlerThread的run()方法,先調(diào)用Looper.prepare(),然后通過Looper.myLooper()方法,把值給mLooper,再通過notifyAll()方法喚醒在等待的當(dāng)前線程的對象,這時handler就能拿到looper對象,就不會再報(bào)空指針異常的問題。
9、如何在主線程給子線程發(fā)送消息呢?
Log打印的日志如下,實(shí)現(xiàn)子線程和主線程之間相互交替的執(zhí)行異步任務(wù)。
10、android中更新UI的幾種方式?
(1)、runOnUiThread(runnable)
(2)、handler.post(runnable)
(3)、handler.sendMessage
(4)、view.post(runnable)
11、非UI線程真的不能更新UI嗎?
在onCreate方法里創(chuàng)建子線程,在子線程中直接更新UI也是可以的,因?yàn)閂iewRootImpl是在onResum方法中創(chuàng)建的,在調(diào)用checkThread方法前ViewRootImpl還沒有創(chuàng)建,不檢查當(dāng)前線程是不是main線程,所以不報(bào)錯,可以更新UI,但是一般不建議這樣做。當(dāng)線程休眠1秒后,再直接在子線程中更新UI 就會直接報(bào)錯。
代碼演示:
xml布局文件:
代碼中直接在子線程中更新UI:
更新成功界面截圖:
線程休眠1秒后,再直接在子線程中更新UI ,就會直接報(bào)錯,代碼如下:
12、使用handler時遇到的問題?
(1)、子線程中更新UI
報(bào)錯日志:
報(bào)錯源碼:
(2)、子線程中創(chuàng)建handler
報(bào)錯日志:
錯誤源碼: