Handler機(jī)制淺析

前言

Android應(yīng)用程序是基礎(chǔ)Java語(yǔ)言,內(nèi)部封裝了消息隊(duì)列红符,然后在主線程開啟了死循環(huán)不斷獲取消息并處理來(lái)實(shí)現(xiàn)不間斷的界面變化與各種操作的執(zhí)行。這個(gè)過程主要由:Looper、Handler、Message三者來(lái)共同完成默刚,Message作為消息載體,Looper用于封裝消息隊(duì)列逃魄,Handler用于插入與處理消息荤西。

Looper簡(jiǎn)析

首先,看看Looper類的幾個(gè)成員變量

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;

sThreadLoca并非是一個(gè)線程的實(shí)現(xiàn)版本伍俘,它并不是一個(gè)Thread邪锌,而是線程局部變量。簡(jiǎn)單地說(shuō)癌瘾,它是一種較為特殊的線程綁定機(jī)制觅丰。很顯然,這里已經(jīng)暴露了Looper與線程之間肯定有一些“特殊的聯(lián)系”妨退。

sMainLooper是個(gè)Looper對(duì)象妇萄,很顯然Looper不可能是單例實(shí)現(xiàn)蜕企,并且從命名上我們也可以很容易看出來(lái)該對(duì)象,它可能是主線程/UI線程的Looper對(duì)象冠句,咱們看到prepareMainLooper方法中初始化了這個(gè)對(duì)象

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

從方法注釋可以得知轻掩,該方法是實(shí)例化一個(gè)屬于該應(yīng)用主Looper(暫且這么理解),它是由系統(tǒng)自動(dòng)調(diào)用懦底,不需要你手動(dòng)調(diào)用該方法

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

getMainLooper方法是提供獲取主線程Looper對(duì)象唇牧,也就是說(shuō)sMainLooper確實(shí)是屬于主線程的Looper對(duì)象,并且它在應(yīng)用開始時(shí)就被初始化了
具體的初始化位置于ActivityThread#main()方法中基茵,有興趣可自行了解

mQueue 是一個(gè)MessageQueue對(duì)象奋构,咱們來(lái)看看官方對(duì)它的解釋

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 * 
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue {...}

我粗俗的翻譯下,MessageQueue是一個(gè)低級(jí)類拱层,通過Looper提供消息集合的派發(fā)弥臼,消息不會(huì)直接被添加到MessageQueue,而是通過Handler對(duì)象與其關(guān)聯(lián)Looper對(duì)象根灯【睹澹總結(jié)下,就是MessageQueue的作用只是簡(jiǎn)單是存儲(chǔ)消息烙肺,具體的消息存取是由Handler與Looper來(lái)觸發(fā)與管理纳猪。

之前咱們提到,Looper很可能與線程間存在密切聯(lián)系桃笙。mThread成員變量再次證實(shí)了咱們的想法氏堤,怎么說(shuō)?不急搏明,咱們現(xiàn)在先分析下Looper中的核心方法

首先鼠锈,prepare方法

 /** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
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));
}

初始化生成當(dāng)前線程的Looper對(duì)象,只要當(dāng)prepare方法執(zhí)行了之后對(duì)應(yīng)線程中才可以創(chuàng)建Handler對(duì)象星著,并且必須確保在loop執(zhí)行前調(diào)用购笆。從代碼中可以看出來(lái),此時(shí)sThreadLocal對(duì)象被實(shí)例化并與當(dāng)前線程綁定虚循,What同欠?你是想說(shuō)sThreadLocal.set(new Looper(quitAllowed));就完成了綁定?從哪得出線程信息了横缔?好铺遂,我們?cè)賮?lái)看看構(gòu)造器。

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

OK剪廉,mThread = Thread.currentThread()娃循,mThread現(xiàn)身了,引用了當(dāng)前線程

loop方法

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the 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) {
            // 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);
        }

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

        msg.recycleUnchecked();
    }
}

注釋已經(jīng)給了明確的意思斗蒋,它是開始循環(huán)派發(fā)當(dāng)前線程的消息隊(duì)列捌斧,可以調(diào)用quit停止笛质。咱們?cè)倏纯捶椒▋?nèi)部細(xì)節(jié),在開始真正之前調(diào)用myLooper方法獲取了sThreadLocal.get()判斷若為空捞蚂,表示當(dāng)前線程并沒有實(shí)例化過Looper妇押,即并沒有調(diào)用prepare方法,于是拋出異常姓迅。

Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
...
for (;;) {
...
final long newIdent = Binder.clearCallingIdentity();
}

這里涉及到IPC通信中的UID與PIC識(shí)別敲霍,存儲(chǔ)于IPCThreadState中的mCallingPid與mCallingUid,簡(jiǎn)單的解釋下丁存。假設(shè)processA對(duì)processB進(jìn)行了遠(yuǎn)程調(diào)用肩杈,processB必須攜帶processA的pid與uid,用于A端的權(quán)限校驗(yàn)解寝。因此上述代碼塊的意思很明了:在每次派發(fā)消息前清除IPC身份標(biāo)識(shí)扩然,并判斷身份是否變化

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

msg.target.dispatchMessage(msg);

if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

獲取隊(duì)列中的下一個(gè)消息,獲取Looper的日志輸出對(duì)象聋伦。OK夫偶,開始派發(fā)消息,在處理消息的前后輸出日志

需要注意的觉增,loop方法中若成功執(zhí)行兵拢,那里面執(zhí)行for死循環(huán),將會(huì)阻塞后續(xù)代碼逾礁。即在loop調(diào)用后说铃,后續(xù)的代碼將永遠(yuǎn)執(zhí)行不到,不信你看~

ActivityThread#main(String[] args)

public static void main(String[] args) {
...
    Looper.prepareMainLooper();
...
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Message淺析

Message嘹履,消息載體截汪。包括:身份標(biāo)識(shí)what、數(shù)值變量arg1與arg2(用于額外數(shù)據(jù)較簡(jiǎn)單的情況下使用)植捎、數(shù)據(jù)obj(Object類型)、replyTo(用于IPC阳柔,與本篇內(nèi)容)焰枢、數(shù)據(jù)data(Bunlde類型)、target對(duì)象(存儲(chǔ)目標(biāo)Handler對(duì)象)

/**
 * 
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.  
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
 public final class Message implements Parcelable {...}

obtain方法

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    ...
}
/**
 * Same as {@link #obtain()}, but copies the values of an existing
 * message (including its target) into the new one.
 * @param orig Original message to copy.
 * @return A Message object from the global pool.
 */
public static Message obtain(Message orig) {
    ...
}
/**
 * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
 * @param h  Handler to assign to the returned Message object's <em>target</em> member.
 * @return A Message object from the global pool.
 */
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;

    return m;
}

實(shí)現(xiàn)Message的復(fù)用舌剂,內(nèi)部緩存是用一個(gè)鏈表形式的全局緩存池實(shí)現(xiàn)济锄,一共有八個(gè)重載方法。各個(gè)方法的差異只是對(duì)于獲取到的Message對(duì)象各信息賦值霍转。

既然使用了緩存荐绝,那么肯定有回收消息的方法。對(duì)于被回收了消息避消,清除了對(duì)象信息后低滩,若鏈表緩存池未滿召夹,會(huì)把該消息放到鏈表的首部。結(jié)合recycleUnchecked與obtain方法可以看出恕沫,這是個(gè)后進(jìn)先出的鏈表监憎。

public void recycle() {
    // check ...
    recycleUnchecked();
}
/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    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) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

總結(jié),Message對(duì)象作為消息內(nèi)容的載體婶溯,它的主要核心就是實(shí)現(xiàn)了鏈表緩存與對(duì)于消息內(nèi)容的存取與維護(hù)

Handler淺析

Handler鲸阔,簡(jiǎn)單總結(jié)下源碼注釋
1.Handler用于處理與其自身關(guān)聯(lián)的線程中的消息隊(duì)列
2.一個(gè)Handler只對(duì)應(yīng)一個(gè)Looper與一個(gè)線程
3.Handler的主要兩個(gè)作用:消息的調(diào)度與處理;在不同線程間執(zhí)行操作

/**
 * A Handler allows you to send and process {@link Message} and Runnable
 * objects associated with a thread's {@link MessageQueue}.  Each Handler
 * instance is associated with a single thread and that thread's message
 * queue.  When you create a new Handler, it is bound to the thread /
 * message queue of the thread that is creating it -- from that point on,
 * it will deliver messages and runnables to that message queue and execute
 * them as they come out of the message queue.
 * 
 * <p>There are two main uses for a Handler: (1) to schedule messages and
 * runnables to be executed as some point in the future; and (2) to enqueue
 * an action to be performed on a different thread than your own.
 * 
 * <p>Scheduling messages is accomplished with the
 * {@link #post}, {@link #postAtTime(Runnable, long)},
 * {@link #postDelayed}, {@link #sendEmptyMessage},
 * {@link #sendMessage}, {@link #sendMessageAtTime}, and
 * {@link #sendMessageDelayed} methods.  The <em>post</em> versions allow
 * you to enqueue Runnable objects to be called by the message queue when
 * they are received; the <em>sendMessage</em> versions allow you to enqueue
 * a {@link Message} object containing a bundle of data that will be
 * processed by the Handler's {@link #handleMessage} method (requiring that
 * you implement a subclass of Handler).
 * 
 * <p>When posting or sending to a Handler, you can either
 * allow the item to be processed as soon as the message queue is ready
 * to do so, or specify a delay before it gets processed or absolute time for
 * it to be processed.  The latter two allow you to implement timeouts,
 * ticks, and other timing-based behavior.
 * 
 * <p>When a
 * process is created for your application, its main thread is dedicated to
 * running a message queue that takes care of managing the top-level
 * application objects (activities, broadcast receivers, etc) and any windows
 * they create.  You can create your own threads, and communicate back with
 * the main application thread through a Handler.  This is done by calling
 * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
 * your new thread.  The given Runnable or Message will then be scheduled
 * in the Handler's message queue and processed when appropriate.
 */
public class Handler {...}

咱們主要看兩方面內(nèi)容
1.Handler與Looper之間的關(guān)聯(lián)
2.Handler與Message之間的關(guān)聯(lián)

/**
 * Use the {@link Looper} for the current thread with the specified callback interface
 * and set whether the handler should be asynchronous.
 *
 * Handlers are synchronous by default unless this constructor is used to make
 * one that is strictly asynchronous.
 *
 * Asynchronous messages represent interrupts or events that do not require global ordering
 * with represent to synchronous messages.  Asynchronous messages are not subject to
 * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
 *
 * @param callback The callback interface in which to handle messages, or null.
 * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
 * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
 *
 * @hide
 */
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實(shí)例化的時(shí)候迄委,判斷了當(dāng)前線程對(duì)應(yīng)的Looper對(duì)象是否已創(chuàng)建褐筛。這也證明了在分析Looper時(shí)對(duì)Looper與線程之間存在聯(lián)系的判斷,以及Looper與線程是一一對(duì)應(yīng)的叙身。

public final Message obtainMessage(...)
{
    ...
}

obtainMessage的四個(gè)重載方法渔扎,分別調(diào)用了相關(guān)的Message.obtain(…)

發(fā)送消息的兩種方式:Message與Runnbale,這里我就拿delayed舉例

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

從上面的代碼可以看出曲梗,發(fā)送Runnable實(shí)質(zhì)上也是Message對(duì)象赞警,只不過Runnable方式會(huì)為Message對(duì)象的callback對(duì)象賦值

消息處理

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

消息處理時(shí)首先會(huì)判斷callback對(duì)象,若不為空則執(zhí)行對(duì)應(yīng)的回調(diào)代碼虏两。否則將空方法handleMessage進(jìn)行處理愧旦,如果想要接收到消息后進(jìn)行對(duì)應(yīng)的事件操作,那么handleMessage是子類必須重寫的方法定罢。

三者關(guān)系

用一張圖來(lái)解釋他們?nèi)咧g的聯(lián)系

這里寫圖片描述
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笤虫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子祖凫,更是在濱河造成了極大的恐慌琼蚯,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惠况,死亡現(xiàn)場(chǎng)離奇詭異遭庶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稠屠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門峦睡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人权埠,你說(shuō)我怎么就攤上這事榨了。” “怎么了攘蔽?”我有些...
    開封第一講書人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵龙屉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我满俗,道長(zhǎng)转捕,這世上最難降的妖魔是什么作岖? 我笑而不...
    開封第一講書人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任瓜富,我火速辦了婚禮鳍咱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘与柑。我一直安慰自己谤辜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開白布价捧。 她就那樣靜靜地躺著丑念,像睡著了一般。 火紅的嫁衣襯著肌膚如雪结蟋。 梳的紋絲不亂的頭發(fā)上脯倚,一...
    開封第一講書人閱讀 52,821評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音嵌屎,去河邊找鬼推正。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宝惰,可吹牛的內(nèi)容都是我干的植榕。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼尼夺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尊残!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起淤堵,我...
    開封第一講書人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寝衫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拐邪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慰毅,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年扎阶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了事富。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乘陪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雕擂,到底是詐尸還是另有隱情啡邑,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布井赌,位于F島的核電站谤逼,受9級(jí)特大地震影響贵扰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜流部,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偎痛,春花似錦刁岸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绒障,卻和暖如春吨凑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背户辱。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工鸵钝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庐镐。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓恩商,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親焚鹊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痕届,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容