編輯:關於Android編程
什麼是Android消息機制?從開發者使用的角度,消息機制只有一個上層接口,就是我們經常使用的Handler,通過Handler可以輕松的將任務切換到Handler所在的線程去執行,而最常見的做法相信每個開發者都用過,就是更新UI。Android的UI控件的操作只能在主線程(也叫UI線程)中執行,但我們經常在子線程或者線程池做一些耗時或者聯網的工作,當工作完成我們希望在UI中做一些更新,比如關閉對話框、改變按鈕顯示等等。那麼最常見也是比較容易的方法就是創建一個Handler對象,將操作在Handler的handleMessage函數中執行(也就是在主線程中),只需要在子線程發送消息,就可以輕松切換到主線程。當然我們也可以看出Handler不是專門來做此操作才出現的,只是非常適合這個情況,那麼Handler是如何輕松切換到主線程的呢?為什麼Handler就可以在主線程中創建呢?我們從源碼的角度來做一次全面的分析。
一、消息機制的角色分析
首先我們介紹一下消息機制的幾位主人公,正是有它們的通力合作,消息機制才能正常的運行:
1、Handler:處理器,負責的內容是消息的發送和具體處理流程,一般使用時由開發者重寫handleMessage函數,根據自定義的不同message做不同的UI更新操作;
2、Message:消息對象,代表一個消息實體,存放消息的基本內容;
3、MessageQueue:消息隊列,按順序存放消息實體,由單鏈表實現,被Looper(4)所使用(一個Looper具有一個消息隊列);
4、Looper:循環器,也是我們的消息機制的主角,一個線程至多具有一個並且與線程一一對應(如何理解等會來說),負責的內容是不斷從消息隊列取出放入的消息並且交由消息對應的Handler進行處理(Handler的具體handleMessage操作);
5、ThreadLocal:線程本地數據,是一個線程內部的數據存儲類,為每個線程存儲屬於自己的獨立的數據。
二、消息機制總覽
我們來用最簡短的語言說明一下消息循環的整個過程,有個整體性的認識,之後再進行逐一的進行源碼分析。
1、首先,我們知道ThreadLocal是線程內部的數據存儲類,一個線程對應一個自己獨一無二的數據,而我們的主角Looper就是這樣一個對象,每個線程都可以有自己獨一無二的Looper,而Looper自己具有一個屬於自己的MessageQueue,它不斷地從MessageQueue中取Message(注意,Looper的代碼是運行在自己對應的線程中的),如果MessageQueue中沒有消息,Looper只會不斷地循環嘗試取消息(阻塞)。
2、這時,我們在主線程創建了Handler對象,它需要它所在的線程(這裡是主線程)所擁有的Looper對象(也就是說,沒有Looper的線程創建不了Handler,後面我們也會看到具體代碼),一旦我們用Handler發送了消息(可以在別的線程中,這裡假設在某個子線程中),Handler就會把這個消息放入自己擁有的Looper對象的屬於這個Looper對象的MessageQueue中(這句有點拗口,就是放入Looper的MessageQueue中)。
3、我們已經知道Looper會不斷地嘗試從自己的MessageQueue中取出消息,我們剛剛放入的消息立刻被Looper取出來了,它得到了消息就執行了發出消息的Handler(也就是2過程我們所創建的)的消息處理函數handleMessage,我們編寫的UI更新操作就在Looper對象的代碼中執行了,而我們剛才也說了這個Looper運行在我們創建Handler的線程中的,也就是主線程(UI線程),那這樣一來就達到了我們的目標,成功的把代碼執行從子線程切換到了主線程中,這個過程我們也就有個總覽了,下面我們進行源碼的一步一步分析。
三、MessageQueue與Looper
1、MessageQueue
MessageQueue的作用我們已經大致了解了,就是一個可以存放消息和可以取消息的消息隊列,那麼它主要的兩個操作就是插入和讀取,分別對應的函數就是enqueueMessage函數和next函數,分別是用作插入消息和讀取並刪除消息,我們來看一下具體的實現源碼:
(1)enqueueMessage方法
final boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null) { throw new AndroidRuntimeException("Message must have a target."); } boolean needWake; synchronized (this) { if (mQuiting) { return false; } msg.when = when; Message p = mMessages; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } } if (needWake) { nativeWake(mPtr); } return true; }
首先,進行消息的合理性的檢查,防止兩個非法情況:消息已經在使用中和消息沒有target(也就是我們發出消息的Handler,它是作為target最終要來真正做相應處理);之後,進行單鏈表的插入操作。插入完成之後,我們會看到這樣一個操作:
if (needWake) { nativeWake(mPtr); }
這個needWake和本地方法nativeWake(mPtr)有一些關系,同樣有關的還有插入過程的一些if語句,但這裡對我們了解整體機制沒有非常重要的作用,本地方法也非常復雜,要與next方法中的本地方法一起講解,在講完整體機制後我們再詳細介紹。
(2)next方法
省略了部分非關鍵代碼(用於一些優化操作)
final Message next() { int pendingIdleHandlerCount = -1; //空閒處理器的個數 int nextPollTimeoutMillis = 0; //等待時間 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(mPtr, nextPollTimeoutMillis); //本地方法調用 synchronized (this) { if (mQuiting) { return null; } // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ... }
next的作用就是為了獲取隊列中的被放入的消息,我們先介紹一下變量mMessages,這個其實就是MessageQueue的鏈表頭,也就是最先需要處理的消息;接著我們可以看出next是一個阻塞方法,如果沒有尋找到需要處理的消息,即msg == null的情況,那麼next方法一直在無限循環中;如果尋找到了位於鏈表頭的消息,還要判斷是否處於需要執行的時間,如果這些都滿足了要求,就把message取走(移除)並返回,關鍵代碼如下:
// Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); msg.markInUse(); return msg;
這裡有本地方法上面我們也說到了,其實涉及到了底層的一些處理,其中最重要的就是這句函數:
nativePollOnce(mPtr, nextPollTimeoutMillis);
並且我們還有兩個變量的作用沒有講解,就是next開頭創建的兩個變量:
int pendingIdleHandlerCount = -1; //空閒處理器的個數 int nextPollTimeoutMillis = 0; //等待時間
這兩個變量的作用我們也沒有講解,他們與這個本地方法有著非常重要的聯系,我們會在底層源碼分析中進行介紹,在分析完整體機制之後會著重分析MessageQueue的底層部分,先不著急,我們現在只需要確定MessageQueue是作為一個存放消息的消息隊列就行了,但實際上它在消息循環中的地位非常的特殊,講完整體框架我們再細看這個類的實現,從整體來看我們只需要大致了解它添加、讀取的作用即可。
2、Looper
Looper作為我們消息處理的主人公,在消息處理中起了非常大的作用,我們來好好看一下Looper的具體實現。Looper擁有一個屬於自己的MessageQueue,並且一直查看其中是否有新的消息,如果有就會立刻進行處理,否則就會一直阻塞在循環中。
(1)首先看一看Looper擁有的MessageQueue
//成員變量之一 final MessageQueue mQueue; //私有構造函數 private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
這裡可以看出,每個Looper對象都有自己的MessageQueue,在構造函數中初始化。
(2)Looper的初始化過程
既然Looper是一個與線程有關的變量,它在哪裡初始化呢?前面我也說過,一個線程沒有Looper就不能創建Handler,那我們平時怎麼沒接觸到Looper的初始化呢?因為我們那時在主線程中創建的Handler,而主線程本身就有一個主消息循環(MainLooper),就不用我們自己去創建了。我們來看一看如何在子線程中初始化Looper,開啟屬於自己的消息循環:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare();//給線程創建一個消息循環 mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop();//開啟消息循環 } }
這裡用到了兩個頗為關鍵的方法,prepare和loop,我們來詳細的看一下
2.1 prepare方法
//靜態成員變量之一 private static final ThreadLocal sThreadLocal = new ThreadLocal(); //靜態方法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()); //放入新的Looper對象(對應當前線程) }
首先我們要明確一下剛才說的另一個小主角:ThreadLocal,它的作用就是為每個線程存儲屬於每個線程自己的數據,這裡的sThreadLocal就是為每個線程存儲屬於自己的Looper變量,prepare方法做了什麼呢?首先判斷當前線程是否已經有Looper變量,如果有就報異常,因為每個線程不能有多個Looper對象;然後為當前線程創建屬於自己的Looper對象,並保存到sThreadLocal變量中,這樣當前線程就具有了屬於自己的Looper。
2.2 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; ... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); msg.recycle(); } }
終於看到loop方法的真面目,首先我們要明確一個重要的觀點,loop函數是運行在Looper所在的線程中的,比如我們的loop就是運行在我們的LooperThread中,而主線程得loop方法就是運行在主線程中。我們來慢慢看loop的操作:
步驟1、得到當前所在線程的Looper對象,調用了函數myLooper
final Looper me = myLooper();
這個myLooper的作用就是獲取當前線程的Looper對象,相信大家估計都知道是怎麼實現的了:
//靜態成員變量之一 private static final ThreadLocal sThreadLocal = new ThreadLocal(); //靜態方法 public static final Looper myLooper() { return (Looper)sThreadLocal.get(); }
其作用就是獲取當前線程的Looper對象,從代碼中我們可以很清楚的看到,從靜態成員變量ThreadLocal中獲取當前線程的Looper對象,我們接著往下看。
步驟2:
if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }
這裡做一個簡單的判斷,如果沒有調用之前的Looper.prepare()進行Looper對象的創建,這裡就會觸發異常,下面進入關鍵的loop階段:
步驟3:
首先獲取了當前線程Looper對象的MessageQueue,緊接著開始循環,在循環中不斷進行獲取message的嘗試:
final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); //阻塞方法,可能阻塞 if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); //Handler的消息處理函數執行 msg.recycle(); }
MessageQueue的next方法我們剛才已經分析過了,這是一個阻塞方法,如果沒有Message會一直阻塞;一旦獲取到了Message,首先判斷是否為null,這個的作用就是用來判斷Looper的退出,當Looper調用quit方法,就會調用MessageQueue的quit方法,它的next就會返回null,然後loop方法就return,即退出了循環,達到了退出的作用。一旦獲取到了不為null的Message,就會在loop函數中調用msg的target的dispatchMessage函數,Message我們知道,那這個target是什麼呢?我們來看一看Message的成員變量中的target:
Handler target; // 發送和處理消息的 Handler
原來target就是我們的Handler對象,也就是我們下一個要介紹的主人公,現在我們只要知道一點,這個Handler的dispatchMessage函數是在loop中執行的,也就是Looper所在的線程中執行的(也可以說是Looper對應的線程),其實這裡已經完成了我們所熟知的線程切換,Handler發送消息的線程已經切換到執行消息處理的Looper所在線程(熟知的主線程)了。
四、Handler
終於要說到我們經常使用的Handler類了,說了前面這麼多,相信大家對Handler在消息循環中的地位也有了一定的認識。我們都知道真正消息處理的代碼是寫在Handler中的,但Looper還是真正的主角,畢竟Handler中的處理函數還是Looper調用的,可以說一切的獲取Message、執行處理都是Looper調用,Handler只是在別的線程中“發出”一個Message而已,之後的執行通過對Looper的分析我們都知道是Looper的活。這裡為什麼說“發出”要加雙引號更多的是因為不是真正意義的發出,通過下面的分析我們可以知道,其實真正的操作只是在Looper的MessageQueue中加入新的Message對象而已。
首先來看一看Handler的構造函數,也是我們創建Handler的調用之一:
(1)Handler構造函數
public Handler(){ ... 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的默認構造函數做的第一件事就是獲取當前線程的Looper對象,如果沒有Looper對象自然也創建不了默認的Handler對象(前面的結論在這裡得到了印證);第二件事就是獲取當前線程的Looper的MessageQueue,我們記住這個mQueue,實際上大家肯定明白為什麼要這個mQueue,其實就是為了插入消息才獲取的;第三個這個mCallback是干嘛的呢,我們往下看就知道了。
(2)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){ boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; //把target設置為自己,這樣我們就知道Message的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; }
無論是哪種sendMessage方法,最終都會調用sendMessageAtTime函數,我們來看看它做了哪些操作:
步驟一:獲取消息隊列,其實就是Handler初始化時獲取到的
MessageQueue queue = mQueue;
步驟二:當隊列不為空時(正常情況),做了兩件事,第一件事把消息的target設置為自己,這也印證了我們之前講到的msg.target對象;第二件事就是簡單的隊列添加操作,調用之前分析過的enqueueMessage函數,關鍵代碼如下:
if (queue != null) { msg.target = this; //1.把target設置為自己,這樣我們就知道Message的target就是發送它的Handler sent = queue.enqueueMessage(msg, uptimeMillis); //2.隊列的添加消息操作 }
(3)dispatchMessage函數
分析了前面那麼多,終於要說到我們分析loop函數時調用的那句msg.target.dispatchMessage(msg);中的關鍵函數dispatchMessage,讓我們來看看代碼,但大家一定要清楚,這句話是運行在Looper所屬於的線程中的喲。
public void dispatchMessage(Message msg) { //1.如果msg本身設置了callback,則直接交給這個msg的callback處理了 if (msg.callback != null) { handleCallback(msg); } else { //2.如果該handler的callback有的話,則交給這個callback處理了 if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //3.在msg和自身都沒有callback 的情況下,運行自己的handleMessage(是由我們重寫的handleMessage,Handler本身的handleMessage什麼也不會干) handleMessage(msg); }
其實我的注釋已經把能說的都說了,我簡單的解釋一下:
步驟1:首先就是msg是否有callback,而這個callback我們可以看Message的源碼中:
Runnable callback; // 處理消息的回調
可以看到這是一個Runnable對象,實際就是Handler的post函數所傳遞的參數,它的調用也非常簡單,handleCallback函數只做了一件事:
private static void handleCallback(Message message) { message.callback.run(); }
實際上就是針對post方法來進行執行而已,這裡延伸一下post方法,我們知道他也是Handler提交任務的一種方式,具體過程如下:
handler.post(new Runnable(){ @Override public void run() { //do something }});
那麼這個post如何實現呢?
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
又回到了我們剛才的sendMessageDelayed函數,相信大家對於getPostMessage是干嘛的也應該有一定想法了,不就是創建Runnable對應的Message嘛,對,我們來看一下實現:
private final Message getPostMessage(Runnable r) { Message m = Message.obtain(); //新建一個Message m.callback = r; //設置Runnable對象 return m; }
這下知道了這個post是干嘛的了吧,我們也可以知道上面的msg的callback是怎麼來的了。
步驟2:檢查Handler本身是否有callback,也就是我們之前在初始化函數中說到的mCallback對象,它是一個回調接口,定義如下:
public interface Callback { public boolean handleMessage(Message msg); }
那他是干嘛的呢?實際上他就是Handler使用的另外一種方式,我們平時都是使用派生Handler的子類,重寫handleMessage函數來實現自己的Handler;而另外一種方式就是這個Callback接口,通過傳入實現的Callback接口就可以實現Handler處理的自定義,它的另一個構造函數如下所示:
public Handler(Callback callback) { ... 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 = callback; }
這就是通過實現Callback來進行創建Handler的過程,只不過多了一個mCallback的設置而已,如果你設置了callback,那麼就可以直接進行消息的處理。
步驟3:在上面兩步都沒有執行的情況下,進行消息處理,這裡就會調用我們熟悉的handleMessage函數了,你的重寫方法真正的得到了執行:
handleMessage(msg);
這樣我們就清楚了整個任務調用流程。
五、主線程消息循環的開啟
上面我們也說了,之所以你可以不用Looper就創建Handler的原因就是因為你在主線程中進行的操作,而主線程會自己創建Looper循環,那具體在哪裡呢,就在主線程的入口函數main中:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ...//省略中間代碼 Looper.loop(); }
那這個prepareMainLooper函數是怎麼實現的呢?我們來看一看:
public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); ... } private synchronized static void setMainLooper(Looper looper) { mMainLooper = looper; }
我們可以看出主要工作就是進行Looper的准備並設置MainLooper對象。
總結:通過整體的流程分析和各個類的關鍵函數分析,我們已經知道了Handler線程切換的秘密了,其實Handler並不是真正的主角,真正重要的是Looper和MessageQueue,它們才是線程切換的真正主角,但我前面提到的MessageQueue我也說了,它有著非常重要的作用,具體的作用就離不開它的兩個本地方法,其實在C++層,MessageQueue做了更多的操作來進行處理,這個底層部分我們在下一篇分析中來慢慢看。不過通過這第一篇的分析,我們已經對消息處理機制有了總體的了解,起碼知道Handler的實現比使用要復雜的多。
背景介紹最近負責的Gallery模塊測試時頻繁報出使用一段時間後就發生Crash,通過Log查看發現是OOM引發,本篇文檔記錄解決該問題的分析過程。Log分析該OOM問題
因為開發android的語言為java語言,所以開發android應用程序是建立在java平台上面。在此之前要確保我們已經安裝配置好了JDK(Java SE Develo
在研究如何實現Pushing功能期間,收集了很多關於Pushing的資料,其中有一個androidnp開源項目用的人比較多,但是由於長時間沒有什麼人去維護,聽說bug的幾
利用TabHost創建標簽式的版面設置,進行不同標簽的切換,顯示不同的背景圖片。效果如下所示: 1、添加6張圖片資源(直接拖入drawable文件夾),分別為gra