本文主要包含:
1轻掩、推薦主線程更新UI的原因。(Android單線程模型)
2懦底、消息處理機制的原理唇牧。
3、非UI線程真的不能更新UI嗎?
在子線程中更新UI會報錯:Only the original thread that created a view hierarchy can touch its views.丐重,原因是ViewRootImpl的checkThread()方法做了檢查腔召,只有主線程才能更新UI。
子線程不能更新UI的原因:UI線程不是線程安全的扮惦,多線程并發(fā)操作UI會造成UI混亂臀蛛;加鎖會造成效率低下⊙旅郏基于這兩點采用單線程處理UI操作浊仆,通過Handler切換進程。
消息分發(fā)機制就是消息的分發(fā)和處理過程豫领;簡單的說就是:handler作為消息的發(fā)送者和處理者抡柿,MessageQueen是消息的載體,Looper不斷從MessageQueen中取出消息交由Handler來處理氏堤。
我們平常使用消息處理機制來更新UI一般都是在主線程中new一個Handler重寫handleMessage方法沙绝,在該方法中做更新UI的操作。在使用Handler發(fā)送消息之前必須要looper.prepare(),否則會拋出異常鼠锈。下面分析一下消息的分發(fā)和處理:
new一個Handler分為在主線程和子線程創(chuàng)建Handler實例,如果在子線程中創(chuàng)建Handler實例星著,必須在之前調(diào)用Looper的prepare方法創(chuàng)建一個Looper购笆,否則會報錯。原因是:
在主線程不需要調(diào)用Looper的prepare的原因是:在程序啟動的時候虚循,ActivityThread為我們創(chuàng)建了Looper同欠,并調(diào)用了loop()方法。ActivityThread的main()方法中調(diào)用了Looper.prepareMainLooper()和Looper.loop();因此不需要我們自己手動調(diào)用横缔。
Looper:
prepare():先從ThreadLocal中取looper铺遂,如果存在looper就會拋出異常,保證一個線程只有一個looper實例(prepare方法只允許執(zhí)行一次)茎刚;創(chuàng)建一個looper實例放入ThreadLocal中襟锐;在構造Looper實例的時候,會創(chuàng)建一個MessageQueen和獲得當前線程膛锭。
Looper的prepare方法:
![LZU5GO3X$YIJX2G~0E0]JLH.png](http://upload-images.jianshu.io/upload_images/2578759-f1f9e026c98dd136.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Looper的構造方法:
接下來是Handler的sendMessage()和post()方法:
sendMessage()方法最終會調(diào)用enqueueMessage(),將發(fā)送的消息插入到MessageQueue中:
Handler的post(new Runnable()):實際上和sendMessage方法是相同的粮坞,只是將runnable賦值給了message的callBack對象。
![X6M]D%ZJHBVSSJ5GKM496FG.png](http://upload-images.jianshu.io/upload_images/2578759-8c350485fd8e0702.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
接下來是最重要的方法loop():首先拿到當前線程的Looper實例和該Looper對應的MessageQueen初狰;然后進入無限循環(huán)莫杈,不斷的從MessageQueen中取出消息,如果消息為空就阻塞等待奢入,如果消息不為空就通過meg.target.dispatchMessage(msg)將該消息交給handler處理(這時才是真正的消息處理階段)筝闹。
接下來看一下handler的dispatchMessage方法:最終消息交由runnable的run方法直接執(zhí)行或者handleMessage()方法進行處理。
![IDH}8]O2WKHY}G{FMQ_@_J2.png](http://upload-images.jianshu.io/upload_images/2578759-9299c4785dab7f02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后再來看一下Activity中的runOnUiThread()方法:如果當前的線程不等于UI線程(主線程),就去調(diào)用Handler的post()方法关顷,否則就直接調(diào)用Runnable對象的run()方法糊秆。
![4%NAKOD2EG3`0~$]DYMQJ@K.png](http://upload-images.jianshu.io/upload_images/2578759-9b7cb1d852ff18ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
非UI線程真的不能更新UI嗎?其實在特殊情況下是可以更新UI的解寝,比如在onCreate方法中開啟子線程更新UI就不會報錯扩然,原因是checkThread方法是在onResume之后執(zhí)行的,在onResume之前ViewRootImpl還沒有創(chuàng)建完成聋伦。但是我們通常不在onCreate中開啟子線程做更新UI的操作夫偶。
更新UI的操作都是調(diào)用View的invalidate。在View的invalidate里面有這樣一段代碼:
![8Q0GR~D]}AKSZ_2@O0GQ%KV.png](http://upload-images.jianshu.io/upload_images/2578759-36b26cbe8963313c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
ViewParent是一個接口觉增,ViewRootImpl是ViewParent的具體實現(xiàn)兵拢,p.invalidateChild(this, damage)就是調(diào)用的ViewRootImpl的invalidateChild方法,該方法會調(diào)用checkThread方法逾礁,就是用于檢查更新UI的操作是否是在主線程说铃,如果不是在主線程就會拋出那個常見的異常。
在onCreate方法中開啟子線程更新UI不報異常的原因就是在onCreate時ViewRootImpl還沒有創(chuàng)建完成嘹履,不會調(diào)用checkThread方法腻扇。
看一下ActivityThread的handleResumeActivity方法,該方法里面有一個performResumeActivity方法砾嫉,這個方法最終會回調(diào)Activity的onResume方法幼苛。而ViewRootImpl又是在什么時候創(chuàng)建的呢?handleResumeActivity中會調(diào)用windManager的addView方法焕刮,WindowManagerImpl是WindowManager的具體實現(xiàn)類舶沿,其中的addView方法會調(diào)用WindowManagerGlobal的addView方法,而ViewRoopImpl就是在此時創(chuàng)建的配并±ǖ矗總是ViewRootImpl的創(chuàng)建是在onResume方法之后,這就解釋了在onCreate開啟子線程更新UI不會出錯的原因溉旋。