Android消息機(jī)制原理深入分析
1.消息機(jī)制原理的解釋
在主線程里創(chuàng)建一個(gè)Handler,然后在分線程中引用這個(gè)Handler來發(fā)送Message對(duì)象給MessageQueue,循環(huán)器Looper從MessageQueue里面取出一個(gè)需要處理的Message,交給Handler處理,一般是進(jìn)行UI處理。處理完之后,Message就沒有太大的用處,Looper清理Message,讓Message回到默認(rèn)狀態(tài)。

2.Android的消息機(jī)制概述
Handler的背景(三個(gè)常見問題)
(1)Android為什么要提供Handler?
這是因?yàn)锳ndroid規(guī)定訪問UI只能在主線程中進(jìn)行,如果在子線程中訪問UI,那么程序就會(huì)拋出異常,但是Android又建議不要在主線程中進(jìn)行耗時(shí)操作,否則會(huì)導(dǎo)致線程無法響應(yīng)即ANR,比如我們需要從服務(wù)器拉取一些信息并將其顯示在UI上,這個(gè)時(shí)候必須在子線程中進(jìn)行拉取工作,拉取完畢后,不能在子線程上直接訪問UI,這時(shí)候通過Handler就可以將訪問UI的操作切換到主線程去執(zhí)行。
(2)系統(tǒng)為什么不允許在子線程中訪問UI呢?
這個(gè)因?yàn)锳ndroid的UI控件并不是線程安全的,如果在多線程中并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)。
(3)為什么系統(tǒng)不對(duì)UI控件的訪問加上鎖機(jī)制呢?
缺點(diǎn)有兩個(gè):
- 加上鎖機(jī)制會(huì)讓UI訪問的邏輯變得復(fù)雜
- 鎖機(jī)制會(huì)降低UI訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行
鑒于這兩個(gè)缺點(diǎn),最簡單且高效的方法就是采用單線程模型來處理UI操作。
Handler的工作原理的解釋
子線程默認(rèn)沒有Looper的,如果需要使用Handler,那么在Handler創(chuàng)建時(shí)就需要為線程創(chuàng)建Looper,利用Looper來構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng),如果當(dāng)前線程沒有Looper,那么就會(huì)報(bào)錯(cuò)。Handler創(chuàng)建完畢之后,Handler通過post方法把一個(gè)Runnable傳到Looper中去處理,或者通過send方法發(fā)送消息,Looper會(huì)調(diào)用MessageQueue的enqueueMessage方法將消息發(fā)入消息隊(duì)列中,然后Looper不斷循環(huán)發(fā)現(xiàn)需要處理的消息之后,就會(huì)調(diào)用消息中的Runnable或者或者Handler的handleMessage方法,這樣一來,Handler中的業(yè)務(wù)邏輯就被切除到創(chuàng)建Handler所在的線程中去執(zhí)行。

3.消息機(jī)制的分析
1.了解Message
可理解為線程間通訊的數(shù)據(jù)單元,可通過message攜帶需要的數(shù)據(jù)
創(chuàng)建Message的方法
val message:Message= Message() val message1:Message=Message.obtain()//它利用了Message中消息池(sPool)
封裝數(shù)據(jù):
int what:標(biāo)識(shí)
int arg1:保存int數(shù)據(jù)
int arg2:保存int數(shù)據(jù)
Object obj:保存任意時(shí)刻的數(shù)據(jù)
Long when :記錄應(yīng)該被處理的時(shí)間值,若為即時(shí)消息,時(shí)間值=發(fā)送時(shí)間,若為延時(shí)消息,時(shí)間值=發(fā)送時(shí)間+延遲時(shí)間
Handler target:用來處理消息的Handler對(duì)象,就是發(fā)送消息的Handler
Runnable callback:用來處理消息的回調(diào)器
Message next:指向下一個(gè)Message用來形成一個(gè)鏈表
Message sPool:用來緩存處理過的Message,以便復(fù)用
2.了解Handler
Handler是Message的處理器,同時(shí)也負(fù)責(zé)消息的發(fā)送和移除的工作
(1)Handler的構(gòu)造方法
Android API 30以上,使用Handler()方法時(shí)會(huì)顯示刪除線,并提示相關(guān)的方法已經(jīng)被棄用,不建議使用。如圖所示:

但是安卓不是棄用Handler這個(gè)類,而只是棄用Handler的兩個(gè)構(gòu)造方法:
Handler() Handler(callback:Handler.Callback)
安卓建議采用如下方法來解決
1.使用Executor
2.明確指定Looper
3.使用Looper.getMainLooper()定位并使用主線程的Looper
4.如果又想在其他線程,又想要不出bug,請(qǐng)使用Handler(looper:Looper)或者Handler(Looper.myLooper())這兩個(gè)構(gòu)造方法。
(2) Handler導(dǎo)致的內(nèi)存泄漏問題
在Android中最常用的一種內(nèi)存泄漏是Handler導(dǎo)致的泄漏,原因:
(1)在Activity被摧毀時(shí),延遲消息還沒發(fā)出,Handler可能有未執(zhí)行完或者正在執(zhí)行的Message,MessagesQueue就會(huì)持有這個(gè)消息的引用,導(dǎo)致Handler持有Activity的引用,進(jìn)而導(dǎo)致GC無法回收Activity。
(2)Handler中有還沒執(zhí)行完的Message,還在運(yùn)行,而運(yùn)行中的子線程不會(huì)被回收,所以就導(dǎo)致了內(nèi)存泄漏。
解決方法:
1.在Activity的onDestroy()方法中,清空Handler中的未執(zhí)行或者正在執(zhí)行的Message和Callbacks
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}2.static+弱引用
class MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {//處理消息的回調(diào)方法
myWeakReference.get()?.run {
...
}
}
}(3)Handler的常用方法:
- 發(fā)送即時(shí)消息:sendMessage(msg Message)
- 發(fā)送延時(shí)消息:sendMessageDelayed(msg Message,delayMillis Long)
- 處理方法:handleMessage(msg Message)(回調(diào)方法)
- 移除還未處理的消息:removeMessages(what int)
部分源碼:
由下面的源碼可以看出,這些方法的本質(zhì)是調(diào)用了queue.enqueueMessage(msg,uptimeMillis)方法。
sendMessage(Message msg)
->sendMessageDelayed(msg,0)
sendEmptyMessage(int what)
->sendEmptyMessageDelayed(what,0)
sendEmptyMessageDelayed(what,0)//發(fā)送不帶數(shù)據(jù)的消息
->sendMessageDelayed(msg,delayMillis)
sendMessageDelayed(Message msg,long delayMillis)
->sendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis)
sendMessageAtTime(Message msg,long uptimeMillis)
->enaueueMessage(queue,msg,uptimeMillis)//將消息添加到消息隊(duì)列中
enaueueMessage(MessageQueue queue,Message msg,long uptimeMillis)
->queue.enqueueMessage(msg,uptimeMillis)//調(diào)用消息隊(duì)列保存消息對(duì)象
removeMessage(int what)//移除消息
->mQueue.removeMessage(this,what,null)//調(diào)用消息隊(duì)列移除它內(nèi)部的指定what消息
handleMessage(Message msg)//處理消息的回調(diào)方法3.消息隊(duì)列的工作原理
MessageQueue消息隊(duì)列,負(fù)責(zé)入隊(duì)和出隊(duì),儲(chǔ)存Handler發(fā)送的消息,它是一個(gè)按Message的when的排序的優(yōu)先隊(duì)列。
雖然MessageQueue叫消息隊(duì)列,但是它內(nèi)部是用鏈表來實(shí)現(xiàn)的。
MessageQueue主要包含兩個(gè)操作:插入和讀取,讀取操作本身會(huì)伴隨刪除操作,插入和讀取對(duì)應(yīng)的方法分別為enqueueMessage和next:
boolean enqueueMessage(Message msg, long when):往消息隊(duì)列中插入一條消息
Message next() :消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除
enqueueMessage和 next方法的部分源碼:
enqueueMessage的主要操作就是單鏈表的插入操作
boolean enqueueMessage(Message msg, long when) {//將Messages插入消息隊(duì)列
...
msg.when = when;//指定消息應(yīng)該被處理的時(shí)間
...
for (;;) {//將當(dāng)前消息對(duì)象保存到消息隊(duì)列中的一個(gè)合適的位置
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}//最終的結(jié)果是:消息隊(duì)列是按when來排序的
...
nativeWake(mPtr);//通過本地方法實(shí)現(xiàn)對(duì)處理等待狀態(tài)的底層線程
...
}next方法是一個(gè)無限循環(huán)的方法。如果消息隊(duì)列中沒有消息,那么next方法可以阻塞在這里,
當(dāng)有新的消息到來時(shí),next方法會(huì)返回這條消息并將其中單鏈表中移除。
Message next() {//取出一個(gè)合適的Message對(duì)象,可能不會(huì)立刻返回
...
nativePollOnce(ptr, nextPollTimeoutMillis);//本地方法,會(huì)導(dǎo)致可能處理等待狀態(tài),但不會(huì)阻塞主線程
...
Message msg = mMessages;//取出消息隊(duì)列中的第一個(gè)消息
...
return msg;//返回
...
}4.Looper的工作原理
Looper在Android的消息機(jī)制中扮演著消息循環(huán)的角色。
Looper為一個(gè)線程開啟一個(gè)消息循環(huán),創(chuàng)建MessageQueue,負(fù)責(zé)循環(huán)取出Message Queue里面的當(dāng)前需要處理的Message,也就是說,它會(huì)一直不停地從MessageQueue中查看是否會(huì)有新消息,如果有新消息就會(huì)交給對(duì)應(yīng)的Handler進(jìn)行處理,處理完后,將Message緩存到消息池中以備復(fù)用,否則就一直阻塞在那里,Looper退出后,Handler發(fā)送消息會(huì)失敗,線程會(huì)立刻終止。
常用方法:?
Looper.prepare():為當(dāng)前線程創(chuàng)建一個(gè)Looper。
Looper.loop():開啟消息循環(huán).
Looper.getMainLooper():獲取主線程的Looper。
Looper.quit()直接推遲Looper
Looper.quitSafely()設(shè)定一個(gè)退出標(biāo)記,然后把消息隊(duì)列中的已有消息處理完畢才安全地退出。
如何為一個(gè)線程創(chuàng)建Looper?
thread {
Looper.prepare()
val handler:Handler=MyHandler(this)
Looper.loop()
}loop方法的部分源碼:
public static void loop() {
final Looper me = myLooper();//得到looper對(duì)象
...
for (;;) {//無限循環(huán)
...
Message msg = me.mQueue.next(); // 從消息隊(duì)列中取出消息
...
msg.target.dispatchMessage(msg);//調(diào)用Handler去分發(fā)并處理消息
...
msg.recycleUnchecked();//回收利用Message
...
}
}4.Handler使用(DEMO)
功能描述:
1.初始時(shí)
顯示10,可以通過點(diǎn)擊按鈕改變其值
2.點(diǎn)擊自動(dòng)增加
每隔一秒上面的文本數(shù)值增加1,但最大顯示20并作出提示
3.點(diǎn)擊自動(dòng)減少
每隔一秒上面的文本數(shù)值減少1,但是最小顯示1并作出提示
4.點(diǎn)擊暫停
上面的數(shù)值文本不再變化
效果圖:

代碼如下:
class HandlerActivity : AppCompatActivity() {
lateinit var handler: Handler
lateinit var number:TextView
lateinit var increase:Button
lateinit var decrease:Button
lateinit var pause:Button
//static+弱引用
class MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {//處理消息的回調(diào)方法
myWeakReference.get()?.run {
var numberint:Int=Integer.parseInt(number.text.toString())
when(msg.what){
1->{
if(numberint==20){
Toast.makeText(this,"已經(jīng)達(dá)到最大值",Toast.LENGTH_SHORT).show()
return
}
numberint++
number.text= numberint.toString()
handler.sendEmptyMessageDelayed(1,1000)
}
2->{
if(numberint==1){
Toast.makeText(this,"已經(jīng)達(dá)到最小值",Toast.LENGTH_SHORT).show()
return
}
numberint--
number.text=numberint.toString()
handler.sendEmptyMessageDelayed(2,1000)
}
else -> {}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
number=findViewById(R.id.number)
increase=findViewById(R.id.increase)
decrease=findViewById(R.id.decrease)
pause=findViewById(R.id.pause)
handler=MyHandler(this)
increase.setOnClickListener {
increase.isEnabled=false
decrease.isEnabled=true
pause.isEnabled=true
handler.removeMessages(2)
handler.sendEmptyMessage(1)
}
decrease.setOnClickListener {
increase.isEnabled=true
decrease.isEnabled=false
pause.isEnabled=true
handler.removeMessages(1)
handler.sendEmptyMessage(2)
}
pause.setOnClickListener {
increase.isEnabled=true
decrease.isEnabled=true
pause.isEnabled=false
handler.removeMessages(1)
handler.removeMessages(2)
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
}到此這篇關(guān)于Android消息機(jī)制原理深入分析的文章就介紹到這了,更多相關(guān)Android消息機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用Android中BitmapShader制作自帶邊框的圓形頭像
這篇文章給大家介紹了一下如何利用BitmapShader制作圓形頭像,可以自定義要顯示的圖片,邊框顏色和邊框?qū)挾鹊?,有需要的朋友們可以參考借鑒。2016-09-09
Android 圖片存入系統(tǒng)相冊更新顯示實(shí)例詳解
這篇文章主要介紹了Android 圖片存入系統(tǒng)相冊更新顯示實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
Kotlin數(shù)據(jù)存儲(chǔ)方式全面總結(jié)講解
在開發(fā)過程中,數(shù)據(jù)存取是較為頻繁的,今天我們來了解下android幾種常見的數(shù)據(jù)存取方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-12-12
基于adbkit的android設(shè)備管理(精簡版stf)
這篇文章主要為大家介紹了基于adbkit的android設(shè)備管理(精簡版stf)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Android實(shí)現(xiàn)為Notification加上一個(gè)進(jìn)度條的方法
這篇文章主要介紹了Android實(shí)現(xiàn)為Notification加上一個(gè)進(jìn)度條的方法,結(jié)合實(shí)例形式分析了Android針對(duì)Notification組件的相關(guān)操作技巧,需要的朋友可以參考下2016-10-10
Android界面設(shè)計(jì)(APP設(shè)計(jì)趨勢 左側(cè)隱藏菜單右邊顯示content)
這文章講述了2013年未來的移動(dòng)APP設(shè)計(jì)趨勢,感覺挺有道理的:Android界面設(shè)計(jì)實(shí)現(xiàn)左側(cè)隱藏菜單右邊顯示content,感興趣的你可以了解下啊,希望本文對(duì)你的APP設(shè)計(jì)提高有所幫助哦2013-01-01
Android Studio連接手機(jī)設(shè)備教程
這篇文章主要為大家詳細(xì)介紹了Android Studio連接手機(jī)設(shè)備教程,非常完整的連接步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android編程實(shí)現(xiàn)等比例顯示圖片的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)等比例顯示圖片的方法,實(shí)例分析了Android等比例縮放圖片的具體步驟與相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度
本篇文章主要介紹了Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07

