Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 消息處理機制(Looper、Handler、MessageQueue,Message)

Android 消息處理機制(Looper、Handler、MessageQueue,Message)

編輯:關於Android編程

1.Android消息處理機制:

消息處理機制本質:一個線程開啟循環模式持續監聽並依次處理其他線程給它發的消息。

簡單的說:一個線程開啟一個無限循環模式,不斷遍歷自己的消息列表,如果有消息就挨個拿出來做處理,如果列表沒消息,自己就堵塞(相當於wait,讓出cpu資源給其他線程),其他線程如果想讓該線程做什麼事,就往該線程的消息隊列插入消息,該線程會不斷從隊列裡拿出消息做處理。

2.Looper、Handler、MessageQueue,Message作用和存在的意義:

(1)Looper

我們知道一個線程是一段可執行的代碼,當可執行代碼執行完成後,線程生命周期便會終止,線程就會退出,那麼做為App的主線程,如果代碼段執行完了會怎樣?,那麼就會出現App啟動後執行一段代碼後就自動退出了,這是很不合理的。所以為了防止代碼段被執行完,只能在代碼中插入一個死循環,那麼代碼就不會被執行完,然後自動退出,怎麼在在代碼中插入一個死循環呢?那麼Looper出現了,在主線程中調用Looper.prepare()...Looper.loop()就會把當前線程變成Looper線程(可以先簡單理解:無限循環不退出的線程),Looper.loop()方法裡面有一段死循環的代碼,所以主線程會進入while(true){...}的代碼段跳不出來,但是主線程也不能什麼都不做吧?其實所有做的事情都在while(true){...}裡面做了,主線程會在死循環中不斷等其他線程給它發消息(消息包括:Activity啟動,生命周期,更新UI,控件事件等),一有消息就根據消息做相應的處理,Looper的另外一部分工作就是在循環代碼中會不斷從消息隊列挨個拿出消息給主線程處理。

(2)MessageQueue

MessageQueue 存在的原因很簡單,就是同一線程在同一時間只能處理一個消息,同一線程代碼執行是不具有並發性,所以需要隊列來保存消息和安排每個消息的處理順序。多個其他線程往UI線程發送消息,UI線程必須把這些消息保持到一個列表(它同一時間不能處理那麼多任務),然後挨個拿出來處理,這種設計很簡單,我們平時寫代碼其實也經常這麼做。每一個Looper線程都會維護這樣一個隊列,而且僅此一個,這個隊列的消息只能由該線程處理。

(3)Handler

簡單說Handler用於同一個進程的線程間通信。Looper讓主線程無限循環地從自己的MessageQueue拿出消息處理,既然這樣我們就知道處理消息肯定是在主線程中處理的,那麼怎樣在其他的線程往主線程的隊列裡放入消息呢?其實很簡單,我們知道在同一進程中線程和線程之間資源是共享的,也就是對於任何變量在任何線程都是可以訪問和修改的,只要考慮並發性做好同步就行了,那麼只要拿到MessageQueue 的實例,就可以往主線程的MessageQueue放入消息,主線程在輪詢的時候就會在主線程處理這個消息。那麼怎麼拿到主線程 MessageQueue的實例,是可以拿到的(在主線程下
mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 為了統一添加消息和消息的回調處理,又專門構建了Handler類,你只要在主線程構建Handler類,那麼這個Handler實例就獲取主線程MessageQueue實例的引用(獲取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的時候就通過這個引用往消息隊列裡插入新消息。Handler 的另外一個作用,就是能統一處理消息的回調。這樣一個Handler發出消息又確保消息處理也是自己來做,這樣的設計非常的贊。具體做法就是在隊列裡面的Message持有Handler的引用(哪個handler 把它放到隊列裡,message就持有了這個handler的引用),然後等到主線程輪詢到這個message的時候,就來回調我們經常重寫的Handler的handleMessage(Message msg)方法。



(4)Message

Message 很簡單了,你想讓主線程做什麼事,總要告訴它吧,總要傳遞點數據給它吧,Message就是這個載體。

源碼解析:

1.Looper :

public final class Looper {
    // sThreadLocal 是static的變量,可以先簡單理解它相當於map,key是線程,value是Looper,
    //那麼你只要用當前的線程就能通過sThreadLocal獲取當前線程所屬的Looper。
    static final ThreadLocal sThreadLocal = new ThreadLocal();
    //主線程(UI線程)的Looper 單獨處理,是static類型的,通過下面的方法getMainLooper() 
    //可以方便的獲取主線程的Looper。
    private static Looper sMainLooper; 

    //Looper 所屬的線程的消息隊列
    final MessageQueue mQueue;
    //Looper 所屬的線程
    final Thread mThread;

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
         //如果線程的TLS已有數據,則會拋出異常,一個線程只能有一個Looper,prepare不能重復調用。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //往線程的TLS插入數據,簡單理解相當於map.put(Thread.currentThread(),new Looper(quitAllowed));
        sThreadLocal.set(new Looper(quitAllowed));
    }

    //實際上是調用  prepare(false),並然後給sMainLooper賦值。
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    //static 方法,方便獲取主線程的Looper.
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    public static @Nullable Looper myLooper() {
        //具體看ThreadLocal類的源碼的get方法,
        //簡單理解相當於map.get(Thread.currentThread()) 獲取當前線程的Looper
        return sThreadLocal.get();
    }
}

注:看之前先稍微了解下ThreadLocal是什麼?
ThreadLocal: 線程本地存儲區(Thread Local Storage,簡稱為TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。這裡線程自己的本地存儲區域存放是線程自己的Looper。具體看下ThreadLocal.java 的源碼!

    看了上面的代碼(仔細看下注釋),我們發現 Looper.prepareMainLooper()做的事件就是new了一個Looper實例並放入Looper類下面一個static的ThreadLocal sThreadLocal靜態變量中,同時給sMainLooper賦值,給sMainLooper賦值是為了方便通過Looper.getMainLooper()快速獲取主線程的Looper,sMainLooper是主線程的Looper可能獲取會比較頻繁,避免每次都到 sThreadLocal 去查找獲取。

Looper的構造函數:

    private Looper(boolean quitAllowed) {  
            mQueue = new MessageQueue(quitAllowed);  
            mRun = true;  
            mThread = Thread.currentThread();  
    }  
這時候給當前線程創建了消息隊列MessageQueue,並且讓Looper持有MessageQueue的引用。執行完Looper.prepareMainLooper() 之後,主線程從普通線程轉成一個Looper線程。目前的主線程線程已經有一個Looper對象和一個消息隊列mQueue

prepare()方法:

    public static final void prepare() {  
            if (sThreadLocal.get() != null) {  
                throw new RuntimeException("Only one Looper may be created per thread");  
            }  
            sThreadLocal.set(new Looper(true));  
    }  
sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變量。可以看到,在第5行,將一個Looper的實例放入了ThreadLocal,並且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說明了Looper.prepare()方法不能被調用兩次,同時也保證了一個線程中只有一個Looper實例

loop()方法:

public static void loop() {
    final Looper me = myLooper();  //獲取TLS存儲的Looper對象,獲取當前線程的Looper 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    final MessageQueue queue = me.mQueue;  //獲取Looper對象中的消息隊列
    ....

    for (;;) { //主線程開啟無限循環模式
        Message msg = queue.next(); //獲取隊列中的下一條消息,可能會線程阻塞
        if (msg == null) { //沒有消息,則退出循環,退出消息循環,那麼你的程序也就可以退出了
            return;
        }
        ....
        //分發Message,msg.target 是一個Handler對象,哪個Handler把這個Message發到隊列裡,
        //這個Message會持有這個Handler的引用,並放到自己的target變量中,這樣就可以回調我們重寫
        //的handler的handleMessage方法。
        msg.target.dispatchMessage(msg);
        ....
        ....
        msg.recycleUnchecked();  //將Message回收到消息池,下次要用的時候不需要重新創建,obtain()就可以了。
    }
}
         loop()方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。

public static Looper myLooper() {
return sThreadLocal.get();
}
方法直接返回了sThreadLocal存儲的Looper實例,如果me為null則拋出異常,也就是說looper方法必須在prepare方法之後運行



   問題:
     1、UI線程一直在這個循環裡跳不出來,主線程不會因為Looper.loop()裡的死循環卡死嗎,那還怎麼執行其他的操作呢?

在looper啟動後,主線程上執行的任何代碼都是被looper從消息隊列裡取出來執行的。也就是說主線程之後都是通過其他線程給它發消息來實現執行其他操作的。生命周期的回調也是如此的,系統服務ActivityManagerService通過Binder發送IPC調用給APP進程,App進程接到到調用後,通過App進程的Binder線程給主線程的消息隊列插入一條消息來實現的。

2、主線程是UI線程和用戶交互的線程,優先級應該很高,主線程的死循環一直運行是不是會特別消耗CPU資源嗎?App進程的其他線程怎麼辦?

這基本是一個類似生產者消費者的模型,簡單說如果在主線程的MessageQueue沒有消息時,就會阻塞在loop的queue.next()方法裡,這時候主線程會釋放CPU資源進入休眠狀態,直到有下個消息進來時候就會喚醒主線程,在2.2 版本以前,這套機制是用我們熟悉的線程的wait和notify 來實現的,之後的版本涉及到Linux pipe/epoll機制,通過往pipe管道寫端寫入數據來喚醒主線程工作。原理類似於I/O,讀寫是堵塞的,不占用CPU資源。

2.Handler

 public Handler() {
        this(null, false);
 }
 public Handler(Callback callback, boolean async) {
        //不是static 發出可能內存洩露的警告!
        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());
            }
        }
        //獲取當前線程的Looper,還記得前面講過 Looper.myLooper()方法了嗎?
        //Looper.myLooper()內部實現可以先簡單理解成:map.get(Thread.currentThread()) 
        //獲取當前線程的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            //當前線程不是Looper 線程,沒有調用Looper.prepare()給線程創建Looper對象
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //讓Handler 持有當前線程消息隊列的引用
        mQueue = mLooper.mQueue;
        //這些callback先不管,主要用於handler的消息發送的回調,優先級是比handlerMessage高,但是不常用
        mCallback = callback;
        mAsynchronous = async;
    }

通過Looper.myLooper()獲取了當前線程保存的Looper實例,然後在19行又獲取了這個Looper實例中保存的MessageQueue(消息隊列),這樣就保證了handler的實例與我們Looper實例中MessageQueue關聯上了。

sendMessage方法:

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

       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) {  
           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);  
       }  
輾轉反則最後調用了sendMessageAtTime,在此方法內部有直接獲取MessageQueue然後調用了enqueueMessage方法,我們再來看看此方法:
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  
           msg.target = this;  
           if (mAsynchronous) {  
               msg.setAsynchronous(true);  
           }  
           return queue.enqueueMessage(msg, uptimeMillis);  
       }  
enqueueMessage中首先為meg.target賦值為this,【如果大家還記得Looper的loop方法會取出每個msg然後交給msg,target.dispatchMessage(msg)去處理消息】,也就是把當前的handler作為msg的target屬性。最終會調用queue的enqueueMessage的方法,也就是說handler發出的消息,最終會保存到消息隊列中去。


現在已經很清楚了Looper會調用prepare()和loop()方法,在當前執行的線程中保存一個Looper實例,這個實例會保存一個MessageQueue對象,然後當前線程進入一個無限循環中去,不斷從MessageQueue中讀取Handler發來的消息。然後再回調創建這個消息的handler中的dispathMessage方法,下面我們趕快去看一看這個方法:
    public void dispatchMessage(Message msg) {  
            if (msg.callback != null) {  
                handleCallback(msg);  
            } else {  
                if (mCallback != null) {  
                    if (mCallback.handleMessage(msg)) {  
                        return;  
                    }  
                }  
                handleMessage(msg);  
            }  
        }  

這裡調用了handleMessage方法

/** 
   * Subclasses must implement this to receive messages. 
   */  
  public void handleMessage(Message msg) {  
  }  
可以看到這是一個空方法,為什麼呢,因為消息的最終回調是由我們控制的,我們在創建handler的時候都是復寫handleMessage方法,然後根據msg.what進行消息處理。

例如:
    private Handler mHandler = new Handler()  
        {  
            public void handleMessage(android.os.Message msg)  
            {  
                switch (msg.what)  
                {  
                case value:  

                    break;  

                default:  
                    break;  
                }  
            };  
        };  

1、首先Looper.prepare()在本線程中保存一個Looper實例,然後該實例中保存一個MessageQueue對象;因為Looper.prepare()在一個線程中只能調用一次,所以MessageQueue在一個線程中只會存在一個。

2、Looper.loop()會讓當前線程進入一個無限循環,不端從MessageQueue的實例中讀取消息,然後回調msg.target.dispatchMessage(msg)方法。

3、Handler的構造方法,會首先得到當前線程中保存的Looper實例,進而與Looper實例中的MessageQueue想關聯。

4、Handler的sendMessage方法,會給msg的target賦值為handler自身,然後加入MessageQueue中。

5、在構造Handler實例時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調用的方法。

好了,總結完成,大家可能還會問,那麼在Activity中,我們並沒有顯示的調用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創建呢,這是因為在Activity的啟動代碼中,已經在當前UI線程調用了Looper.prepare()和Looper.loop()方法。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved