1. 程式人生 > >Android Handler 機制

Android Handler 機制

Handler 簡介

一個Handler允許傳送和處理Message,通過關聯執行緒的 MessageQueue 執行 Runnable 物件。
每個Handler例項都和一個單獨的執行緒及其訊息佇列繫結。
可以將一個任務切換到Handler所在的執行緒中去執行。一個用法就是子執行緒通過Handler更新UI。

主要有2種用法:

    1. 做出計劃,在未來某個時間點執行訊息和Runnable
    1. 在其他執行緒規劃並執行任務

要使用好Handler,需要了解與其相關的 MessageQueue, MessageLooper;不能孤立的看Handler
Handler就像一個操作者(或者像一個對開發者開放的視窗),利用MessageQueue

Looper來實現任務排程和處理

    // 這個回撥允許你使用Handler時不新建一個Handler的子類
    public interface Callback {
        publicbooleanhandleMessage(Message msg);
    }
    final Looper mLooper; // Handler持有 Looper 的例項
    final MessageQueue mQueue; // 持有訊息佇列
    final Callback mCallback;

在Handler的構造器中,我們可以看到訊息佇列是相關的Looper管理的

    public Handler(Callback callback, boolean async) {
        // 處理異常
        mLooper = Looper.myLooper();
        // 處理特殊情況...
        mQueue = mLooper.mQueue; // 獲取的是Looper的訊息佇列
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue
; // 獲取的是Looper的訊息佇列 mCallback = callback; mAsynchronous = async; }

Android是訊息驅動的,實現訊息驅動有幾個要素:

  • 訊息的表示:Message
  • 訊息佇列:MessageQueue
  • 訊息迴圈,用於迴圈取出訊息進行處理:Looper
  • 訊息處理,訊息迴圈從訊息佇列中取出訊息後要對訊息進行處理:Handler

初始化訊息佇列

在Looper構造器中即建立了一個MessageQueue

傳送訊息

通過Looper.prepare初始化好訊息佇列後就可以呼叫Looper.loop進入訊息迴圈了,然後我們就可以向訊息佇列傳送訊息,
訊息迴圈就會取出訊息進行處理,在看訊息處理之前,先看一下訊息是怎麼被新增到訊息佇列的。

訊息迴圈

Java層的訊息都儲存在了Java層MessageQueue的成員mMessages中,Native層的訊息都儲存在了Native Looper的
mMessageEnvelopes中,這就可以說有兩個訊息佇列,而且都是按時間排列的。

為什麼要用Handler這樣的一個機制

因為在Android系統中UI操作並不是執行緒安全的,如果多個執行緒併發的去操作同一個元件,可能導致執行緒安全問題。
為了解決這一個問題,android制定了一條規則:只允許UI執行緒來修改UI元件的屬性等,也就是說必須單執行緒模型,
這樣導致如果在UI介面進行一個耗時較長的資料更新等就會形成程式假死現象 也就是ANR異常,如果20秒中沒有完成
程式就會強制關閉。所以比如另一個執行緒要修改UI元件的時候,就需要藉助Handler訊息機制了。

Handler傳送和處理訊息的幾個方法

1.void handleMessage( Message msg):處理訊息的方法,該方法通常被重寫。
2.final boolean hasMessage(int what):檢查訊息佇列中是否包含有what屬性為指定值的訊息
3.final boolean hasMessage(int what ,Object object) :檢查訊息佇列中是否包含有what好object屬性指定值的訊息
4.sendEmptyMessage(int what):傳送空訊息
5.final Boolean send EmptyMessageDelayed(int what ,long delayMillis):指定多少毫秒傳送空訊息
6.final boolean sendMessage(Message msg):立即傳送訊息
7.final boolean sendMessageDelayed(Message msg,long delayMillis):多少秒之後傳送訊息

與Handler工作的幾個元件Looper、MessageQueue各自的作用:

  • 1.Handler:它把訊息傳送給Looper管理的MessageQueue,並負責處理Looper分給它的訊息
  • 2.MessageQueue:管理Message,由Looper管理
  • 3.Looper:每個執行緒只有一個Looper,比如UI執行緒中,系統會預設的初始化一個Looper物件,它負責管理MessageQueue,
    不斷的從MessageQueue中取訊息,並將相對應的訊息分給Handler處理

Handler.java (frameworks/base/core/java/android/os)

    // 將訊息新增到佇列前,先判斷佇列是否為null
    publicbooleansendMessageAtTime(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);
    }
// ......
    // 將訊息新增到佇列中
    privatebooleanenqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; // 將自己指定為Message的Handler
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

從這裡也不難看出,每個Message都持有Handler。如果Handler持有Activity的引用,Activity onDestroy後Message卻仍然在佇列中,
因為Handler與Activity的強關聯,會造成Activity無法被GC回收,導致記憶體洩露。
因此在Activity onDestroy 時,與Activity關聯的Handler應清除它的佇列由Activity產生的任務,避免記憶體洩露。

訊息佇列 MessageQueue.java (frameworks/base/core/java/android/os)

    // 新增訊息
    booleanenqueueMessage(Message msg, long when) {
        // 判斷並新增訊息...
        return true;
    }

Handler.sendEmptyMessage(int what) 流程解析

獲取一個Message例項,並立即將Message例項新增到訊息佇列中去。

簡要流程如下

// Handler.java
// 立刻傳送一個empty訊息
sendEmptyMessage(int what) 

// 傳送延遲為0的empty訊息  這個方法裡通過Message.obtain()獲取一個Message例項
sendEmptyMessageDelayed(what, 0) 

// 計算訊息的計劃執行時間,進入下一階段
sendMessageDelayed(Message msg, long delayMillis)

// 在這裡判斷佇列是否為null  若為null則直接返回false
sendMessageAtTime(Message msg, long uptimeMillis)

// 將訊息新增到佇列中
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

// 接下來是MessageQueue新增訊息
// MessageQueue.java
booleanenqueueMessage(Message msg, long when)

部分原始碼如下

    publicfinalbooleansendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    publicfinalbooleansendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

    publicfinalbooleansendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

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

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

Handler 取消任務 removeCallbacksAndMessages

要取消任務時,呼叫下面這個方法

publicfinalvoidremoveCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

通過呼叫Message.recycleUnchecked()方法,取消掉與此Handler相關聯的Message。

相關的訊息佇列會執行取消指令

voidremoveCallbacksAndMessages(Handler h, Object object)

Message 和 MessageQueue 簡介

Message

Message 屬於被傳遞,被使用的角色
Message 是包含描述和任意資料物件的“訊息”,能被髮送給Handler
包含2個int屬性和一個額外的物件
雖然構造器是公開的,但獲取例項最好的辦法是呼叫Message.obtain()Handler.obtainMessage()
這樣可以從他們的可回收物件池中獲取到訊息例項

一般來說,每個Message例項握有一個Handler

部分屬性值

    /*package*/ Handler target; // 指定的Handler
    
    /*package*/ Runnable callback;
    
    // 可以組成連結串列
    // sometimes we store linked lists of these things
    /*package*/ Message next;

重置自身的方法,將屬性全部重置

publicvoidrecycle()
voidrecycleUnchecked()

獲取Message例項的常用方法,得到的例項與傳入的Handler繫結

    /**     * 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.     */
    publicstatic Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

將訊息傳送給Handler

    /**     * Sends this Message to the Handler specified by {@link #getTarget}.     * Throws a null pointer exception if this field has not been set.     */
    publicvoidsendToTarget() {
        target.sendMessage(this); // target 就是與訊息繫結的Handler
    }

呼叫這個方法後,Handler會將訊息新增進它的訊息佇列MessageQueue

MessageQueue

持有一列可以被Looper分發的Message。
一般來說由Handler將Message新增到MessageQueue中。

獲取當前執行緒的MessageQueue方法是Looper.myQueue()

Looper 簡介

Looper與MessageQueue緊密關聯

在一個執行緒中執行的訊息迴圈。執行緒預設情況下是沒有與之管理的訊息迴圈的。
要建立一個訊息迴圈,線上程中呼叫prepare,然後呼叫loop。即開始處理訊息,直到迴圈停止。

大多數情況下通過Handler來與訊息迴圈互動。

Handler與Looper線上程中互動的典型例子

class LooperThread extends Thread {
    public Handler mHandler;
    publicvoidrun() {
        Looper.prepare(); // 為當前執行緒準備一個Looper
        // 建立Handler例項,Handler會獲取當前執行緒的Looper
        // 如果例項化Handler時當前執行緒沒有Looper,會報異常 RuntimeException
        mHandler = new Handler() {
            publicvoidhandleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop(); // Looper開始執行
    }
}

Looper中的屬性

Looper持有MessageQueue;唯一的主執行緒Looper sMainLooper;Looper當前執行緒 mThread
儲存Looper的sThreadLocal

    // 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; // Handler會獲取這個訊息佇列例項(參考Handler構造器)
    final Thread mThread; // Looper當前執行緒

ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。

Looper 方法

準備方法,將當前執行緒初始化為Looper。退出時要呼叫quit

publicstaticvoidprepare() {
    prepare(true);
}

privatestaticvoidprepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed)); // Looper例項存入了sThreadLocal
}

prepare方法新建 Looper 並存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal<T>

    publicvoidset(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

當要獲取Looper物件時,從sThreadLocal獲取

    // 獲取與當前執行緒關聯的Looper,返回可以為null
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在當前執行緒執行一個訊息佇列。結束後要呼叫退出方法quit()

publicstaticvoidloop()

準備主執行緒Looper。Android環境會建立主執行緒Looper,開發者不應該自己呼叫這個方法。
UI執行緒,它就是ActivityThread,ActivityThread被建立時就會初始化Looper,這也是在主執行緒中預設可以使用Handler的原因。

publicstaticvoidprepareMainLooper() {
    prepare(false); // 這裡表示了主執行緒Looper不能由開發者來退出
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

獲取主執行緒的Looper。我們開發者想操作主執行緒時,可呼叫此方法

publicstatic Looper getMainLooper()