1. 程式人生 > >從原始碼角度分析java層Handler機制

從原始碼角度分析java層Handler機制

在Android中,當要更新ui的時候,我們必須要在主執行緒中進行更新,原因時當主執行緒被阻塞了5s以上就會出現anr異常,會導致程式崩潰。所以一些耗時的操作必須要放在子執行緒中,但是在子執行緒中又不能做更新ui的操作,所以為了解決這個問題,Android設計了handler機制,handler的出現建立起了主執行緒與子程序之間的通訊橋樑,使得ui更新問題得到改善,下面就來剖析一下handler。ActivityThread啟動了應用程式的主執行緒,在ActivityThread的main方法中:

public static final void main(String[] args) {
        SamplingProfilerIntegration.start();
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
       ......
}

從上述程式碼可以看出,首先要執行Looper.prepareMainLooper();操作,然後進入loop進行迴圈。在prepareMainLooper中,呼叫prepare方法使用sThreadLocal給當前執行緒設定一個Looper,如果當前執行緒中沒有,就初始化一個Looper,在Looper的構造方法中順便建立了一個MessageQueue。細心的讀者可能會注意到prepareMainLooper和prepare方法都是static的,sThreadLocal也是個靜態變數,首先不考慮子執行緒存在的情況,只考慮主執行緒,所以無論我們在應用程式的哪個地方呼叫Looper.prepareMainLooper();通過sThreadLocal.get()得到的都是同一個looper物件,這樣就可以保證一個執行緒中只有一個Looper物件,那麼也就意味著一個執行緒中只有一個MessageQueue。

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
 }

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));
 }

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

執行完 Looper.prepareMainLooper()之後,就是開始Looper.loop()進行訊息的迴圈讀取並且進行分發,這個稍後分析完Handler後再分析。

下面我們再分析一下Handler。
在程式碼中我們經常的這樣用:

private Handler handler = new Handler(){
    public void handleMessage(Message msg) {
        // process incoming messages here
    }
}

public Handler(Callback callback, boolean async) {
    ......
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

在Handler的構造方法中,通過Looper.myLooper()獲取本執行緒中唯一的一個Looper物件,並且初始化hanlder中的訊息佇列,這個訊息佇列和Looper中的一開始初始化的訊息佇列是同一個。
當呼叫handler.sendMessage或者sendEmpty方法時,最終要走的方法都是sendMessageAtTime方法:

 public boolean sendMessageAtTime(Message msg, long     uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg,     long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

在enqueueMessage方法中,給當前要加入訊息佇列的msg設定一個target為this,這個this也就是當前的handler物件,主要是為了後面的looper迴圈出訊息後,方便知道這個msg向何處分發,該由哪個handler進行處理。接著就呼叫MessageQueue的enqueueMessage方法將msg加入佇列中。

boolean enqueueMessage(Message msg, long when) {
    ......
    synchronized (this) {
        ......
        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 {
            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.next
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

當訊息對列中沒有任何msg的時候,當前加入的msg就應該是佇列的隊頭,並且從else語句我們可以看出,整個訊息對列是個迴圈對列。此時訊息對列中已經有了msg,那麼這個msg應該被接受並進行分發處理,在ActivityThread中呼叫了Looper.loop()方法進行訊息的輪詢。

public static void loop() {
    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();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            return;
        }
        Printer logging = me.mLogging;
        .......
        msg.target.dispatchMessage(msg);

        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {

        }
        msg.recycle();
    }
}

其中的for迴圈為死迴圈,有人可能納悶了,looper.loop()是執行在主執行緒中的,而其中有是個死迴圈,不是說好的主執行緒中不能做超時的操作嗎?呵呵,因為在迴圈中輪詢訊息佇列中的訊息時候,如果沒有訊息,則會被阻塞。所以這裡不用擔心anr的問題。通過queue.next()獲取出msg後,通過msg.target.dispatchMessage(msg)處理這個訊息,這個msg.target就是要處理訊息的handle,這也就是為啥在handler中要重寫dispatchMessage方法的原因。最後呼叫recycle釋放訊息,之所以要recycle一下,是因為Message可以不用new的方式,也可以通過Message.obtain方法從訊息池中獲取一個,因為訊息池中的訊息個數有限,如果用完訊息後,不及時的recycle的 話,就會造成msg物件不能重複利用。

接下來具體的分析下queue.next()這個方法,在註釋中我們看到,這個方法有可能會被阻塞,阻塞的原因是訊息佇列中沒有訊息。

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // We can assume mPtr != 0 because the loop is obviously still running.
        // The looper will not call this method after the loop quits.
        nativePollOnce(mPtr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            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());
            }
            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;
                    if (false) Log.v("MessageQueue", "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf("MessageQueue", "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

nativePollOnce(mPtr, nextPollTimeoutMillis)當輪詢沒有訊息時,會進行阻塞。訊息喚醒和阻塞機制將會在下一篇文章進行介紹,請大家關注。

最後對Handler做一下總結。從訊息的分發一直到訊息的處理,先後接觸到的幾個名詞有Looper、MessageQueue、Thread、Handler、Message。

  • Looper:負責初始化訊息佇列,並不斷的從訊息佇列中輪詢訊息。
  • MessageQueue : 是訊息佇列,存放在handler傳送過來的訊息。
  • Thread:當前訊息佇列和looper所操作的場所或者說執行的環境。
  • Handler:負責訊息的傳送和處理。
  • Message:一個更新UI的訊息,由handler發出,由MessageQueue列隊。

訊息處理機制大概的一個處理過程如下:
這裡寫圖片描述

補充:在子執行緒中要更新ui的時候,可以這樣處理

    class LooperThread extends Thread {
        public Handler mHandler;
       public void run() {
           Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                   // process incoming messages here
                }
            };
            Looper.loop();
        }
    }

相關推薦

原始碼角度分析javaHandler機制

在Android中,當要更新ui的時候,我們必須要在主執行緒中進行更新,原因時當主執行緒被阻塞了5s以上就會出現anr異常,會導致程式崩潰。所以一些耗時的操作必須要放在子執行緒中,但是在子執行緒中又不能做更新ui的操作,所以為了解決這個問題,Android設計了

原始碼角度分析Android系統的異常捕獲機制是如何執行的

我們在開發的時候經常會遇到各種異常,當程式遇到異常,便會將異常資訊拋到LogCat中,那這個過程是怎麼實現的呢? 我們以一個例子開始: import android.app.Activity; import android.os.Bundle; public clas

Java面試題 原始碼角度分析HashSet實現原理?

面試官:請問HashSet有哪些特點? 應聘者:HashSet實現自set介面,set集合中元素無序且不能重複; 面試官:那麼HashSet 如何保證元素不重複? 應聘者:因為HashSet底層是基於HashMap實現的,當你new一個HashSet時候,實際上是new了一個map,執行add方法時,實

原始碼角度理解Java設計模式--責任鏈模式

本文內容思維導圖如下:                                        

帶你原始碼角度分析ViewGroup中事件分發流程

序言 這篇博文不是對事件分發機制全面的介紹,只是從原始碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,瞭解各個事件在ViewGroup的分發邏輯對理解、解決滑動衝突問題很有幫助。 ViewGroup中事件分發流

原始碼角度理解Java設計模式——裝飾者模式

一、飾器者模式介紹 裝飾者模式定義:在不改變原有物件的基礎上附加功能,相比生成子類更靈活。 適用場景:動態的給一個物件新增或者撤銷功能。 優點:可以不改變原有物件的情況下動態擴充套件功能,可以使擴充套件的多個功能按想要的順序執行,以實現不同效果。 缺點:更多的類,使程式複雜 型別:結構型。 類圖

原始碼角度理解Java設計模式——門面模式

一、門面模式介紹 門面模式定義:也叫外觀模式,定義了一個訪問子系統的介面,除了這個介面以外,不允許其他訪問子系統的行為發生。 適用場景:子系統很複雜時,增加一個介面供外部訪問。 優點:簡化層級間的呼叫,減少依賴,防止風險。 缺點:如果設計不當,增加新的子系統可能需要修改門面類的原始碼,違背了開閉原則

原始碼角度分析GreenDao的一個查詢過程

GreenDao是一個物件關係對映(ORM)的開源框架,是目前最流行的Android資料庫框架。什麼是物件關係對映(ORM),就是把物件層次的結構對映成關係結構的過程。因為sqlite是一種關係型的資料庫,所以我們要通過SQL語句去操作資料庫,這既麻煩也不符合android程式設計師面向物件的程

Java面試題】之類載入:面試題分析Java類載入機制

 “載入”(Loading)階段是“類載入”(Class Loading)過程的第一個階段,在此階段,虛擬機器需要完成以下三件事情:        1、 通過一個類的全限定名來獲取定義此類的二進位制位元組流。        2、 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結

原始碼角度分析ViewDragHelper

最近群裡的小夥伴都在說ViewDragHelper這玩意,我就感覺好像很牛逼的樣子。然後稍微看了下,不是很難,在此先做個筆記。因為之前他們說scroller的時候,我都不知道是啥。然後今天發現我去年寫的demo中還用到了。原諒我豬一般的記性!! 先來個測試de

原始碼角度分析ViewStub 疑問與原理

一、提出疑問     ViewStub比較簡單,之前文章都提及到《Android 效能優化 三 佈局優化ViewStub標籤的使用》,但是在使用過程中有一個疑惑,到底是ViewStub上設定的引數有效還是在其包括的layout中設定引數有效?如果不明白描述的問題,可以看下以下佈局虛擬碼。 res/lay

原始碼角度剖析 setContentView() 背後的機制

注:本文基於 AS 2.3,示例中的 Activity 繼承自 AppcompatActivity。 示例 日常開發中,我們在 Activity 中基本上不可避免的都會使用到 setContentView() 這行程式碼,而理解它背後的機制能夠讓我們對日

原始碼角度分析imageLoader框架

本文來自http://blog.csdn.net/andywuchuanlong,轉載請說明出處 對於圖片的載入和處理基本上是Android應用軟體專案中的常客,很多初學者在遇到圖片載入這個問題是,總

HashMap原始碼角度分析遍歷過程

上一篇分析了HashMap的資料結構以及put方法的原始碼 HashMap原始碼解析,下面分析HashMap的遍歷過程的原始碼。 遍歷的方法有很多中,主要分析下面這種: Iterator<Map.Entry<String, String&g

高併發之——原始碼角度分析建立執行緒池究竟有哪些方式

前言 在Java的高併發領域,執行緒池一直是一個繞不開的話題。有些童鞋一直在使用執行緒池,但是,對於如何建立執行緒池僅僅停留在使用Executors工具類的方式,那麼,建立執行緒池究竟存在哪幾種方式呢?就讓我們一起從建立執行緒池的原始碼來深入分析究竟有哪些方式可以建立執行緒池。 使用Executors工具類建

原始碼角度學習Java動態代理

[公眾號文章連結:https://mp.weixin.qq.com/s/jaLvb21yVHg2R_gJ-JSeVA](https://mp.weixin.qq.com/s/jaLvb21yVHg2R_gJ-JSeVA) # 前言 最近,看了一下關於RMI(Remote Method Invocation)

【Android】原始碼角度Handler機制

在Android開發規範中,規定了主執行緒的任務的響應時間不能超過5s,否則會出現ANR,即程式無響應。為了避免這個問題的出現,常用的一個解決方案就是開闢新執行緒,在開闢出來的子執行緒中去處理耗時的業務,然後回到UI執行緒(主執行緒)來重新整理UI,這個過程中“

實例角度分析java的public、protected、private和default訪問權限

png mage 分享 import 調用 ring lin tro 1.5 一、public   同一個package   1.本類內部 public class A { public int f=1; public void m1() {}

Java內存模型之JMM角度分析DCL

span 利用 eight first 多人 能夠 的人 ref upload DCL,即Double Check Lock,中衛雙重檢查鎖定。其實DCL很多人在單例模式中用過,LZ面試人的時候也要他們寫過,但是有很多人都會寫錯。他們為什麽會寫錯呢?其錯誤根源在哪裏?有什麽

原始碼角度深入分析ant

Ant的基本概念 首先是ant的基本概念:Project,Target,Tasks,Properties,Paths 1.Project <project> build.xml檔案最頂層的元素,它有三個可選的屬性: 名稱(name):工程的名稱 預設(d