異步消息機制-Handler

參考資料

Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message三者關系


目錄

  • 1)概述
  • 2)分析
    • 2.1)ThreadLocal工作原理
    • 2.2)MessageQueue工作原理
    • 2.3)Looper工作原理
    • 2.4)Handler總結

1)概述

概述 說明
MessageQueue 并不是真正的隊列,采用的是單鏈表的數據結構來存儲消息列表
ThreadLocal 線程內部數據存儲類果正,可以在指定線程內部存儲數據
Looper 無限循環(huán)的處理消息,有新消息處理,無則阻塞
Handler 將一個任務切換到指定線程處理帜消。注意:線程默認沒有Looper的,需要自己創(chuàng)建浓体。但主線程ActivityThread在創(chuàng)建的時候會初始化Looper泡挺,不用我們管
屏幕快照 2017-08-04 上午10.50.24.png
屏幕快照 2017-08-04 上午10.50.41.png
MQ單鏈表
image

2)分析

2.1)ThreadLocal(線程的本地存儲)工作原理

通過ThreadLocal可實現Looper在線程中的存取,以方便Handler獲取當前線程的Looper命浴。

//舉例,不同線程訪問同一ThreadLocal對象娄猫,值是不一樣的
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();

//主線程中設置
mThreadLocal.set(true);   //Log:  mThreadLocal.get() =  true
//子線程1
new Thread("Thread#1"){
  @override
  public void run(){
    mThreadLocal.set(false);  //Log:  mThreadLocal.get() = false
  }
}
//子線程2
new Thread("Thread#2"){
  @override
  public void run(){
    Log.i(mThreadLocal.get());  //Log:  null
  }
}
ThreadLocal.set(T value)
ThreadLocal.get()

2.2)MessageQueue工作原理

方法 說明
enqueueMessage(Message msg, long when) 插入消息
next() 讀取消息并從消息隊列移除贱除。無限循環(huán),如果MessageQueue中無消息媳溺,next()將會阻塞月幌,有消息到來會返回此消息并從MQ中移除

MessageQueue是消息機制的Java層和C++層的連接紐帶,大部分核心方法都交給native層來處理


native方法

MQ構造

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    //通過native方法初始化消息隊列褂删,其中mPtr是供native代碼使用
    mPtr = nativeInit();
}
  • MessageQueue是按照Message觸發(fā)時間的先后順序排列的飞醉,隊頭的消息是將要最早觸發(fā)的消息。當有消息需要加入消息隊列時屯阀,會從隊列頭開始遍歷缅帘,直到找到消息應該插入的合適位置,以保證所有消息的時間順序
    MessageQueue.enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
    // 每一個普通Message必須有一個target
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        if (mQuitting) {  //正在退出時难衰,回收msg钦无,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p為null(代表MessageQueue沒有消息) 或者msg的觸發(fā)時間是隊列中最早的, 則進入該該分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //當阻塞時需要喚醒
        } else {
            //將消息按時間順序插入到MessageQueue盖袭。一般地失暂,不需要喚醒事件隊列,除非
            //消息隊頭存在barrier鳄虱,并且同時Message是隊列中最早的異步消息弟塞。
            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;
            prev.next = msg;
        }
        //消息沒有退出,我們認為此時mPtr != 0
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue.next()

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //當消息循環(huán)已經退出拙已,則直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循環(huán)迭代的首次為-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作决记,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒倍踪,都會返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //當消息Handler為空時系宫,查詢MessageQueue中的下一條異步消息msg,則退出循環(huán)建车。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //當異步消息觸發(fā)時間大于當前時間扩借,則設置下一次輪詢的超時時長
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 獲取一條消息,并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //設置消息的使用狀態(tài)缤至,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地獲取MessageQueue中的下一條即將要執(zhí)行的消息
                }
            } else {
                //沒有消息
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出潮罪,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //當消息隊列為空,或者是消息隊列的第一個消息時
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //沒有idle handlers 需要運行领斥,則循環(huán)并等待错洁。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循環(huán)時,會運行idle handlers戒突,執(zhí)行完成后,重置pendingIdleHandlerCount為0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去掉handler的引用
            boolean keep = false;
            try {
                keep = idler.queueIdle();  //idle時執(zhí)行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置idle handler個數為0描睦,以保證不會再次重復運行
        pendingIdleHandlerCount = 0;
        //當調用一個空閑handler時膊存,一個新message能夠被分發(fā),因此無需等待可以直接查詢pending message.
        nextPollTimeoutMillis = 0;
    }
}

MessageQueue.quit()

void quit(boolean safe) {
        // 當mQuitAllowed為false,表示不運行退出隔崎,強行調用quit()會拋出異常
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) { //防止多次執(zhí)行退出操作
                return;
            }
            mQuitting = true;
            if (safe) {
                removeAllFutureMessagesLocked(); //移除尚未觸發(fā)的所有消息
            } else {
                removeAllMessagesLocked(); //移除所有的消息
            }
            //mQuitting=false今艺,那么認定為 mPtr != 0
            nativeWake(mPtr);
        }
    }

2.3)Looper工作原理

Looper構造方法

private Looper(boolean quitAllowed){
  //創(chuàng)建MQ
  mQueue = new MessageQueue(quitAllowed);
  //記錄當前線程
  mThread = Thread.currentThread();
}

Looper.prepare()

public static final void prepare() {  
        //每個線程只允許執(zhí)行一次該方法,第二次執(zhí)行會拋出異常
        if (sThreadLocal.get() != null) {  
            throw new RuntimeException("Only one Looper may be created per thread");  
        }  
        sThreadLocal.set(new Looper(true));  
}  

Looper.loop()會循環(huán)調用MessageQueue的next()來獲取消息爵卒,而next()是一個阻塞操作虚缎,當沒有消息時,next()會阻塞钓株,導致loop()也一直阻塞实牡。

public static void loop() {  
        //myLooper() => return sThreadLocal.get();
        // 獲取sThreadLocal存儲的Looper實例,每個線程是不一樣的
        final Looper me = myLooper();  
        if (me == null) {  
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
        }  
        final MessageQueue queue = me.mQueue;  
  
        // Make sure the identity of this thread is that of the local process,  
        // and keep track of what that identity token actually is. 
        //確保在權限檢查時基于本地進程轴合,而不是基于最初調用進程创坞。 
        Binder.clearCallingIdentity();  
        final long ident = Binder.clearCallingIdentity();  
  
        //無限循環(huán)
        for (;;) {  
            //取出一條消息,如果沒有消息則阻塞
            Message msg = queue.next(); // might block  
            if (msg == null) {  
                // No message indicates that the message queue is quitting.  
                return;  
            }  
  
            // This must be in a local variable, in case a UI event sets the logger  
            Printer logging = me.mLogging;  
            if (logging != null) {  
                logging.println(">>>>> Dispatching to " + msg.target + " " +  
                        msg.callback + ": " + msg.what);  
            }  
  
            //交給Handler的dispatch去處理
            msg.target.dispatchMessage(msg);  
  
            if (logging != null) {  
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  
            }  
  
            // Make sure that during the course of dispatching the  
            // identity of the thread wasn't corrupted.  
            final long newIdent = Binder.clearCallingIdentity();  
            if (ident != newIdent) {  
                Log.wtf(TAG, "Thread identity changed from 0x"  
                        + Long.toHexString(ident) + " to 0x"  
                        + Long.toHexString(newIdent) + " while dispatching to "  
                        + msg.target.getClass().getName() + " "  
                        + msg.callback + " what=" + msg.what);  
            }  
            //把分發(fā)后的Message回收到消息池受葛,以便重復利用
            msg.recycle();  
        }  
}  
//為子線程創(chuàng)建Looper
//在沒有Looper的子線程中創(chuàng)建Handler會報錯L庹恰!W芴病8俣隆!`
new Thread("Thread#2"){
  @override
  public void run(){
    Looper.prepare();
    Handler handler = new Handler();
    Looper.loop();
  }
}
方法 說明
loop() 無限循環(huán)調用MQ的next()獲取消息
getMainLooper() 任何地方獲取主線程Looper
quit 立刻退出Looper
quitSafely 安全退出闰渔,會將MQ中的消息處理完畢

PS:子線程中創(chuàng)建的Looper席函,完成任務后應quit,否則此子線程會一直處于等待狀態(tài)澜建。

public void quit() {
    mQueue.quit(false); //消息移除
}
public void quitSafely() {
    mQueue.quit(true); //安全地消息移除
}

2.4)Handler總結

  • Looper.prepare()通過ThreadLocal在本線程保存Looper實例向挖,只能調用一次,否則異常炕舵。其保存的MQ對象也只能是一個何之。
  • Looper.loop()無限循環(huán)讀MQ
  • Handler的構造方法中獲取當前線程保存的Looper實例
  • 通過Looper獲取MessageQueue
//構造方法
...
mLooper = Looper.myLooper();  //從當前線程的TLS中獲取Looper對象
...
mQueue = mLooper.mQueue;  //消息隊列,來自Looper對象
  • sendXXX調用MQ的enqueueMessage()咽筋,然后加入MQ中


    最終都是調用MessageQueue.enqueueMessage()
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  
       //將自身賦予msg.target
       //方便loop()中的msg.target.dispatchMessage(msg)
       msg.target = this;  
       if (mAsynchronous) {  
           msg.setAsynchronous(true);  
       }  
       return queue.enqueueMessage(msg, uptimeMillis);  
   } 
  • Looper.loop()中的msg.target.dispatchMessage(msg)最終會進入handleMessage

2.5)消息池

  • obtain
    從消息池取Message溶推,都是把消息池表頭的Message取走,再把表頭指向next
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null; //從sPool中取出一個Message對象奸攻,并消息鏈表斷開
            m.flags = 0; // 清除in-use flag
            sPoolSize--; //消息池的可用大小進行減1操作
            return m;
        }
    }
    return new Message(); // 當消息池為空時蒜危,直接創(chuàng)建Message對象
}
  • recycle
    把不再使用的消息加入消息池
    將Message加入到消息池的過程,都是把Message加到鏈表的表頭
public void recycle() {
    if (isInUse()) { //判斷消息是否正在使用
        if (gCheckRecycle) { //Android 5.0以后的版本默認為true,之前的版本默認為false.
            throw new IllegalStateException("This message cannot be recycled because it is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

//對于不再使用的消息睹耐,加入到消息池
void recycleUnchecked() {
    //將消息標示位置為IN_USE辐赞,并清空消息所有的參數。
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;
    synchronized (sPoolSync) {
        //靜態(tài)變量MAX_POOL_SIZE代表消息池的可用大邢跹怠响委;消息池的默認大小為50
        if (sPoolSize < MAX_POOL_SIZE) { //當消息池沒有滿時新思,將Message對象加入消息池
            next = sPool;
            sPool = this;
            sPoolSize++; //消息池的可用大小進行加1操作
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赘风,隨后出現的幾起案子夹囚,更是在濱河造成了極大的恐慌,老刑警劉巖邀窃,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荸哟,死亡現場離奇詭異,居然都是意外死亡瞬捕,警方通過查閱死者的電腦和手機鞍历,發(fā)現死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來山析,“玉大人堰燎,你說我怎么就攤上這事∷窆欤” “怎么了秆剪?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爵政。 經常有香客問我仅讽,道長,這世上最難降的妖魔是什么钾挟? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任洁灵,我火速辦了婚禮,結果婚禮上掺出,老公的妹妹穿的比我還像新娘徽千。我一直安慰自己,他們只是感情好汤锨,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布双抽。 她就那樣靜靜地躺著,像睡著了一般闲礼。 火紅的嫁衣襯著肌膚如雪牍汹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天柬泽,我揣著相機與錄音慎菲,去河邊找鬼。 笑死锨并,一個胖子當著我的面吹牛露该,可吹牛的內容都是我干的。 我是一名探鬼主播第煮,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼解幼,長吁一口氣:“原來是場噩夢啊……” “哼闸拿!你這毒婦竟也來了?” 一聲冷哼從身側響起书幕,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揽趾,沒想到半個月后台汇,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡篱瞎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年苟呐,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俐筋。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡牵素,死狀恐怖,靈堂內的尸體忽然破棺而出澄者,到底是詐尸還是另有隱情笆呆,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布粱挡,位于F島的核電站赠幕,受9級特大地震影響,放射性物質發(fā)生泄漏询筏。R本人自食惡果不足惜榕堰,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫌套。 院中可真熱鬧逆屡,春花似錦、人聲如沸踱讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勇蝙。三九已至沫勿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間味混,已是汗流浹背产雹。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翁锡,地道東北人蔓挖。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像馆衔,于是被迫代替她去往敵國和親瘟判。 傳聞我的和親對象是個殘疾皇子怨绣,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容