天天看點

Android中的消息機制Looper與MessageQueue子線程中建立Handler為何會抛出異常 ?總結

參考文章:

http://blog.csdn.net/bboyfeiyu/article/details/38555547

http://www.jianshu.com/p/6f25729ef62a

在Android應用啟動時,會預設有一個主線程(UI線程),在這個線程中會關聯一個消息隊列,所有的操作都會被封裝成消息然後交給主線程來處理。為了保證主線程不會主動退出,會将取消息的操作放在一個死循環中,這樣程式就相當于一直在執行死循環,是以不會退出。

示例圖如下 :

Android中的消息機制Looper與MessageQueue子線程中建立Handler為何會抛出異常 ?總結

Android應用程式的入口為ActivityThread.main方法:

public static void main(String[] args) {  
     SamplingProfilerIntegration.start();  
     CloseGuard.setEnabled(false);  

     Environment.initForCurrentUser();  

     // Set the reporter for event logging in libcore  
     EventLogger.setReporter(new EventLoggingReporter());  

     Process.setArgV0("<pre-initialized>");  

     Looper.prepareMainLooper();// 1、建立消息循環Looper  

     ActivityThread thread = new ActivityThread();  
     thread.attach(false);  

     if (sMainThreadHandler == null) {  
     sMainThreadHandler = thread.getHandler(); // UI線程的Handler  
     }  

     AsyncTask.init();  

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

     Looper.loop();   // 2、執行消息循環  

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

執行ActivityThread.main方法後,應用程式就啟動了,并且會一直從消息隊列中取消息,然後處理消息。那麼系統是如何将消息投遞到消息隊列中的?又是如何從消息隊列中擷取消息并且處理消息的呢? 答案就是Handler。

每個Handler都會關聯一個消息隊列,消息隊列被封裝在Lopper中,而每個Looper又會關聯一個線程(ThreadLocal),也就是每個消息隊列會關聯一個線程。Handler就是一個消息處理器,将消息投遞給消息隊列,然後再由對應的線程從消息隊列中挨個取出消息,并且執行。預設情況下,消息隊列隻有一個,即主線程的消息隊列,這個消息隊列是在ActivityThread.main方法中建立的,通過Lopper.prepareMainLooper()來建立,然後最後執行Looper.loop()來啟動消息循環。那麼Handler是如何關聯消息隊列以及線程的呢?我們看看如下源碼 :

public Handler() {  
    if (FIND_POTENTIAL_LEAKS) {  
    final Class<? extends Handler> 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();   // 擷取Looper  
    if (mLooper == null) {  
    throw new RuntimeException(  
    "Can't create handler inside thread that has not called Looper.prepare()");  
    }  
    mQueue = mLooper.mQueue;   // 擷取消息隊列  
    mCallback = null;  
}  
           

從Handler預設的構造函數中我們可以看到,Handler會在内部通過Looper.getLooper()來擷取Looper對象,并且與之關聯,最重要的就是消息隊列。那麼Looper.getLooper()又是如何工作的呢?我們繼續往下看.

/** 
 * Return the Looper object associated with the current thread.  Returns 
 * null if the calling thread is not associated with a Looper. 
 */  
public static Looper myLooper() {  
    return sThreadLocal.get();  
}  

/** 
 * 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();  
    setMainLooper(myLooper());  
    myLooper().mQueue.mQuitAllowed = false;  
    }  

    private synchronized static void setMainLooper(Looper looper) {  
    mMainLooper = looper;  
}  

 /** 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() {  
    if (sThreadLocal.get() != null) {  
    throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
}  
           

Looper與MessageQueue

建立了Looper後,如何執行消息循環呢?通過Handler來post消息給消息隊列( 連結清單 ),那麼消息是如何被處理的呢?答案就是在消息循環中,消息循環的建立就是通過Looper.loop()方法。源碼如下 :

/** 
 * Run the message queue in this thread. Be sure to call 
 * {@link #quit()} to end the loop. 
 */  
public static void loop() {  
    Looper me = myLooper();  
    if (me == null) {  
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
}  
MessageQueue queue = me.mQueue;// 1、擷取消息隊列  

// 代碼省略  


while (true) {   // 2、死循環,即消息循環  
    Message msg = queue.next(); // 3、擷取消息 (might block )  
    if (msg != null) {  
        if (msg.target == null) {  
                // No target is a magic identifier for the quit message.  
                return;  
            }  

            long wallStart = 0;  
            long threadStart = 0;  

            // 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);  
                wallStart = SystemClock.currentTimeMicro();  
                threadStart = SystemClock.currentThreadTimeMicro();  
            }  

            msg.target.dispatchMessage(msg);// 4、處理消息  

            // 代碼省略  

            msg.recycle();  
        }  
    }  
}  
           

可以看到,loop方法中實質上就是建立一個死循環,然後通過從消息隊列中挨個取出消息,最後處理消息的過程。對于Looper我們總結一下 : 通過Looper.prepare()來建立Looper對象(消息隊列封裝在Looper對象中),并且儲存在sThreadLoal中,然後通過Looper.loop()來執行消息循環,這兩步通常是成對出現的!! 最後我們看看消息處理機制,我們看到代碼中第4步通過msg.target.dispatchMessage(msg)來處理消息。其中msg是Message類型,我們看源碼 :

public final class Message implements Parcelable {  

    public int what;  

    public int arg1;   

    public int arg2;  

    public Object obj;  


    int flags;  

    long when;  

    Bundle data;  

    Handler target; // target處理  

    Runnable callback;  // Runnable類型的callback  

    // sometimes we store linked lists of these things  
    Message next;   // 下一條消息,消息隊列是鍊式存儲的  


    // 代碼省略 ....  
}
           

從源碼中可以看到,target是Handler類型。實際上就是轉了一圈,通過Handler将消息投遞給消息隊列,消息隊列又将消息分發給Handler來處理。我們繼續看:

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

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

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

可以看到,dispatchMessage隻是一個分發的方法,如果Runnable類型的callback為空則執行handlerMessage來處理消息,該方法為空,我們會将更新UI的代碼寫在該函數中;如果callback不為空,則執行handleCallback來處理,該方法會調用callback的run方法。其實這是Handler分發的兩種類型,比如我們post(Runnable callback)則callback就不為空,當我們使用Handler來sendMessage時通常不會設定callback,是以也就執行handlerMessage這個分支。我們看看兩種實作:

public final boolean post(Runnable r)  
{  
   return  sendMessageDelayed(getPostMessage(r), 0);  
}  
private final Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();  
    m.callback = r;  
    return m;  
}  

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

public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
{  
    boolean sent = false;  
    MessageQueue queue = mQueue;  
    if (queue != null) {  
        msg.target = this;  // 設定消息的target為目前Handler對象  
        sent = queue.enqueueMessage(msg, uptimeMillis);  // 将消息插入到消息隊列  
    }  
    else {  
        RuntimeException e = new RuntimeException(  
        this + " sendMessageAtTime() called with no mQueue");  
        Log.w("Looper", e.getMessage(), e);  
    }  
    return sent;  
}   
           

可以看到,在post(Runnable r)時,會将Runnable包裝成Message對象,并且将Runnable對象設定給Message對象的callback字段,最後會将該Message對象插入消息隊列。sendMessage也是類似實作 :

public final boolean sendMessage(Message msg)  
{  
    return sendMessageDelayed(msg, 0);  
}
           

子線程中建立Handler為何會抛出異常 ?

“Can’t create handler inside thread that has not called Looper.prepare()”異常

解決:

new Thread(){  
    Handler handler = null;  
    public void run() {  
        Looper.prepare();    // 1、建立Looper,并且會綁定到ThreadLocal中  
        handler = new Handler();  
        Looper.loop();       // 2、啟動消息循環  
    };  
}.start(); 
           

在代碼中我們加了2處,第一是通過Looper.prepare()來建立Looper,第二是通過Looper.loop()來啟動消息循環。這樣該線程就有了自己的Looper,也就是有了自己的消息隊列。如果之建立Looper,而不啟動消息循環,雖然不會抛出異常,但是你通過handler來post或者sendMessage也不會有效,因為雖然消息被追加到消息隊列了,但是并沒有啟動消息循環,也就不會從消息隊列中擷取消息并且執行了!

總結

在應用啟動時,會開啟一個主線程(UI線程),并且啟動消息循環,應用不停地從該消息隊列中取出、處理消息達到程式運作的效果。Looper對象封裝了消息隊列,Looper對象是ThreadLocal的,不同線程之間的Looper對象不能共享與通路。而Handler通過與Looper對象綁定來實作與執行線程的綁定,handler會把Runnable(包裝成Message)或者Message對象追加到與線程關聯的消息隊列中,然後在消息循環中挨個取出消息,并且處理消息。當Handler綁定的Looper是主線程的Looper,則該Handler可以在handleMessage中更新UI,否則更新UI則會抛出異常!其實我們可以把Handler、Looper、Thread想象成一個生産線,勞工(搬運工)相當于Handler,負責将貨物搬到傳輸帶上(Handler将消息傳遞給消息隊列);傳送帶扮演消息隊列的角色,負責傳遞貨物,貨物會被挨取出,并且輸送到目的地 ( target來處理 );而貨物到達某個工廠中的房間後再被勞工處理,工廠中的房間就扮演了Thread這個角色,每個工廠中的房間有自己獨立的傳送帶,工廠中的房間A的貨物不能被工廠中的房間B的拿到,即相當于ThreadLocal( 工廠中的房間獨有 )。