編輯:關於Android編程
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都需要先調用它的perpare函數,在perpare中利用java的ThreadLocal類,往當前運行的線程中存入局部變量,最終存入到了ThreadLocal.ThreadLocalMap當中。以key = Looper.sThreadLocal。(每個線程都一樣) value = new Looper.. 存入值。由於每個Thread的ThreadLocalMap不同(通過每個Thread的ID保證),所以即使key相同取出來時也是自己存入的而互不影響。 此處就理解為每個線程都為自己保存了一個Looper對象,且可以通過ThreadLocal的get方法獲取這個對象。各個線程之間相互不影響。在 一些需要判斷的地方通過sThreadLocal的get方法,會自動的從當前線程中以相關的key。取出存入的value值。一旦發sThreadLocal.get()!= null,證明前面已經設置過,拋出異常。那麼一定要先prepare嗎?這就要結合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."); } .......}
public static Looper myLooper() {return sThreadLocal.get(); } 綜上可以看出,調用loop進行循環前,假如沒有prepare的話,me == null直接拋出異常。loop的操作你總是要做的吧,不然那怎麼處理消息。那麼為什麼在主線程中用的時候也沒有調用loop那?後面分析。 Android的消息機制中另一個關鍵類是Handler.當消息循環器在其寄宿的線程裡正式運作後(loop),消息發送者就可以通過該looper對應的Handler向該消息循環發出事件。 網上的圖: 當你構造完Handler的時候,你就可以通過獲取目標線程的Handler對象,然後往目標線程發送消息了。最終處理還是由目標線程的消息隊列和loop配合來完成,這樣就實現了同一進程內兩個不同線程之間的通信。注意:一個線程也可以由多個Handler進行訪問(只要這個Handler屬於這個Looper),但是他們的MessageQueue只有一個,因為由前面可知Looper只有一個,而Looper中封裝的MessageQueue。 注意:一旦獲得到這個線程的looper對象以後,就可以。通過這個looper,來獲得一個對應的handler對象,這個handler對象就可以往這個 looper對應的messagequeue中發送消息了。但處理回調還是這個handler,因為一個looper,可以有多個handler。所以系統提供了獲取主線 程Looper對象的函數。便於和主線程通信。 2、使用。 基本的使用方法十分簡單,網上也是一大推。此處不再記錄。此處先說幾個概念分享,個人的理解。 進程:感覺可以用一個CPU的任務調度的概念,完成一次什麼什麼的操作。當你開始申請進程的時候,CPU開始為這個進程開辟資源空間、復制數據等等。當完成這個調度之後,就會銷毀它。 每個進程啟動的時候都會有一條主線程,這個主線程貫穿這個進程的始終,在它的執行時間段內,可以分化出很多子線程。對於Android 的UI操作,考慮到安全性,只允許在主線程進行操作。注意這個主線程的概念。剛進入一個app就已經開始執行這條主線程,此時你從這個界 面跳轉到另一個界面,它還是在主線程上進行的操作(以前每太理解這個)。要理解這個概念。下面就開始討論Handler、Looper的用法。 對於Handler: 1、構造: 它的構造有很多的重寫。其實就是三個參數Looper、Callback、boolean async初始化的值的問題。此處沒有討論Callback、async賦值的問題,CallBack好像是用於handler內部接口Callback實現時覆蓋我們Handler的handleMessage,感興趣可以去看一下Handler中的dispatchMessage的方法,async為true時候,表示當前是個異步的Handler,發出的消息為異步消息,本文在於理解整個Handler機制,暫未分析此兩個參數。 最常用的空構造Handler()--->Handler(Callback callback, boolean async).首先就會檢查Looper.myLooper(),獲得的就是線程的本地存儲區中存放的Looper,假如沒有執行過相應代碼(prepare)時就會為null。假如為null那麼就會拋出異常。只有調用Looper.prepare()就能實例化通過,(無論是系統中調用了還是你調用了)由此也可以看出,你想要發送信息,肯定的實例化Handler把,你實例化它,你的先prepare,不然出問題。
public Handler(Callback callback, boolean async) {...... 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; } 第二常用的構造new Handler(Looper)---->new Handler(Looper looper, Callback callback, boolean async)。實例化時候過看起來不調用prapare沒事,但是你肯定的調用loop把讓其循環跑起來,由前面loop方法中也會進行檢查(由於沒有set),到時候報錯。
public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } 對於Looper: 1、它是一個final的類,並且構造是私有的,且只在本類的prepare中進行了new。 構造函數中實例化了一個MessageQueue,用來處理和存放消息(存放的只是java層的消息,現在looper在底層頁有實現,功能更為強大),和當前所在的線程mThread.
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 對於MessageQueue此處先簡單看一下它的構造流程:
MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;//設置是否允許退出線程,主線程是不允許退出的。 mPtr = nativeInit();//這個long類型最終指向了本地的NativeMessageQueue對象。 } private native static long nativeInit();可以看到nativeInit是一個本地的方法,通過JNI調用到android_os_MessageQueue.cpp。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast
NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); }} 注意Java層的Looper對象和底層的Looper對象不是同一個對象。它們各司其職,來完成整個消息機制。上層的Looper用來完成消息隊列的創建和消息的循環檢查。而native層的looper主要是用來通過Linux的epoll機制實現dengdai/喚醒,以及對底層的消息的分發處理。也可以看出來,有java層的Looper對象,那麼就一定有一個和它對應的native層的Looper對象,但注意有native層的不一定需要java層的Looper,可擦肩input系統中的InputDispatcher。 接下來對於Looper的實例,其它類只能通過如下兩個接口進行獲取。
/** Returns the application's main looper, which lives in the main thread of the application.*/ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }
/*** 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(); } 2、在使用消息機制前,Looper循環器必須已經實例化,且存入到它所在的線程的,本地存儲區當中,key為成員變量sThreadLocal。(沒有循環器,你怎麼讀取消息?),那麼這個實例化並且存入到所在縣城本地的操作什麼時候執行的那?尤其是在主線程中直接new Handler使用,也沒有看到實例化Looper。 談一些個人概念理解: 主線程和其它線程一樣,只不過可能優先級什麼的高一點。Android應用程序是消息驅動的,在應用程序中,主線程也是圍繞這它的消息隊列無限的循環,知道應用退出。如果隊列中有消息,並且消息已到時那麼就分發給相應的Handler處理,如果隊列中沒有符合條件的消息,那麼主線程也不會一直的空轉,而會空閒等待,知道下一個消息的到來(怎麼實現的看完文章就清楚了)。個人理解注意此時的空閒等待,不是我們平常所獲的ANR出現的地方,不是說這邊等一定時長沒消息後,系統會出現ANR。這塊是整個線程一直在循環工作的地方,如主線程就一直在這個循環中跑,等消息,分發處理消息等等。當你再在這個循環中處理某些費時操作的時候才有可能出現ANR(應該是android設置的什麼檢測). 在主線程中: 進程啟動時,會啟動一條主線程,然後在在ActivityThread類中的main方法裡,完成了對Looper的初始化以及循環調用。問題:這個主線程在哪體現的?ActivityThread我看也不是線程類啊?(暫不懂) 另一個需要注意的問題,執行完Looper.loop後當前的線程會進入到一個循環當中,界面上看就是一直卡死在這,等待消息過來。所以你在OnCreate中再次執行這個會導致界面無法顯示,無法完成OnCreate。你要是在Looper.loop()後面還有一些必須的參數的實例化,他們也會執行不到,完成不了實例化。 下面摘取進程啟動流程的一部分,至於進程啟動的流程,大家可以去看羅升陽,羅老師的博客(個人非常崇拜)。
public static void main(String[] args) { ...... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 已經看到會先執行prepareMainLooper方法。所以此時有個限定,就是無論你在主線程還是子線程中都不可以再調用prepareMainLooper了。因為很明顯它會拋出異常的。所以這個prepareMainLooper其實就是讓系統調用的。
public static void prepareMainLooper() {prepare(false); synchronized (Looper.class) { if (sMainLooper != null) {//由於第二次調用的時候這裡不為null那麼必然報錯。 throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } 在執行prepareMainLooper方法中,會先執行prepare(false),執行這個操作的時候就會new一個Looper對象,然後把它以key = sThreadLocal存入到本地存儲區域。然後再賦值給sMainLooper。
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的初始化,此步驟是源碼內部實現的(myLooper和sMainLooper都有值了)。 所以你在主線程中實例化Handler時可以直接。new Handler()。不需要傳入Looper對象,因為早已有。假如你非要傳入的話,通過一開始的兩個接口獲得Looper實例然後傳入,調用另外重寫的構造,這也是一樣的。沒啥影響。 在子線程中: 子線程中就沒有上面的便利了,你new 出來新的線程後,不會自動的調用prepare,然後去loop。 此時有如下幾種錯誤,根據前面相關構造和方法的分析。 假如你直接 new Handler()的時候 Looper.mLooper() == null,會拋出異常。 假如你new Handler(Looper.myLooper());由於傳入帶Looper,最終會走到三個參數的,進行賦值。但是mQueue = looper.mQueue,由於傳入的參數Looper.myLooper()為null,導致looper為null,進而mQueue = looper.mQueue也會looper == null的報錯。 假如你new Handler(Looper.getMainLooper()),此時sMainLooperr肯定不為null,那麼mQueue = looper.mQueue不會報錯,看起來能往下走。但是當你執行looper.loop()開始輪詢的時候,會執行如下代碼:final Looper me = myLooper();myLooper函數就是調用:sThreadLocal.get();去獲取當前正在運行的線程存入到的局部變量的Looper,此時由於沒有prepare肯定為null。在looper中有如下判斷 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } 最後也會拋出異常。 綜上你必須在當前線程中調用Looper.prepare().(注意你不能調用main的perpare會出錯的哦).下面對prepare做出簡單分析
public static void prepare() {prepare(true); }
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)); } 首先可以看出來多次perpare由於get不為null會拋出異常。 然後就是實例化一個Looper為這個Thread,並且存放到當前線程的本地存儲區,以key =sThreadLocal. 所以在你實例化Handler的時候,先調用prepare,然後你再用空構造或者帶new Handler(Looper.myLooper())都不會報錯了。因為newHandler(Looper.myLooper())是正確的looper。此時有個小知識點當你用new Handler(Looper.getMainLooper ())時,最後你再Looper.loop()也能正常的消息傳遞。這是因為在你是在操作主線程的消息隊列啊,真是醉了(當時沒注意考慮到還納悶那)。此處核心知識點:可以通過傳入的looper對象來獲得一個新的Handler,然後就可以往對應的Looper中的messagequeue中發送消息就。(當時的錯誤理解:final Looper me = myLooper()。而這個myLooper-->sThreadLocal.get(); 注意!!是從當前線程的本地存儲區當中取值。這個取出來就是前面Looper.prepare()放的。所以自己線程的循環還是輪詢起來了。但是注意個問題。最終到構造的時候,mLooper = looper;mQueue = looper.mQueue;這都已經是main Looper的了啊!!不是自己的那個looper的。但是為什麼竟然能收到消息啊??????本地跟蹤代碼如下,按理說main的時候會入隊到main消息隊列當中,那麼,這個是怎麼觸發的那?可能是就算是在main中,main已經輪訓起來了,那麼也會回到這個msg的相關信息。可能和後期傳遞有點關系吧????以前的錯誤理解) 注意事項:一定要注意Handler的定義位置,很多時候你以為定義到了子線程中,其實還定義在主線程中。只有在子線程的類中,或者run方法中定義了,這樣才會到子線程中。 3、內部源碼實現。 由於Android中實現的消息機制用到了下面兩個技術點,為了更好地認識Handler機制。分別把下面的知識點做了整理。 3.1 java中的線程的本地存儲技術ThreadLocal類。 參考http://blog.csdn.net/zy00000000001/article/details/52795870有個人的比較全的整理。 3.2 linux下的epoll機制的初識。 參考http://blog.csdn.net/zy00000000001/article/details/52797127有個人初識epoll的整理。 3.3 消息分隔符/同步分隔欄解釋說明。 我也不知道這個到底該怎麼稱呼.反正這個玩意就是消息的一種。Message有三種類型 a、普通Message 它的成員變量target為發送Message的Handler自己。通過入隊時的函數進行了賦值處理
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;// 當前的往外發送消息的Handler if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } b、異步Message 同樣也是在調用enqueueMessage的時候判斷mAsynchronous是否為true。mAsynchronous是在Handler實例化的時候傳入的,如果為true那麼就調用Message的setAsynchronous設置為異步消息。
public void setAsynchronous(boolean async) {if (async) { flags |= FLAG_ASYNCHRONOUS; } else { flags &= ~FLAG_ASYNCHRONOUS; } } c、Message分隔符(同步分隔欄) 它是一種特殊的Message,在隊列中作為一個分隔符,以它為標志後面的同步信息即使已經到了觸發時間,也不會被Looper處理. MessageQueue提供了enqueueSyncBarrier(long when)、removeSyncBarrier分別用於插入和移除分隔符。
int enqueueSyncBarrier(long when) {// Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
void removeSyncBarrier(int token) {// Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. synchronized (this) { Message prev = null; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); // If the loop is quitting then it is already awake. // We can assume mPtr != 0 when mQuitting is false. if (needWake && !mQuitting) { nativeWake(mPtr); } } } 網上的圖: 至於為什麼這麼設計,暫時沒想到。我在源碼工程下搜索整個android目錄。發現除了cts的測試包,其它所有的系統應用都沒有調用這個。個人感覺可能和某些具體的消息處理有關,比如有些消息需要在什麼之完成之後了等等。而且在Android的消息機制裡面,同步消息和異步消息,目前也就這點區別了(遇到分隔符時後面會不會被執行)。 3.3 消息的發送:入隊、喚醒。 獲得到目標線程的Handler對象以後,就可以調用相應的發送消息的方法,把消息放入到MessageQueue當中,其實最終是在它的成員變量mMessages所包含的Messgae對象以及通過Message對象的next成員變量組成的一條鏈式的消息當中,這個消息是按照觸發時間的順序排列的。 此處以一個標准的消息發送過程來分析。 在Handler中的流程如下:sendMessgae-->sendMessageDelay----->sendMessageAtTime--->enqueueMessage
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);//SystemClock.uptimeMillis() 開機到現在的時間。 }
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;// 當前的往外發送消息的Handler if (mAsynchronous) {//Handler構造中傳入的用來標記是否是異步的Handler msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } 然後會調入到MessageQueue中的enqueueMessage函數,嘗試去把消息入隊。
boolean enqueueMessage(Message msg, long when) { //!!!注意next域的賦值,它是用來代表當前消息的下一個要處理的消息if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); //寫標志位,讓人知道消息正在使用。 msg.when = when;//發送的消息處理的時間,當前系統時間或者是當前系統時間加上你延遲的時間。 Message p = mMessages;//mMessages成員變量存放的就是當前消息鏈條(不能說是鏈表,又不能說是集合)中的觸發時間最靠前的那個消息。 boolean needWake; //p == null時是第一次往消息鏈條插入;when == 0時需要立刻處理這個信息; //又或者when < p.when當前傳入的消息的觸發時間小於以前的在消息鏈條最先面的消息的觸發時間時候。 if (p == null || when == 0 || when < p.when) {//進入判斷後需要往消息鏈條的頭部插入消息。 // New head, wake up the event queue if blocked. msg.next = p;//而此時原先的消息鏈條的頭部消息就放到了當前傳入的消息的next變量中,用於下次處理。 mMessages = msg;//當前正在處理的msg設置為成員變量mMessages,也就是消息鏈條最前面最需要先觸發的消息。 needWake = mBlocked; // 根據是否阻塞來決定是否要喚醒。//一般走到這的話,應該就為true了。需要喚醒一下。 } else { // 這樣就不是插入消息鏈條頭部了,而是插入到鏈條當中某個合適的位置,所以不需要調整喚醒時間。 needWake = mBlocked && p.target == null && msg.isAsynchronous();//needWake為true時插入的肯定是"同步分隔欄"(p.target == null && msg.isAsynchronous())。 Message prev; for (;;) {//循環找合適的位置,按照觸發時間進行的排序 prev = p; p = p.next; //配合prev = p 就這樣一直循環的找合適位置。 if (p == null || when < p.when) {//下一個消息是null 或者 當前消息的觸發時間小於某個消息的觸發時間時,找到正確位置退出循環。 break; } if (needWake && p.isAsynchronous()) { needWake = false;//此處不懂??????????? } } msg.next = p; //在前面找到合適位置when < p.when,所以此時的p應該是msg下一個需要處理的消息 prev.next = msg;// prev 是當前消息的前一個需要處理的消息。 } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { //需要喚醒就喚醒,就是往管道中寫入一個字符"w",這樣epoll機制就監聽到了事件,然後處理. nativeWake(mPtr); } } return true; } 這樣就完成把當前消息插入到了消息鏈條中,也把msg.next域做了正確處理。(下一個要處理的消息)。nativeWake是一個java本地方法,通過JNI技術調用到稍微底層一點的函數。private native static void nativeWake(long ptr);調用到android/framework/base/core/jni/android_os_MessageQueue.cpp當中的android_os_MessageQueue_nativeWake方法。
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {//可以看出來ptr被強制轉換成 *nativeMessageQueue 指針對象。 NativeMessageQueue* nativeMessageQueue = reinterpret_cast
void NativeMessageQueue::wake() {mLooper->wake();} 調用到了Lopper中的wake。千萬要注意和這個looper可不是java層的looper對象。而是c++實現的一個Looper對象。它位於system/core/libutils/在看Looper對象的wake函數之前我們看一下Looper.cpp的構造函數。
Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { int wakeFds[2]; int result = pipe(wakeFds); //調用系統的pipe函數,創建一個管道。 LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); mWakeReadPipeFd = wakeFds[0]; mWakeWritePipeFd = wakeFds[1]; result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);//設置為非阻塞模式 LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", errno); result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);//設置為非阻塞模式 LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", errno); mIdling = false; // Allocate the epoll instance and register the wake pipe. mEpollFd = epoll_create(EPOLL_SIZE_HINT); //創建一個epoll句柄。 LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union eventItem.events = EPOLLIN; //要監聽的事件,具體可參考上面epoll整理連接中的內容 eventItem.data.fd = mWakeReadPipeFd; result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem); //注冊監聽事件. LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d", errno);} 總的來說就是創建了一個管道,然後初始化好一個epoll句柄,來監控這個管道的讀取端。具體的實現基本用法可以看給出的鏈接。到時候就明了。不懂epoll的建議去看看基本的概念,不深入研究實現的話還是比較好理解的。
void Looper::wake() {#if DEBUG_POLL_AND_WAKE ALOGD("%p ~ wake", this);#endif ssize_t nWrite; do { nWrite = write(mWakeWritePipeFd, "W", 1);//就是往管道中寫入了一個“W”字符。用於喚醒沒有實際的含義。 } while (nWrite == -1 && errno == EINTR); if (nWrite != 1) { if (errno != EAGAIN) { ALOGW("Could not write wake signal, errno=%d", errno); } }} 至此發送消息的標准流程算是分析完畢,看到這裡可能還有為什麼要往管道中寫入一個“W”字符去喚醒epoll,這就要接著分析一下循環的實現原理了。 3.4 消息的循環。 前面我們已經分析過,想要正常的使用Handler往目標線程發送消息並得到處理,那麼必須先實例化Looper對象,並且讓Looper對象調用loop使它循環的處理消息隊列中的消息。Looper.perpare( )然後調用 Looper.loop().使Looper循環起來,下面我們來分析loop函數的具體實現。
public static void loop() {final Looper me = myLooper();//此處就會檢測本線程有沒有調用perpare方法。 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } 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 (;;) {//在一個 for循環中 不斷的嘗試去摘取隊列裡的下一條消息 Message msg = queue.next(); // might block 此處是關鍵,去獲取下一個消息,但是也可能阻塞。 if (msg == null) {//不發生大的異常或者調用Handler的quit方法時,此處不會是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); } msg.target.dispatchMessage(msg); //通過msg.targ往外派送消息,msg.target就是我們發送消息時用的那個handler。(前面有) 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函數中的整體邏輯很簡單,主要就是先通過MessageQueue的next()函數去獲取下一個Message,注意可能阻塞。而當正確取到消息的時候在調用Handler的dispatchMessage函數做進一步分發處理消息。
public void dispatchMessage(Message msg) {if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg);//就會調用到我們重寫的handleMessage函數。 } } 至此整體流程走完,但是我們還需要去進一步的分析一下MessageQueue的next函數,以便來更深入的理解為什麼有時候要阻塞,又要怎麼阻塞,這個裡面的循環到底是個什麼邏輯。 MessageQueue的next()函數就是要去MessageQueue的message消息鏈條中摘取消息,然後用於分發處理。 對於消息隊列而言在摘取消息時,為了提高效率,減少內存和CPU的使用應該考慮很多的技術細節: a、如果消息隊列中沒有合適的消息可以摘取,那麼就要讓它所屬的線程阻塞,而不應該一直循環的做無用的查詢動作。 b、消息鏈條上面的消息按觸發時間的順序依次排列。誰先觸發誰在前。(這個感覺在發送消息入隊的時候就已經排序好了) c、沒有消息時阻塞,阻塞的時間最好能精准一點兒,所以應該算出一個合適的時間差。根據還有多長時間首條消息觸發。 d、有時候外界希望在進入阻塞狀態之前做一些動作(反正這會兒沒消息處理),這些動作可以成為idle操作,我們要處理這些操作。典型的應用很多時候系統內部,在隊列進入阻塞之前會做一次垃圾回收。
Message next() {// Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; //下一個消息到來的時間。(也就是阻塞的時間) for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //第一次nextPollTimeoutMillis傳入0肯定會立馬返回的,不會導致阻塞,傳入一定時間的時候就阻塞這麼長時間,傳入-1時,用就阻塞直到epoll監聽到事件。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //mMessages 就是我們前面發送消息,插入消息鏈條時處理的這個,最終把在最前面的消息賦值給他。 if (msg != null && msg.target == null) {//如果是同步分隔欄,直接去找下一個異步消息。這個同步分隔欄,其實就是一個targ為null的Msg. // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; // 配合prevMsg = msg 循環查找。 } while (msg != null && !msg.isAsynchronous());// 不是null,並且不是異步的消息的時候就一直循環,直到找到第一個異步的。 } if (msg != null) {//如果次消息不為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;//阻塞置為false if (prevMsg != null) {//不等於null的時候肯定取到的是同步分隔欄後的第一個異步的消息。 prevMsg.next = msg.next;//把這個異步消息的下一個消息復制到它的前一個消息的next。(此時取的不是第一個即將到時的消息) } else { mMessages = msg.next;//把當前到時的信息的下一個賦值給mMessages,使其成為以後要處理的第一個。 } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); return msg;// 返回要處理的這個 } } else { // No more messages. //如果取出來的消息為null,也就是沒有消息要處理了,那麼賦值為-1,利用epoll機制,無限阻塞直到下一個消息到來(然後會在C++中做處理)。 nextPollTimeoutMillis = -1;//這樣下一個循環到前面的nativePollOnce會是線程進入阻塞。 } // Process the quit message now that all pending messages have been handled. if (mQuitting) { //主線程不允許退出(主線程一直在等界面各種消息,然後作相應的邏輯處理啊). dispose(); //而我們一般也沒有調用退出啊。退出以後就不循環了,程序不卡那了等消息,這個線程沒有其它的阻塞的話,執行完會退出。 return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. //能走到此處,那就是msg為null了,暫時沒什麼要處理的,那麼就准備處裡一些idle操作 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; //沒有idle操作,直接開始下一個循環,並且把阻塞標志置為true。 } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); //實現 IdleHandler 接口 } catch (Throwable t) { Log.wtf("MessageQueue", "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0;//處理完handle操作後,重置一下 // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. //只要隊列中有Idle Handler需要處理,那麼在處理完所有Idle Handler之後,會強制將nextPollTimeoutMillis賦值為0。 //這主要是考慮到在處理Idle Handler時,不知道會耗時多少,雖然處理前已經沒有消息了,但在此期間消息隊列極有可能有新的消息,或者到時了。 nextPollTimeoutMillis = 0;//然後就置0,讓nativePollOnce傳入0,不等待直接處理一次消息。 } } 然後就是nativePollOnce函數的具體分析以及如何實現阻塞的,也是java native函數通過JNI調用到了android_os_MessageQueue的android_os_MessageQueue_nativePollOnce當中。NativeMessageQueue--->pollOnce--->Looper.pollOnce(time)----->Looper.pollOnce(time,x,x,x)
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast
void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {mInCallback = true; mLooper->pollOnce(timeoutMillis); mInCallback = false; if (mExceptionObj) { env->Throw(mExceptionObj); env->DeleteLocalRef(mExceptionObj); mExceptionObj = NULL; }}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0; for (;;) { .....//此處省略了一些針對於c++的層的Message信息的處理。Handler不僅能處理java層的信息,還能處理c++層的消息。 result = pollInner(timeoutMillis);//循環調用pollInner }}
int Looper::pollInner(int timeoutMillis) {//次方法只分析和java層相關的處理...... struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //利用epoll機制行程阻塞,根據timeoutMillis決定阻塞的時長。或者立即返回、或者永久(直到事件觸發)或者特定時間 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // No longer idling. mIdling = false; // Acquire lock. mLock.lock(); for (int i = 0; i < eventCount; i++) { // 等到事件以後。 int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeReadPipeFd) { if (epollEvents & EPOLLIN) { //接收到數據,讀socket awoken(); // 從管道中感知到EPOLLIN,於是調用awoken() } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); } } else { ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " "no longer registered.", epollEvents, fd); } } } ...... // Release lock. mLock.unlock(); } return result;}
void Looper::awoken() {#if DEBUG_POLL_AND_WAKE ALOGD("%p ~ awoken", this);#endif char buffer[16]; ssize_t nRead; do { nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));} 可以看出awoken函數只是把管道中的數據全部給讀取出來,完成清空管道。至此整個獲取下一個消息的處理完畢。 4、總結: 兩個線程之間進行通信,獲取到目標線程的Handler後,通過這個Handler向目標線程發送消息,然後消息就會被插入到目標線程的消息隊列中,注意此時的隊列不是說隊列數據結構,是一個概念就是一個鏈條式的消息集合。目標線程是一直在loop中等待事件的到來的,本身來說不具備鏈表一變動就能感知的功能,但通過管道和linux的epoll機制來完成阻塞循環和感知消息的到來,進而處理分發這個消息。 個人小知識點理解整理: 1、為什麼不同notify和wait來實現循環等待了? 現在來看的話在c++層實現的手,Handler的功能更加強大,並且native層也可以利用Looper.cpp的函數實現native消息的循環處理。可能效率會更快。 2、Handler.post(Runnable r).使用這個方法為什麼Runable執行在Handler所屬的線程中。 它最終是通過Handler的getPostMessage方法把Runable封裝到了Message的callback變量當中.最後插入到了Handler所屬的Looper的消息隊列當中,然後循環等待執行dispatchMessage時候會進入到msg.callback != null的判斷去執行當前runable的潤方法,此時已經到了Handler所在的線程中。所以要是主線程的Handler,那麼就會放到主線程中。下面放一部分源碼
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }
private static Message getPostMessage(Runnable r) {Message m = Message.obtain(); m.callback = r; return m; }
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(); }
暫停和恢復Activity(Pausing and Resuming an Activity)一個Activity是一個應用程序組件,提供一個屏幕,用戶可以用來交互為了完
在Android應用中,圖片裁剪也是一個經常用到的功能。Android系統中可以用隱式意圖調用系統應用進行裁剪,但是這樣做在不同的手機可能表現出不同的效果,甚至在某些奇葩
ProgressBar進度條,分為旋轉進度條和水平進度條,進度條的樣式根據需要自定義,之前一直不明白進度條如何在實際項目中使用,網上演示進度條的案例大多都是通過Butto
為什麼要使用多線程下載呢?究其原因就一個字:"快",使用多線程下載的速度遠比單線程的下載速度要快,說到下載速度,決定下載速度的因素一般有兩個:一個是客