摘要:通過向消息池發送各種消息事件通過處理相應的消息事件。子線程往消息隊列發送消息,并且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。
目錄介紹
6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的?
6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handler?
6.0.0.3 可以在子線程直接new一個Handler嗎?會出現什么問題,那該怎么做?
6.0.0.4 Looper.prepare()能否調用兩次或者多次,會出現什么情況?
6.0.0.5 為什么系統不建議在子線程訪問UI,不對UI控件的訪問加上鎖機制的原因?
6.0.0.6 如何獲取當前線程的Looper?是怎么實現的?(理解ThreadLocal)
6.0.0.7 Looper.loop是一個死循環,拿不到需要處理的Message就會阻塞,那在UI線程中為什么不會導致ANR?
6.0.0.8 Handler.sendMessageDelayed()怎么實現延遲的?結合Looper.loop()循環中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。
6.0.0.9 Message可以如何創建?哪種效果更好,為什么?
6.0.1.3 使用Hanlder的postDealy()后消息隊列會發生什么變化?
6.0.1.4 ThreadLocal有什么作用?
好消息博客筆記大匯總【15年10月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!所有的筆記將會更新到GitHub上,同時保持更新,歡迎同行提出或者push不同的看法或者筆記!
6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的?
作用:
跨線程通信。當子線程中進行耗時操作后需要更新UI時,通過Handler將有關UI的操作切換到主線程中執行。
四要素:
Message(消息):需要被傳遞的消息,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,最終由Handler處理。技術博客大總結
MessageQueue(消息隊列):用來存放Handler發送過來的消息,內部通過單鏈表的數據結構來維護消息列表,等待Looper的抽取。
Handler(處理者):負責Message的發送及處理。通過 Handler.sendMessage() 向消息池發送各種消息事件;通過 Handler.handleMessage() 處理相應的消息事件。
Looper(消息泵):通過Looper.loop()不斷地從MessageQueue中抽取Message,按分發機制將消息分發給目標處理者。
具體流程
Handler.sendMessage()發送消息時,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;
通過Looper.loop()開啟循環后,不斷輪詢調用MessageQueue.next();
調用目標Handler.dispatchMessage()去傳遞消息,目標Handler收到消息后調用Handler.handlerMessage()處理消息。
6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handler?
注意:一個Thread只能有一個Looper,可以有多個Handler
Looper有一個MessageQueue,可以處理來自多個Handler的Message;MessageQueue有一組待處理的Message,這些Message可來自不同的Handler;Message中記錄了負責發送和處理消息的Handler;Handler中有Looper和MessageQueue。
為什么一個線程只有一個Looper?技術博客大總結
需使用Looper的prepare方法,Looper.prepare()。可以看下源代碼,Android中一個線程最多僅僅能有一個Looper,若在已有Looper的線程中調用Looper.prepare()會拋出RuntimeException(“Only one Looper may be created per thread”)。
所以一個線程只有一個Looper,不知道這樣解釋是否合理!更多可以查看我的博客匯總:https://github.com/yangchong2...
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }6.0.0.3 可以在子線程直接new一個Handler嗎?會出現什么問題,那該怎么做?
不同于主線程直接new一個Handler,由于子線程的Looper需要手動去創建,在創建Handler時需要多一些方法:
Handler的工作是依賴于Looper的,而Looper(與消息隊列)又是屬于某一個線程(ThreadLocal是線程內部的數據存儲類,通過它可以在指定線程中存儲數據,其他線程則無法獲取到),其他線程不能訪問。因此Handler就是間接跟線程是綁定在一起了。因此要使用Handler必須要保證Handler所創建的線程中有Looper對象并且啟動循環。因為子線程中默認是沒有Looper的,所以會報錯。
正確的使用方法是:技術博客大總結
handler = null; new Thread(new Runnable() { private Looper mLooper; @Override public void run() { //必須調用Looper的prepare方法為當前線程創建一個Looper對象,然后啟動循環 //prepare方法中實質是給ThreadLocal對象創建了一個Looper對象 //如果當前線程已經創建過Looper對象了,那么會報錯 Looper.prepare(); handler = new Handler(); //獲取Looper對象 mLooper = Looper.myLooper(); //啟動消息循環 Looper.loop(); //在適當的時候退出Looper的消息循環,防止內存泄漏 mLooper.quit(); } }).start();
主線程中默認是創建了Looper并且啟動了消息的循環的,因此不會報錯:應用程序的入口是ActivityThread的main方法,在這個方法里面會創建Looper,并且執行Looper的loop方法來啟動消息的循環,使得應用程序一直運行。
6.0.0.4 Looper.prepare()能否調用兩次或者多次,會出現什么情況?
Looper.prepare()方法源碼分析
可以看到Looper中有一個ThreadLocal成員變量,熟悉JDK的同學應該知道,當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
思考:Looper.prepare()能否調用兩次或者多次
如果運行,則會報錯,并提示prepare中的Excetion信息。由此可以得出在每個線程中Looper.prepare()能且只能調用一次
技術博客大總結
//這里Looper.prepare()方法調用了兩次 Looper.prepare(); Looper.prepare(); Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { Log.i(TAG, "在子線程中定義Handler,并接收到消息。。。"); } } }; Looper.loop();6.0.0.5 為什么系統不建議在子線程訪問UI,不對UI控件的訪問加上鎖機制的原因?
為什么系統不建議在子線程訪問UI
系統不建議在子線程訪問UI的原因是,UI控件非線程安全,在多線程中并發訪問可能會導致UI控件處于不可預期的狀態。
不對UI控件的訪問加上鎖機制的原因
上鎖會讓UI控件變得復雜和低效
上鎖后會阻塞某些進程的執行技術博客大總結
6.0.0.7 Looper.loop是一個死循環,拿不到需要處理的Message就會阻塞,那在UI線程中為什么不會導致ANR?
問題描述
在處理消息的時候使用了Looper.loop()方法,并且在該方法中進入了一個死循環,同時Looper.loop()方法是在主線程中調用的,那么為什么沒有造成阻塞呢?
ActivityThread中main方法
ActivityThread類的注釋上可以知道這個類管理著我們平常所說的主線程(UI線程)
首先 ActivityThread 并不是一個 Thread,就只是一個 final 類而已。我們常說的主線程就是從這個類的 main 方法開始,main 方法很簡短
public static final void main(String[] args) { ... //創建Looper和MessageQueue Looper.prepareMainLooper(); ... //輪詢器開始輪詢 Looper.loop(); ... }
Looper.loop()方法無限循環
看看Looper.loop()方法無限循環部分的代碼
while (true) { //取出消息隊列的消息,可能會阻塞 Message msg = queue.next(); // might block ... //解析消息,分發消息 msg.target.dispatchMessage(msg); ... }
為什么這個死循環不會造成ANR異常呢?
因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。技術博客大總結
處理消息handleMessage方法
如下所示
可以看見Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應措施。
如果某個消息處理時間過長,比如你在onCreate(),onResume()里面處理耗時操作,那么下一次的消息比如用戶的點擊事件不能處理了,整個循環就會產生卡頓,時間一長就成了ANR。
public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord) msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case PAUSE_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case PAUSE_ACTIVITY_FINISHING: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; ........... } }
loop的循環消耗性能嗎?
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環并不會對CPU性能有過多的消耗。
簡單的來說:ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那么你的程序也就可以退出了。
6.0.0.9 Message可以如何創建?哪種效果更好,為什么?runOnUiThread如何實現子線程更新UI?
創建Message對象的幾種方式:技術博客大總結
Message msg = new Message();
Message msg = Message.obtain();
Message msg = handler1.obtainMessage();
后兩種方法都是從整個Messge池中返回一個新的Message實例,能有效避免重復Message創建對象,因此更鼓勵這種方式創建Message
runOnUiThread如何實現子線程更新UI
看看源碼,如下所示
如果msg.callback為空的話,會直接調用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對象是在主線程中創建的,所以handler的handlerMessage方法的執行也會在主線程中。
在runOnUiThread程序首先會判斷當前線程是否是UI線程,如果是就直接運行,如果不是則post,這時其實質還是使用的Handler機制來處理線程與UI通訊。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } @Override public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }6.0.1.3 使用Hanlder的postDealy()后消息隊列會發生什么變化?
post delay的Message并不是先等待一定時間再放入到MessageQueue中,而是直接進入并阻塞當前線程,然后將其delay的時間和隊頭的進行比較,按照觸發時間進行排序,如果觸發時間更近則放入隊頭,保證隊頭的時間最小、隊尾的時間最大。此時,如果隊頭的Message正是被delay的,則將當前線程堵塞一段時間,直到等待足夠時間再喚醒執行該Message,否則喚醒后直接執行。
6.0.1.4 ThreadLocal有什么作用?
線程本地存儲的功能
ThreadLocal類可實現線程本地存儲的功能,把共享數據的可見范圍限制在同一個線程之內,無須同步就能保證線程之間不出現數據爭用的問題,這里可理解為ThreadLocal幫助Handler找到本線程的Looper。
技術博客大總結
怎么存儲呢?底層數據結構是啥?
每個線程的Thread對象中都有一個ThreadLocalMap對象,它存儲了一組以ThreadLocal.threadLocalHashCode為key、以本地線程變量為value的鍵值對,而ThreadLocal對象就是當前線程的ThreadLocalMap的訪問入口,也就包含了一個獨一無二的threadLocalHashCode值,通過這個值就可以在線程鍵值值對中找回對應的本地線程變量。
關于其他內容介紹 01.關于博客匯總鏈接1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org, www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
掘金:https://juejin.im/user/593943...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72956.html
摘要:異步任務必須指定回調函數,當異步任務從任務隊列回到執行棧,回調函數就會執行。事件循環主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為。事件循環事件循環是指主線程重復從消息隊列中取消息執行的過程。 參考鏈接:這一次,徹底弄懂 JavaScript 執行機制https://zhuanlan.zhihu.com/p/...從瀏覽器多進程到JS單線程,JS運行機制...
摘要:文章鏈接之事件驅動機制的簡單使用之事件驅動機制的簡單使用關于事件的發起與相應,在客戶端的交互中可算是非常頻繁的事情了,關于事件的發布訂閱,在生態中,可謂是非常有名了,而也提供了事件機制,本文則主要介紹后端如何在的環境中,使用事件機制使用姿 showImg(https://segmentfault.com/img/remote/1460000015237252); 文章鏈接:https:...
摘要:微軟的雖然引入了事件機制,可以在隊列收到消息時觸發事件,通知訂閱者。由微軟作為主要貢獻者的,則對以及做了進一層包裝,并能夠很好地實現這一模式。 在分布式服務框架中,一個最基礎的問題就是遠程服務是怎么通訊的,在Java領域中有很多可實現遠程通訊的技術,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等,這些名詞之間到底是些什么關系呢,它們背后到底是基...
摘要:微軟的雖然引入了事件機制,可以在隊列收到消息時觸發事件,通知訂閱者。由微軟作為主要貢獻者的,則對以及做了進一層包裝,并能夠很好地實現這一模式。 在分布式服務框架中,一個最基礎的問題就是遠程服務是怎么通訊的,在Java領域中有很多可實現遠程通訊的技術,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等,這些名詞之間到底是些什么關系呢,它們背后到底是基...
閱讀 3893·2021-11-17 09:33
閱讀 1201·2021-10-09 09:44
閱讀 404·2019-08-30 13:59
閱讀 3483·2019-08-30 11:26
閱讀 2185·2019-08-29 16:56
閱讀 2856·2019-08-29 14:22
閱讀 3154·2019-08-29 12:11
閱讀 1278·2019-08-29 10:58