午夜剧场伦理_日本一道高清_国产又黄又硬_91黄色网战_女同久久另类69精品国产_妹妹的朋友在线

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解Android Handler的使用

瀏覽:64日期:2022-09-19 17:33:45
Handler概要

Handler用于線程間的消息傳遞,它可以將一個(gè)線程中的任務(wù)切換到另一個(gè)線程執(zhí)行。切換的目標(biāo)線程與Handler內(nèi)部持有的Looper所在線程一致。若初始化Handler時(shí)未手動(dòng)設(shè)置Looper,Handler會(huì)通過(guò)ThreadLocal獲取并持有當(dāng)前(初始化Handler時(shí))線程的Looper。當(dāng)Handler發(fā)送一條消息后,這條消息會(huì)進(jìn)入目標(biāo)線程的MessageQueue,目標(biāo)線程的Looper掃描并且取出消息,最終由Handler執(zhí)行這條消息。

詳解Android Handler的使用

構(gòu)造器

Handler的構(gòu)造器大致分為以下兩種:

public Handler(Callback callback, boolean async){}public Handler(Looper looper, Callback callback, boolean async){}

構(gòu)造器的參數(shù)列表:

callback:Handler處理消息的接口回調(diào),執(zhí)行消息時(shí)可能會(huì)調(diào)用該接口。 async:默認(rèn)false,若該值為true,則消息隊(duì)列中的所有消息均是AsyncMessage。AsyncMessage的概念請(qǐng)看后續(xù)章節(jié)。 looper:消息的查詢者,會(huì)不斷輪詢檢查MessageQueue是否有消息。

若調(diào)用者傳遞Looper,直接使用該Looper;否則通過(guò)ThreadLocal從當(dāng)前線程中獲取Looper。所以執(zhí)行任務(wù)所在的目標(biāo)線程不是創(chuàng)建Handler時(shí)所在的線程,而是Looper所在的線程。

sendMessageAtTime

無(wú)論是使用post(Runnable r)還是sendMessage(Message m)發(fā)送消息,最終都會(huì)執(zhí)行到sendMessageAtTime方法。該方法指定了Message的執(zhí)行者(msg.target=handler)和調(diào)用時(shí)機(jī)(msg.when)。

dispatchMessage

dispatchMessage方法用于執(zhí)行事先注冊(cè)的Message和Handler回調(diào),源碼如下:

public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }

可以發(fā)現(xiàn)回調(diào)的優(yōu)先級(jí)是:Message的回調(diào)>Handler的回調(diào)(構(gòu)造器章節(jié)中的callback)>Handler子類重寫的handleMessage方法。

ThreadLocal

ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,用于存放以線程為作用域的數(shù)據(jù),在不同的線程中可以持有不同的數(shù)據(jù)副本。通過(guò)ThreadLocal就可以很方便的查找到當(dāng)前線程的Looper。ThreadLocal內(nèi)部實(shí)現(xiàn)的UML類圖如下:

詳解Android Handler的使用

通過(guò)ThreadLocal查找Looper的流程如下:

通過(guò)Thread.currentThread()獲取當(dāng)前線程對(duì)象。 取出線程對(duì)象持有的ThreadLocalMap對(duì)象。 以自身為key,獲取ThreadLocalMap中對(duì)應(yīng)Entry的value。Looper

Looper在Handler中扮演著消息循環(huán)的角色。它會(huì)不斷查詢MessageQueue中是否有消息。當(dāng)沒(méi)有消息時(shí)Looper將一直阻塞。

若當(dāng)前線程沒(méi)有Looper,且調(diào)用者未傳Looper,Handler會(huì)因?yàn)槲传@取Looper而報(bào)錯(cuò)。解決辦法是通過(guò)Looper.prepare在當(dāng)前線程手動(dòng)創(chuàng)建一個(gè)Looper,并通過(guò)Looper.loop開(kāi)啟消息循環(huán):

new Thread('Thread#2') { @override public void run() {Looper.prepare();Handler handler = new Handler();Looper.loop(); }}

Looper提供了quit和quitSafely兩種方式來(lái)退出一個(gè)Looper。區(qū)別在于前者會(huì)直接退出;后者則是在處理完消息隊(duì)列的已有消息后才安全退出。

Looper所在的線程會(huì)一直處于運(yùn)行狀態(tài),所以建議消息處理完畢后及時(shí)退出Looper,釋放線程。

MessageQueue

MessageQueue是消息的存儲(chǔ)隊(duì)列,內(nèi)部提供了很多精彩的機(jī)制。

IdleHandler

IdleHandler本質(zhì)上只是一個(gè)抽象的回調(diào)接口,沒(méi)有做任何操作:

/** * Callback interface for discovering when a thread is going to block * waiting for more messages. */public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle();}

看上述注釋可以了解,MessageQueue會(huì)在將要進(jìn)入阻塞時(shí)執(zhí)行IdleHandler的queueIdle方法,隊(duì)列阻塞的觸發(fā)時(shí)機(jī)是:

消息隊(duì)列沒(méi)有消息。 隊(duì)首消息的執(zhí)行時(shí)間大于當(dāng)前時(shí)間。

當(dāng)我們希望一個(gè)任務(wù)在隊(duì)列下次將要阻塞時(shí)調(diào)用,就可以使用IdleHandler。在Android工程中最常見(jiàn)的例子就是:給Activity提供生命周期以外的回調(diào)。

比如我希望在布局繪制完成后執(zhí)行某個(gè)操作,但是Activity的onStart和onResume回調(diào)均在View繪制完成之前執(zhí)行,可以看看onResume的官方注釋:

/** * ... * <p>Keep in mind that onResume is not the best indicator that your activity * is visible to the user; a system window such as the keyguard may be in * front. Use {@link #onWindowFocusChanged} to know for certain that your * activity is visible to the user (for example, to resume a game). * ... */ @CallSuper protected void onResume() {...}

這種情況下就可以給MessageQueue設(shè)置一個(gè)IdleHandler,等當(dāng)前隊(duì)列中的消息(包括繪制任務(wù))執(zhí)行完畢并將要進(jìn)入阻塞狀態(tài)時(shí),調(diào)用IdleHandler的任務(wù),確保任務(wù)在繪制結(jié)束后執(zhí)行。

使用方式如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Looper.myLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // do something when queue is idle // 返回值表示bKeepAlive標(biāo)識(shí):true->繼續(xù)使用,false->銷毀該Handler return false; } });}AsyncMessage和SyncBarrier

顧名思義,SyncBarrier表示同步柵欄(也叫作障礙消息),用于阻塞SyncMessage,優(yōu)先執(zhí)行AsyncMessage。該機(jī)制大大提升了MessageQueue的操作靈活性。

在進(jìn)一步了解這兩個(gè)概念之前,需要先了解MessageQueue插入消息的機(jī)制,MessageQueue的enqueueMessage源碼如下(省略了喚醒隊(duì)列的相關(guān)代碼):

boolean enqueueMessage(Message msg, long when) { synchronized (this) { msg.markInUse(); msg.when = when; Message p = mMessages; if (p == null || when == 0 || when < p.when) { // New head. msg.next = p; mMessages = msg; } else { // Inserted within the middle of the queue. Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } return true;}

從上述源碼可知,消息按照調(diào)用時(shí)機(jī)(when)有序排列,當(dāng)when等于0時(shí),直接將消息插在隊(duì)頭;當(dāng)when等于隊(duì)列中消息的when時(shí),將消息插在這些消息的后方。

假設(shè)這樣一個(gè)場(chǎng)景:我們有一個(gè)非常緊急的任務(wù),希望能夠優(yōu)先執(zhí)行,該如何處理?

很簡(jiǎn)單,發(fā)送一個(gè)when為0的消息,它將自動(dòng)被插到列表的頭部。Handler中也提供了現(xiàn)成的接口:

public final boolean postAtFrontOfQueue(Runnable r){ return sendMessageAtFrontOfQueue(getPostMessage(r));}public final boolean sendMessageAtFrontOfQueue(Message msg) {return enqueueMessage(queue, msg, 0);}

將場(chǎng)景升級(jí)一下:我們有一個(gè)任務(wù)A,其他所有任務(wù)都依賴于A,若A未執(zhí)行,則其他所有任務(wù)都不允許執(zhí)行。

A插入隊(duì)列的時(shí)間和執(zhí)行時(shí)間都是不確定的,在此之前,所有任務(wù)都不允許執(zhí)行。按照當(dāng)前的機(jī)制無(wú)法實(shí)現(xiàn)該需求,此時(shí)SyncBarrier和AsyncMessage就派上了用場(chǎng),實(shí)現(xiàn)流程如下:

調(diào)用MessageQueue.postSyncBarrier將SyncBarrier插入隊(duì)列:SyncBarrier本質(zhì)上是一個(gè)target為空的消息,插入邏輯和普通消息一致,也是按照when確定插入位置。SyncBarrier的when固定是SystemClock.uptimeMillis(),因此將其插入到隊(duì)列的中間(SyncBarrier前面可能會(huì)有一些無(wú)時(shí)延的消息,后面可能會(huì)有帶時(shí)延的消息)。 插入SyncBarrier后,輪詢消息直至SyncBarrier排到隊(duì)列頭節(jié)點(diǎn),此時(shí)使用next方法查詢消息將自動(dòng)過(guò)濾同步消息,只執(zhí)行異步消息。源碼如下所示:

// mMessages表示隊(duì)首消息Message msg = mMessages;if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do {prevMsg = msg;msg = msg.next; } while (msg != null && !msg.isAsynchronous());} 插入任務(wù)A(將A定義為AsyncMessage),由于SyncBarrier的存在,A將優(yōu)先被執(zhí)行(不排除A有時(shí)延,此時(shí)隊(duì)列將進(jìn)入阻塞狀態(tài),即便隊(duì)列里可能存在無(wú)時(shí)延的同步消息)。 只要SyncBarrier放在隊(duì)首,同步消息將一直被阻塞,消息隊(duì)列只能輸出AsyncMessage。當(dāng)任務(wù)A執(zhí)行完畢后,需要調(diào)用removeSyncBarrier手動(dòng)將SyncBarrier移除。

Handler提供了接口讓我們插入AsyncMessage,即構(gòu)造器中的asyc參數(shù)。當(dāng)async為true時(shí),所有通過(guò)Handler傳遞的消息均會(huì)被定義為AsyncMessage(前提是要和SyncBarrier配合使用,不然AsyncMessage沒(méi)有效果)。

再重新思考SyncBarrier和AsyncMessage機(jī)制的應(yīng)用場(chǎng)景,本質(zhì)上就是為了阻塞從Barrier消息到AsyncMessage消息之間的同步消息的執(zhí)行。

在Android源碼中,布局的繪制就使用了這種機(jī)制。在ViewRootImpl的scheduleTraversals方法中,會(huì)事先往主線程的消息隊(duì)列設(shè)置Barrier,再去提交AsyncMessage,阻塞在此期間的所有同步消息。源碼如下:

void scheduleTraversals() {if (!mTraversalScheduled) { mTraversalScheduled = true;// 設(shè)置Barrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 該方法最終會(huì)提交一個(gè)AsyncMessage mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded();}}

Tips:關(guān)于Barrier的概念在Java并發(fā)中多有涉及,比如CountDownLatch、CyclicBarrier等。詳情請(qǐng)查看《Thinking in Java》21.7章節(jié)。

阻塞和喚醒機(jī)制

阻塞和喚醒機(jī)制是MessageQueue的精髓,極大降低了Loop輪詢的頻率,減少性能開(kāi)銷。

在IdleHandler章節(jié)已經(jīng)提及MessageQueue阻塞的時(shí)機(jī):

消息隊(duì)列沒(méi)有消息。隊(duì)首消息的執(zhí)行時(shí)間大于當(dāng)前時(shí)間。next方法的源碼如下:

Message next() { int nextPollTimeoutMillis = 0; for (;;) {if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands();}// 關(guān)鍵方法,將線程阻塞nextPollTimeoutMillis毫秒,若nextPollTimeoutMillis為-1,線程將一直處于阻塞狀態(tài)。nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) { // Ignore SyncBarrier code final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null) {if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else { // Got a message. mBlocked = false; if (prevMsg != null) {prevMsg.next = msg.next; } else {mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg;} } else {// No more messages.nextPollTimeoutMillis = -1; } // Ignore IdleHandler code if (pendingIdleHandlerCount <= 0) {// No idle handlers to run. Loop and wait some more.mBlocked = true;continue; }} }}

插入消息時(shí)喚醒MessageQueue的時(shí)機(jī)(假設(shè)隊(duì)列處于阻塞狀態(tài)):

隊(duì)首插入一條SyncMessage。 隊(duì)首是一個(gè)柵欄,且插入一條離柵欄最近的AsyncMessage。

enqueueMessage方法的源碼如下:

boolean enqueueMessage(Message msg, long when) {synchronized (this) { msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked; } else {// Inserted within the middle of the queue. Usually we don’t have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) { prev = p; p = p.next; if (p == null || when < p.when) {break; } if (needWake && p.isAsynchronous()) {needWake = false; }}msg.next = p; // invariant: p == prev.nextprev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { // 關(guān)鍵方法,用于喚醒隊(duì)列線程nativeWake(mPtr); }}return true;}

喚醒的第二種時(shí)機(jī)特意強(qiáng)調(diào)了插入離Barrier最近的AsyncMessage。對(duì)于如下的阻塞情況,插入AsyncMessage時(shí)不需要將其喚醒:

詳解Android Handler的使用

Handler內(nèi)存泄漏分析

了解了Handler的內(nèi)部原理后,再來(lái)分析由Handler引起的內(nèi)存泄露問(wèn)題:

當(dāng)定義了一個(gè)非靜態(tài)的Handler內(nèi)部類時(shí),內(nèi)部類會(huì)隱式持有外圍類的引用。 Handler執(zhí)行sendMessageAtTime方法時(shí),Message的target參數(shù)會(huì)持有Handler對(duì)象。 當(dāng)Message沒(méi)有被執(zhí)行時(shí)(比如now<when),若退出了Activity,此時(shí)Message依然持有Handler對(duì)象,而Handler持有Activity的對(duì)象,導(dǎo)致內(nèi)存泄露。

解決方案:

將Handler定義為靜態(tài)內(nèi)部類。 退出Activity時(shí)清空MessageQueue中對(duì)應(yīng)的Message。

以上就是詳解Android Handler的使用的詳細(xì)內(nèi)容,更多關(guān)于Android Handler的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 日本a v网站 | 久久人视频 | 亚洲黄色a级片 | 色多多在线观看视频 | а中文在线天堂 | 国产高清自拍视频 | 撸大师在线观看 | 国产福利91精品一区二区三区 | 蜜桃在线一区二区 | 伊人久久伊人 | 在线男人天堂 | 国产中文字幕第一页 | 人人舔 | av每日更新 | 免费午夜影院 | 国产啊v在线观看 | 欧美大片黄 | 波多野结衣在线观看一区二区 | 97中文在线 | 中文字幕偷拍 | 色哟哟一区二区 | 天美传媒mv免费观看 | 男人的天堂网页 | 国产精选视频在线观看 | 午夜天堂在线 | 成人天堂噜噜噜 | 国产在线观看免费视频今夜 | 色婷久久 | 国产一区二区精品在线 | 一起草av在线 | 欧美综合视频在线 | 中文字幕有码在线 | 欧美在线中文 | 少妇喷水在线观看 | 激情丁香六月 | 久久久www成人免费毛片 | 欧美高清a | 日本a级大片 | 欧美大白屁股 | 欧美性一区二区三区 | 免费看污片网站 |