Android---Handler消息處理機制
搞Android的人都知道。android是不同意你在子線程中更新UI操作的。這主要出於線程安全方面的考慮。通常的做法是在主線程中創建一個Handler對象,在子線程中創建一個Message對象。該Message對象中封裝一些更新UI操作的數據,通過Handler的sendMessage方法發送出去,主線程利用Handler的handleMessage方法來對該Message進行對應的處理。但發現沒有,子線程調用Handler的sendMessage發出Message之後,消息是怎麽傳遞到主線程的handleMessage方法裏面進而進行處理的呢。接下來我們從源代碼角度慢慢進行分析:
先來看看尋常我們是怎麽使用handler的:
實例1:
public class MainActivity extends Activity { public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: System.out.println("接收到空消息"); break; default: break; } } }; new Thread(new Runnable() { @Override public void run() { mHandler.sendEmptyMessage(1); } }).start(); } }
解釋:
能夠看到。在子線程中我們使用的是主線程的Handler來進行sendMessage的,那麽問題來了。子線程中能夠存在自己的Handler麽?而且用這個Handler來sendMessage?以下我們進行測試:
實例2:
public class MainActivity extends Activity { public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: System.out.println("接收到空消息"); break; default: break; } } }; new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(); handler.sendEmptyMessage(1); } }).start(); } }
解釋:
這段程序我們調用的是自己的子線程自己的Handler,執行之後報了下面錯誤:
E/AndroidRuntime(963): java.lang.RuntimeException: Can‘t create handler inside thread that has not called Looper.prepare()
意思就是說在沒有調用Looper.prepare()之前是不同意創建Handler對象的,這一點我們能夠從Handler的構造函數中查看原因:
下面是Handler的構造函數源代碼:
public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { 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()); } } //從ThreadLocal獲取到Looper對象,這個對象是由Looper.prepare()函數創建而且加入到ThreadLocal中的 mLooper = Looper.myLooper(); if (mLooper == null) { <span style="color:#ff6666;">throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");</span> } //獲得Looper對象中的MessageQueue mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
解釋:
拋出異常原因在於mLooper為null,而mLooper是一個Looper對象,這個對象是通過Looper的static方法myLooper從ThreadLocal中獲取的。而創建Looper對象是由Looper的static方法prepare()實現的,而且將其增加到了ThreadLocal中。
因此我們找到了拋出異常的原因。也就是Looper對象為null。正如異常中所提示的一樣,須要調用Looper.prepare()來創建Looper對象。
我們來查看Looper的static方法prepare源代碼:
public static void prepare() { prepare(true);//創建子線程的Looper對象的時候,此處始終為true。可是待會會發現主線程的Looper對象在創建的時候此參數值是false } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
解釋:
從源代碼中我們看到在創建Looper對象之前先查看ThreadLocal中是否已經存在一個Looper對象。一個線程僅僅能創建一個Looper對象,假設多次創建Looper會拋異常。假設不存在的話,調用new Looper(true)創建當前線程的Looper對象,而且將其set到ThreadLocal中(在此多少能夠發現ThreadLocal事實上是一個Map型的數據結構實現的,其源代碼分析以後補上),來吧,該看看new Looper(true)究竟做了寫什麽事的時候了,源代碼例如以下:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed);//創建了一個MessageQueue消息隊列 mRun = true; mThread = Thread.currentThread(); }
解釋:
非常easy,就是創建了一個MessageQueue而且將mThread設置為當前線程;
好了,至此我們創建了Looper對象。那麽我們把Looper.prepare()增加到實例2的Handler handler = new Handler( )之前,看看會有什麽事發生吧:
實例3:
public class MainActivity extends Activity { public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: System.out.println("1: "+Thread.currentThread()); break; default: break; } } }; new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { System.out.println("2: "+Thread.currentThread()); } }; handler.sendEmptyMessage(0); mHandler.sendEmptyMessage(0); } }).start(); } }
解釋:
輸出結果:1: Thread[main,5,main]
發如今我們的輸出信息中,並沒有輸出:子線程中handleMessage的信息
原因事實上也非常easy:你如今僅僅是有Looper對象了,可是你並沒有對Looper對象進行不論什麽操作,就像你非常有錢,可是你不花這些錢,那錢還有什麽用呢?那該怎麽用呢?要想找到這個問題的答案,我們須要分析主線程中是怎麽創建Looper對象以及怎麽使用這個Looper對象的呢?
我們知道應用程序是通過ActivityThread主線程來創建的,為什麽這樣子說呢?看看源代碼就知道啦:
public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper();//創建Looper對象 ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop();//使用Looper對象中的MessageQueue來進行消息處理 throw new RuntimeException("Main thread loop unexpectedly exited"); }
解釋:
由於這個類裏面有我們苦苦想要尋找的main函數,可能你曾經也會疑惑android程序究竟是怎麽啟動的呢?如今明確了吧,入口函數main在這裏呢,來看看裏面我們可能熟悉的代碼吧。Looper.prepareMainLooper()有點類似於我們之前見過的Looper.prepare()吧,顯然他也是用來創建一個Looper對象而且放入ThreadLocal裏面的。僅僅只是他是在主線程中創建的:
public static void prepareMainLooper() { <span style="color:#ff6666;">prepare(false);//這裏的參數值是false,子線程創建Looper的prepare參數值是true</span> synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
解釋:
在有了Looper對象之後。main方法中的Looper.loop()就是利用Looper對象中的MessageQueue來進行消息接收和處理的。源代碼例如以下:
public static void loop() { final Looper me = myLooper();//獲得Looper對象 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } <span style="color:#ff6666;">final MessageQueue queue = me.mQueue;//獲得Looper對象中的MessageQueue</span> // 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(); //採用死循環的方式從MessageQueue中取出消息 for (;;) { <span style="color:#ff6666;"> Message msg = queue.next(); // might block</span> 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); } //調用dispatchMessage來進行消息的處理 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.recycle(); } }
解釋:
詳細過程是:
(1)首先通過myLooper( )靜態方法獲取到Looper對象;
(2)通過獲取到的Looper對象來獲取到該對象中的MessageQueue消息處理隊列;
(3)採用死循環的方式對消息隊列中的每一個消息調用此消息所在的handler(通過msg.target獲取此handler)的dispatchMessage方法進行處理;
(4)消息處處理結束後調用recycle方法回收消息;
這裏最重要的方法當然就是dispatchMessage消息處理和消息回收函數啦,接下來我們先分析一下消息是怎麽傳遞到MessageQueue隊列中的。隨後再來分別來看看究竟在dispatchMessage和recycle這兩個函數中做了什麽?
要想處理消息,首先你得有消息吧。就像你想花錢一樣,首先你總得有錢吧,handler機制中,消息是從哪來的呢?
非常顯然從實例中我們能夠看出,我們都是通過handler的sendEmptyMessage方法來發送消息的,在handler中存在非常多發送消息的方法,可是歸根結底他們最後都會調用sendMessageAtTime方法的(無論你是採用Runnable的post機制還是採用Message的send機制),源代碼例如以下:
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); }解釋:
這種方法有兩個參數。一個是想要傳遞給主線程的Message對象,uptimeMillis表示我們發送消息的時間,假設調用的不是sendMessageDelayed的話。uptimeMillis的值為0。在消息隊列非空的前提下調用enqueueMessage將消息增加到隊列中;
我們來看看enqueueMessage的源代碼:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }解釋:
非常easy。他就是直接調用了MessageQueue隊列中的enqueueMessage入隊方法,來看MessageQueue裏面的enqueueMessage方法源代碼:
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."); } synchronized (this) { if (mQuitting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } msg.when = when; Message p = mMessages; boolean needWake; 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; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }解釋:
這裏我們僅僅討論核心代碼。由於要插入Message對象到隊列。所以我們必須找到隊尾元素的位置。而這個位置在上面的源代碼是通過p的指針值是否為空來進行推斷的,假設p本身為空的話,說明p已經到達了隊尾,我們僅僅須要將該Message對象插入到p之後就可以啦,假如隊列本身為空的話,那麽p本身就是隊尾。直接插入;假如隊列本身不為空的話。須要遍歷整個隊列。找到隊尾元素就可以啦,然後插入,我們能夠看到。MessageQueue的隊列是由單鏈表來實現的。
好了,這下子Message對象增加到了MessageQueue中啦,隨後就是我們該怎麽處理的問題啦;
查看Handler.java源代碼中的dispatchMessage方法:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
解釋:
在正式分析此方法之前先補充一點。Handler支持兩種消息類型。Runnable和Message,因此發送消息提供了post(Runnable r)和sendMessage(Message msg)兩個方法。Message中的callback屬性存儲的就是Runnable對象;詳細分析:
(1)首先查看該Message的callback字段是否為null,即查看此消息是否存在Runnable屬性值(該值是通過post方法將其增加到Message的callback中的)。有的話則運行handleCallback方法。該方法會運行Runnable的run方法,源代碼例如以下:
private static void handleCallback(Message message) { message.callback.run(); }(2)假設Message的callback字段為null的話,則查看是否存在外部的Callback類型對象,這個值是在創建Handler的時候通過構造函數傳遞進來的,假設存在的話運行該Callback對象的handleMessage方法;
(3)假設既不存在Runnable對象。又不存在外部的Callback對象的話,則直接運行handler自身的handleMessage方法,這種方法須要我們在new Handler之後進行重寫,由於Handler本身是沒有實現這種方法的。
public void handleMessage(Message msg) { }好了,消息已經處理結束啦,接下來我們就該回收該消息啦。也就是Looper.loop( )方法所運行的最後一句代碼,msg.recycle( ),再次回到了源代碼級別查看:
public void recycle() { clearForRecycle();//將Message的各種標識位所有歸位 synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++;//將該消息返回給消息緩沖池 } } }
void clearForRecycle() { flags = 0; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; when = 0; target = null; callback = null; data = null; }解釋:
釋放消息前首先將消息裏面的各個消息標識位歸位。隨後將該消息增加到消息緩沖池中,以備下次我們使用消息的時候能夠直接調用Message.obtain( )方法來獲取消息。這樣子就不用new Message( )啦,從而降低了new對象的時空開銷,這就是緩沖機制的優點,由於緩沖池裏面的全部消息對象是能夠反復使用的。僅僅是在使用的時候進行必要標識位的設置就可以,看看Message的obtain方法就一目了然啦:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; sPoolSize--; return m; } } return new Message(); }解釋:
我們能夠發現,僅僅有當消息緩沖池為null的時候我們才會new Message出來,假設消息緩沖池不為空的話,直接獲取緩沖池中的第一個,而且讓緩沖池中的消息個數降低1就可以;
至此,整個消息處理過程已經結束了。
那麽我們應該知道怎麽改動實例3讓他可以輸出:子線程中handleMessage的信息
實例4:
public class MainActivity extends Activity { public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: System.out.println("1: "+Thread.currentThread()); break; default: break; } } }; new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { System.out.println("2: "+Thread.currentThread()); } }; handler.sendEmptyMessage(0); mHandler.sendEmptyMessage(0); Looper.loop(); } }).start(); } }區別就是添加了Looper.loop( )這句代碼而已。
輸出結果:
2: Thread[Thread-120,5,main]
1: Thread[main,5,main]
究竟,我們能夠做做總結啦。看看Handler消息處理機制中究竟用到些什麽?
1. Looper
(1)創建消息循環
prepare( )用於創建Looper對象,而且保存到ThreadLocal中;
(2)獲得消息循環對象
myLooper( ),採用ThreadLocal的get方式獲取存儲在ThreadLocal裏面的消息循環對象;
(3)開始消息循環
詳細過程:
首選獲取MessageQueue裏面的隊頭Message
接著調用該Message所在handler的dispatchMessage方法。最後在dispatchMessage裏面調用handlerMessage方法
消息使用完成之後將其增加到本地消息緩沖池中。以便下次使用,節省創建Message的開銷
2. MessageQueue
每一個Looper對象相應一個MessageQueue隊列,他是消息的存儲區,向Handler發送的消息終於都會存儲到該隊列中
(1)消息入隊
消息入隊採用的方法是enqueueMessage( )。首先會查看隊列是否為空,假設為空。則直接入隊就可以。假設非空須要輪詢鏈表,依據when從低到高的順序插入鏈表的合適位置。這裏的隊列是由單鏈表實現的;
(2)輪詢隊列
next( )用於獲取MessageQueue中的Message
3. Handler
(1)獲取消息
以obtain打頭的方法,這些方法實現的功能就是從本地消息緩沖池中獲取消息,這樣做的目的就僅僅是為了提高時空效率,這些方法實際上還是調用的Message中的各種obtain方法
(2)發送消息
Handler支持兩種消息類型,各自是Runnable和Message,他們發送消息的方法各自是以post打頭的post(Runnable runnable)和以sendMessage(Message message),可是post方法中的Runnable對象在最後還是會被封裝到Message中稱為Message的屬性callback的值,也就是說原則上還是自由Message這樣的方式的,在調用各種sendMessage的時候。都會終於運行sendMessageAtTime。而在這種方法裏面就會調用MessageQueue的enqueueMessage來將Message增加到隊列中啦;
(3)處理消息
Handler中處理消息的開始方法是dispatchMessage,在這種方法裏面會調用Handler本身的handleMessage方法。而這種方法是須要我們在創建Handler的時候重寫的
4. Message
(1)創建消息
創建消息有兩種方式。我們能夠通過new Message( )的方式創建一個新的Message,也能夠通過Message.obtain從本地消息緩沖池中獲取一個消息,後者在時空上效率更高;
(2)釋放消息
消息使用完成之後。調用recycle函數釋放消息,將該消息增加到本地消息緩沖池中,以便下次使用;
以上就是Handler消息處理機制的主要內容了。我們也自己實現了一個子線程中的handler而且創建了子線程自己的MessageQueue,此外子線程可以通過loop方法處理傳遞給子線程的消息了。可是假設每次我們想讓子線程實現這種功能的話,我們都必須利用Looper.prepare( )和Looper.loop( )方法將我們的子線程中的handler代碼包裹起來。這樣是不是太麻煩啦,這就導致了HandlerThread的出現,在下一篇。我們解說下HandlerThread的源代碼分析。
Android---Handler消息處理機制