目錄介紹
- 6.0.0.1 談談消息機制Hander作用弃衍?有哪些要素?流程是怎樣的坚俗?
- 6.0.0.2 為什么一個線程只有一個Looper镜盯、只有一個MessageQueue,可以有多個Handler猖败?
- 6.0.0.3 可以在子線程直接new一個Handler嗎速缆?會出現(xiàn)什么問題,那該怎么做恩闻?
- 6.0.0.4 Looper.prepare()能否調用兩次或者多次艺糜,會出現(xiàn)什么情況?
- 6.0.0.5 為什么系統(tǒng)不建議在子線程訪問UI幢尚,不對UI控件的訪問加上鎖機制的原因破停?
- 6.0.0.6 如何獲取當前線程的Looper?是怎么實現(xiàn)的侠草?(理解ThreadLocal)
- 6.0.0.7 Looper.loop是一個死循環(huán)辱挥,拿不到需要處理的Message就會阻塞,那在UI線程中為什么不會導致ANR边涕?
- 6.0.0.8 Handler.sendMessageDelayed()怎么實現(xiàn)延遲的晤碘?結合Looper.loop()循環(huán)中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析功蜓。
- 6.0.0.9 Message可以如何創(chuàng)建园爷?哪種效果更好,為什么式撼?
- 6.0.1.3 使用Hanlder的postDealy()后消息隊列會發(fā)生什么變化童社?
- 6.0.1.4 ThreadLocal有什么作用?
好消息
- 博客筆記大匯總【15年10月到至今】著隆,包括Java基礎及深入知識點扰楼,Android技術博客呀癣,Python學習筆記等等,還包括平時開發(fā)中遇到的bug匯總弦赖,當然也在工作之余收集了大量的面試題项栏,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的蹬竖!同時也開源了生活博客沼沈,從12年起,積累共計500篇[近100萬字]币厕,將會陸續(xù)發(fā)表到網上列另,轉載請注明出處,謝謝旦装!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好页衙,可以star一下,謝謝阴绢!當然也歡迎提出建議拷姿,萬事起于忽微,量變引起質變旱函!所有的筆記將會更新到GitHub上响巢,同時保持更新,歡迎同行提出或者push不同的看法或者筆記棒妨!
6.0.0.1 談談消息機制Hander作用踪古?有哪些要素?流程是怎樣的券腔?
- 作用:
- 跨線程通信伏穆。當子線程中進行耗時操作后需要更新UI時,通過Handler將有關UI的操作切換到主線程中執(zhí)行纷纫。
- 四要素:
- Message(消息):需要被傳遞的消息枕扫,其中包含了消息ID,消息處理對象以及處理的數(shù)據(jù)等辱魁,由MessageQueue統(tǒng)一列隊烟瞧,最終由Handler處理。技術博客大總結
- MessageQueue(消息隊列):用來存放Handler發(fā)送過來的消息染簇,內部通過單鏈表的數(shù)據(jù)結構來維護消息列表参滴,等待Looper的抽取。
- Handler(處理者):負責Message的發(fā)送及處理锻弓。通過 Handler.sendMessage() 向消息池發(fā)送各種消息事件砾赔;通過 Handler.handleMessage() 處理相應的消息事件。
- Looper(消息泵):通過Looper.loop()不斷地從MessageQueue中抽取Message,按分發(fā)機制將消息分發(fā)給目標處理者暴心。
- 具體流程
- Handler.sendMessage()發(fā)送消息時妓盲,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;
- 通過Looper.loop()開啟循環(huán)后专普,不斷輪詢調用MessageQueue.next()本橙;
- 調用目標Handler.dispatchMessage()去傳遞消息,目標Handler收到消息后調用Handler.handlerMessage()處理消息脆诉。
- image
6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue贷币,可以有多個Handler击胜?
- 注意:一個Thread只能有一個Looper,可以有多個Handler
- Looper有一個MessageQueue役纹,可以處理來自多個Handler的Message偶摔;MessageQueue有一組待處理的Message,這些Message可來自不同的Handler促脉;Message中記錄了負責發(fā)送和處理消息的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/yangchong211/YCBlogs
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嗎尘奏?會出現(xiàn)什么問題滩褥,那該怎么做?
- 不同于主線程直接new一個Handler炫加,由于子線程的Looper需要手動去創(chuàng)建瑰煎,在創(chuàng)建Handler時需要多一些方法:
- Handler的工作是依賴于Looper的,而Looper(與消息隊列)又是屬于某一個線程(ThreadLocal是線程內部的數(shù)據(jù)存儲類俗孝,通過它可以在指定線程中存儲數(shù)據(jù)丢间,其他線程則無法獲取到),其他線程不能訪問驹针。因此Handler就是間接跟線程是綁定在一起了烘挫。因此要使用Handler必須要保證Handler所創(chuàng)建的線程中有Looper對象并且啟動循環(huán)。因為子線程中默認是沒有Looper的,所以會報錯饮六。
- 正確的使用方法是:技術博客大總結
handler = null; new Thread(new Runnable() { private Looper mLooper; @Override public void run() { //必須調用Looper的prepare方法為當前線程創(chuàng)建一個Looper對象其垄,然后啟動循環(huán) //prepare方法中實質是給ThreadLocal對象創(chuàng)建了一個Looper對象 //如果當前線程已經創(chuàng)建過Looper對象了,那么會報錯 Looper.prepare(); handler = new Handler(); //獲取Looper對象 mLooper = Looper.myLooper(); //啟動消息循環(huán) Looper.loop(); //在適當?shù)臅r候退出Looper的消息循環(huán)卤橄,防止內存泄漏 mLooper.quit(); } }).start();
- 主線程中默認是創(chuàng)建了Looper并且啟動了消息的循環(huán)的绿满,因此不會報錯:應用程序的入口是ActivityThread的main方法,在這個方法里面會創(chuàng)建Looper窟扑,并且執(zhí)行Looper的loop方法來啟動消息的循環(huán)喇颁,使得應用程序一直運行。
6.0.0.4 Looper.prepare()能否調用兩次或者多次嚎货,會出現(xiàn)什么情況橘霎?
- 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 為什么系統(tǒng)不建議在子線程訪問UI巧勤,不對UI控件的訪問加上鎖機制的原因嵌灰?
- 為什么系統(tǒng)不建議在子線程訪問UI
- 系統(tǒng)不建議在子線程訪問UI的原因是,UI控件非線程安全颅悉,在多線程中并發(fā)訪問可能會導致UI控件處于不可預期的狀態(tài)沽瞭。
- 不對UI控件的訪問加上鎖機制的原因
- 上鎖會讓UI控件變得復雜和低效
- 上鎖后會阻塞某些進程的執(zhí)行技術博客大總結
6.0.0.7 Looper.loop是一個死循環(huán),拿不到需要處理的Message就會阻塞剩瓶,那在UI線程中為什么不會導致ANR驹溃?
- 問題描述
- 在處理消息的時候使用了Looper.loop()方法,并且在該方法中進入了一個死循環(huán)延曙,同時Looper.loop()方法是在主線程中調用的豌鹤,那么為什么沒有造成阻塞呢?
- ActivityThread中main方法
- ActivityThread類的注釋上可以知道這個類管理著我們平常所說的主線程(UI線程)
- 首先 ActivityThread 并不是一個 Thread枝缔,就只是一個 final 類而已布疙。我們常說的主線程就是從這個類的 main 方法開始蚊惯,main 方法很簡短
public static final void main(String[] args) { ... //創(chuàng)建Looper和MessageQueue Looper.prepareMainLooper(); ... //輪詢器開始輪詢 Looper.loop(); ... }
- ActivityThread類的注釋上可以知道這個類管理著我們平常所說的主線程(UI線程)
- Looper.loop()方法無限循環(huán)
- 看看Looper.loop()方法無限循環(huán)部分的代碼
while (true) { //取出消息隊列的消息,可能會阻塞 Message msg = queue.next(); // might block ... //解析消息灵临,分發(fā)消息 msg.target.dispatchMessage(msg); ... }
- 看看Looper.loop()方法無限循環(huán)部分的代碼
- 為什么這個死循環(huán)不會造成ANR異常呢截型?
- 因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件儒溉、處理事件宦焦,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了顿涣,應用也就停止了波闹。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它涛碑。技術博客大總結
- 處理消息handleMessage方法
- 如下所示
- 可以看見Activity的生命周期都是依靠主線程的Looper.loop精堕,當收到不同Message時則采用相應措施。
- 如果某個消息處理時間過長锌唾,比如你在onCreate(),onResume()里面處理耗時操作,那么下一次的消息比如用戶的點擊事件不能處理了夺英,整個循環(huán)就會產生卡頓晌涕,時間一長就成了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的循環(huán)消耗性能嗎痛悯?
- 主線程Looper從消息隊列讀取消息余黎,當讀完所有消息時,主線程阻塞载萌。子線程往消息隊列發(fā)送消息惧财,并且往管道文件寫數(shù)據(jù),主線程即被喚醒扭仁,從管道文件讀取數(shù)據(jù)垮衷,主線程被喚醒只是為了讀取消息,當消息讀取完畢乖坠,再次睡眠搀突。因此loop的循環(huán)并不會對CPU性能有過多的消耗。
- 簡單的來說:ActivityThread的main方法主要就是做消息循環(huán)熊泵,一旦退出消息循環(huán)仰迁,那么你的程序也就可以退出了。
6.0.0.9 Message可以如何創(chuàng)建?哪種效果更好,為什么垢袱?runOnUiThread如何實現(xiàn)子線程更新UI辆脸?
- 創(chuàng)建Message對象的幾種方式:技術博客大總結
- Message msg = new Message();
- Message msg = Message.obtain();
- Message msg = handler1.obtainMessage();
- 后兩種方法都是從整個Messge池中返回一個新的Message實例,能有效避免重復Message創(chuàng)建對象喧锦,因此更鼓勵這種方式創(chuàng)建Message
- runOnUiThread如何實現(xiàn)子線程更新UI
- 看看源碼辆童,如下所示
- 如果msg.callback為空的話营密,會直接調用我們的mCallback.handleMessage(msg)澄步,即handler的handlerMessage方法冰蘑。由于Handler對象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會在主線程中村缸。
- 在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()后消息隊列會發(fā)生什么變化?
- post delay的Message并不是先等待一定時間再放入到MessageQueue中东羹,而是直接進入并阻塞當前線程剂桥,然后將其delay的時間和隊頭的進行比較,按照觸發(fā)時間進行排序属提,如果觸發(fā)時間更近則放入隊頭权逗,保證隊頭的時間最小、隊尾的時間最大冤议。此時斟薇,如果隊頭的Message正是被delay的,則將當前線程堵塞一段時間恕酸,直到等待足夠時間再喚醒執(zhí)行該Message堪滨,否則喚醒后直接執(zhí)行。
6.0.1.4 ThreadLocal有什么作用蕊温?
- 線程本地存儲的功能
- ThreadLocal類可實現(xiàn)線程本地存儲的功能袱箱,把共享數(shù)據(jù)的可見范圍限制在同一個線程之內,無須同步就能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題义矛,這里可理解為ThreadLocal幫助Handler找到本線程的Looper发笔。
- 技術博客大總結
- 怎么存儲呢?底層數(shù)據(jù)結構是啥凉翻?
- 每個線程的Thread對象中都有一個ThreadLocalMap對象筐咧,它存儲了一組以ThreadLocal.threadLocalHashCode為key、以本地線程變量為value的鍵值對噪矛,而ThreadLocal對象就是當前線程的ThreadLocalMap的訪問入口量蕊,也就包含了一個獨一無二的threadLocalHashCode值,通過這個值就可以在線程鍵值值對中找回對應的本地線程變量艇挨。
關于其他內容介紹
01.關于博客匯總鏈接
02.關于我的博客
- 我的個人站點:www.yczbj.org残炮, www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 簡書:http://www.reibang.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e