Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Handler 消息機制(解惑篇)

Android Handler 消息機制(解惑篇)

編輯:關於Android編程

概述

大家對於Android中的消息處理機制的用法一定都比較熟悉,至於工作原理估計不少人有研究。就像我們自己寫的類我們用起來比較熟悉一樣,如果我們熟悉了消息處理機制的具體實現,那麼我們用起來肯定也會事半功倍。

博主之前只是稍有涉獵,對其中一些地方也還心存疑慮,比如既然Looper.loop()裡是一個死循環,那它會不會很消耗CPU呢?死循環阻塞了線程,那我們其他的事務是如何被處理的呢?Android的UI線程是在哪裡被初始化的呢?等等。索性今天就把他們放到一起,說道說道。

Android中線程的分類

帶有消息隊列,用來執行循環性任務(例如主線程、android.os.HandlerThread)

有消息時就處理

沒有消息時就睡眠

沒有消息隊列,用來執行一次性任務(例如java.lang.Thread)

任務一旦執行完成便退出

帶有消息隊列線程概述

四要素

Message(消息)

MessageQueue(消息隊列)

Looper(消息循環)

Handler(消息發送和處理)

四要素的交互過程

具體工作過程

消息隊列的創建

消息循環

消息的發送

最基本的兩個API

Handler.sendMessage

帶一個Message參數,用來描述消息的內容

Handler.post

帶一個Runnable參數,會被轉換為一個Message參數

消息的處理

基於消息的異步任務接口

android.os.HandlerThread

適合用來處於不需要更新UI的後台任務

android.os.AyncTask

適合用來處於需要更新UI的後台任務

帶有消息隊列線程的具體實現

ThreadLocal

ThreadLocal並不是一個Thread,而是Thread的局部變量。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。

Looper

用於在指定線程中運行一個消息循環,一旦有新任務則執行,執行完繼續等待下一個任務,即變成Looper線程。Looper類的注釋裡有這樣一個例子:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        //將當前線程初始化為Looper線程
        Looper.prepare();

        // ...其他處理,如實例化handler
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        // 開始循環處理消息隊列
        Looper.loop();
    }
}

其實核心代碼就兩行,我們先來看下Looper.prepare()方法的具體實現

public final class Looper {

    private static final String TAG = "Looper";

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

    //Looper內的消息隊列
    final MessageQueue mQueue;
    // 當前線程
    final Thread mThread;

    private Printer mLogging;

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

     /** 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) {
        //試圖在有Looper的線程中再次創建Looper將拋出異常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

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

    //~省略部分無關代碼~
}

從中我們可以看到以下幾點:

prepare()其核心就是將looper對象定義為ThreadLocal 一個Thread只能有一個Looper對象 prepare()方法會調用Looper的構造方法,初始化一個消息隊列,並且指定當前線程 在調用Looper.loop()方法之前,確保已經調用了prepare(boolean quitAllowed)方法,並且我們可以調用quite方法結束循環

說到初始化MessageQueue,我們來看下它是干什麼的

/**
* 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.
*
*

You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/

它是一個低等級的持有Messages集合的類,被Looper分發。Messages並不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。我們可以通過Looper.myQueue()方法來檢索當前線程的MessageQueue。

接下來再看看Looper.loop()

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    //得到當前線程Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //得到當前looper的MessageQueue
    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);
        }

        //將真正的處理工作交給message的target,即handler
        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();
    }
}

通過這段代碼可知,調用loop方法後,Looper線程就開始真正工作了,它不斷從自己的MessageQueue中取出隊頭的消息(或者說是任務)執行

除了prepare()和loop()方法,Looper類還有一些比較有用的方法,比如

Looper.myLooper()得到當前線程looper對象

getThread()得到looper對象所屬線程

quit()方法結束looper循環

這裡需要注意的一點是,quit()方法其實調用的是MessageWueue的quite(boolean safe)方法。

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

我們看到其實主線程是不能調用這個方法退出消息隊列的。至於mQuitAllowed參數是在Looper初始化的時候初始化的,主線程初始化調用的是Looper.prepareMainLooper()方法,這個方法把參數設置為false。

Message

在整個消息處理機制中,message又叫task,封裝了任務攜帶的信息和處理該任務的handler。我們看下這個類的注釋

/**
*
* 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.
*
*

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.


*/

這個類定義了一個包含描述和一個任意類型對象的對象,它可以被發送給Handler。

從注釋裡我們還可以了解到以下幾點:

盡管Message有public的默認構造方法,但是你應該通過Message.obtain()來從消息池中獲得空消息對象,以節省資源。

如果你的message只需要攜帶簡單的int信息,請優先使用Message.arg1和Message.arg2來傳遞信息,這比用Bundle更省內存

用message.what來標識信息,以便用不同方式處理message。

Handler

從MessageQueue的注釋中,我們知道添加消息到消息隊列是通過Handler來操作的。我們通過源碼來看下具體是怎麼實現的

/**
* 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.
*
*

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.
*
*/

注釋比較簡單,這裡就不過多翻譯了,主要內容是:每一個Handler實例關聯了一個單一的ghread和這個thread的messagequeue,當Handler的實例被創建的時候它就被綁定到了創建它的thread。它用來調度message和runnables在未來某個時間點的執行,還可以排列其他線程裡執行的操作。

public class Handler {

    //~省略部分無關代碼~

    final MessageQueue mQueue;
    final Looper mLooper;

    public Handler() {
        this(null, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

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

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    //~省略部分無關代碼~
}

先看構造方法,其實裡邊的重點是初始化了兩個變量,把關聯looper的MessageQueue作為自己的MessageQueue,因此它的消息將發送到關聯looper的MessageQueue上

有了handler之後,我們就可以使用Handler提供的post和send系列方法向MessageQueue上發送消息了。其實post發出的Runnable對象最後都被封裝成message對象

接下來我們看一下handler是如何發送消息的

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is 
 * attached. 
 *  
 * @param r The Runnable that will be executed.
 * 
 * @return Returns true if the Runnable was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

/**
 * Enqueue a message into the message queue after all pending messages
 * before (current time + delayMillis). You will receive it in
 * {@link #handleMessage}, in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

/**
 * Enqueue a message into the message queue after all pending messages
 * before the absolute time (in milliseconds) uptimeMillis.
 * The time-base is {@link android.os.SystemClock#uptimeMillis}.
 * Time spent in deep sleep will add an additional delay to execution.
 * You will receive it in {@link #handleMessage}, in the thread attached
 * to this handler.
 * 
 * @param uptimeMillis The absolute time at which the message should be
 *         delivered, using the
 *         {@link android.os.SystemClock#uptimeMillis} time-base.
 *         
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
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);
}

這裡我們只列出了一種調用關系,其他調用關系大同小異,我們來分析一下

調用getPostMessage(r),把runnable對象添加到一個Message對象中。 sendMessageDelayed(getPostMessage(r), 0),基本沒做什麼操作,又繼續調用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在這個方法裡拿到創建這個Handler對象的線程持有的MessageQueue。 調用enqueueMessage(queue, msg, uptimeMillis)方法,給msg對象的target變量賦值為當前的Handler對象,然後放入到MessageQueue。

那發送消息說完了,那我們的消息是怎樣被處理的呢?

我們看到message.target為該handler對象,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵代碼。

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

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

private static void handleCallback(Message message) {
    message.callback.run();
}

我們看到這裡最終又調用到了我們重寫的handleMessage(Message msg)方法來做處理子線程發來的消息或者調用handleCallback(Message message)去執行我們子線程中定義並傳過來的操作。

思考

為什麼要有Handler機制

這個問題可以這麼考慮

我們如何在子線程更新UI?——使用Handler機制傳遞消息到主線程(UI線程) 為什麼我們不在子線程更新UI呢?——因為Android是單線程模型 為什麼要做成單線程模型呢?——多線程並發訪問UI可能會導致UI控件處於不可預期的狀態。如果加鎖,雖然能解決,但是缺點也很明顯:1.鎖機制讓UI訪問邏輯變得復雜;2.加鎖導致效率低下。

Handler機制與命令模式

我在之前分享過Android源碼中的命令模式,我們仔細分下一下不難看出Handler機制其實是一個非典型的命令模式

接收者:Handler,執行消息處理操作。

調用者:Looper,調用消息的的處理方法。

命令角色:Message,消息類。

客戶端:Thread,創建消息並綁定Handler(接受者)。

Android主線程是如何管理子線程消息的

我們知道Android上一個應用的入口,應該是ActivityThread。和普通的Java類一樣,入口是一個main方法。

public static void main(String[] args) {

    //~省略部分無關代碼~

    //創建Looper和MessageQueue對象,用於處理主線程的消息
    Looper.prepareMainLooper();

    //創建ActivityThread對象
    ActivityThread thread = new ActivityThread();

    //建立Binder通道 (創建新線程)
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    //消息循環運行
    Looper.loop();

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

我們可以看到其實我們在這裡初始化了我們主線程(UI)的Looper並且啟動它。然後就可以處理子線程和其他組件發來的消息了。

為什麼主線程不會因為Looper.loop()裡的死循環卡死或者不能處理其他事務

這裡涉及到的東西比較多,概括的理解是這樣的

為什麼不會卡死

handler機制是使用pipe來實現的,主線程沒有消息處理時會阻塞在管道的讀端。

binder線程會往主線程消息隊列裡添加消息,然後往管道寫端寫一個字節,這樣就能喚醒主線程從管道讀端返回,也就是說queue.next()會調用返回。

主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

既然是死循環又如何去處理其他事務呢?

答案是通過創建新線程的方式。

我們看到main方法裡調用了thread.attach(false),這裡便會創建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS發送來的事件),該Binder線程通過Handler將Message發送給主線程。

ActivityThread對應的Handler是一個內部類H,裡邊包含了啟動Activity、處理Activity生命周期等方法。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved