1. 程式人生 > 其它 >Android訊息機制

Android訊息機制

Handler機制的作用是用在不同執行緒之間的通訊,通常是在子執行緒完成耗時操作,通過handler將有關的ui操作切換到主執行緒。

本質上是一個執行緒開啟無限迴圈並持續監聽其他執行緒給他發的訊息,如果沒有訊息自身就堵塞(相當於wait)

looper是要被呼叫的,android-main方法的呼叫:https://www.jianshu.com/p/302fe75d6778(未看)

大致的流程

先來大致梳理下整個流程:

  1. 應用程式啟動的時候,在主執行緒中會預設呼叫了 Looper.prepare()方法,初始化Looper物件並繫結到當前執行緒中,並在Looper內部維護一個MessageQueue

  2. 接著呼叫handler.sendMessage()傳送訊息,會通過MessageQueue.enqueueMessage()向MessageQueue中新增一條訊息

  3. 主執行緒呼叫Looper.looper()開啟迴圈,不斷輪詢訊息佇列,通過MessageQueue.next()取出訊息

  4. 取出的message不為空則呼叫msg.target.dispatchMessage()傳遞分發訊息,目標handler收到訊息後會執行handler.handlerMessage()方法處理訊息

Looper:

//建構函式:不允許別人建立 同時建立了一個MessageQueue物件
privateLooper(booleanquitAllowed) {
mQueue=newMessageQueue(quitAllowed);
mThread=Thread.currentThread();
}

  1. ActivityThread.main()方法中系統已經幫我們建立好Looper物件,而子執行緒的looper是我們自己建立的。

主執行緒中的Looper的prepare與子執行緒不一樣:

從prepare()可以看出而呼叫prepare的執行緒如果是第二次構建looper物件會丟擲異常

//Looper類中擁有一個ThreadLocal<Looper>池
//ThreadLocal中裝著所有執行緒的Looper物件
// sThreadLocal.get() will return null unless you've called prepare().
staticfinalThreadLocal<Looper>sThreadLocal=newThreadLocal<Looper>();

//Looper類中擁有主執行緒的looper物件!
privatestaticLoopersMainLooper;

//所有子執行緒要構建looper之前需要呼叫
privatestaticvoidprepare(booleanquitAllowed) {
if(sThreadLocal.get()!=null) {
thrownewRuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(newLooper(quitAllowed));
}

/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. See also: {@link #prepare()}
*
* @deprecated The main looper for your application is created by the Android environment,
* so you should never need to call this function yourself.
*/
@Deprecated
publicstaticvoidprepareMainLooper() {
prepare(false);
synchronized(Looper.class) {
if(sMainLooper!=null) {
thrownewIllegalStateException("The main Looper has already been prepared.");
}
sMainLooper=myLooper();
}
}
//現在的版本
publicstaticvoidprepare() {
prepare(true);
}
//主執行緒中不需要自己建立Looper
publicstaticvoidmain(String[]args) {
......
Looper.prepareMainLooper();//為主執行緒建立Looper,該方法內部又呼叫 Looper.prepare()
......
Looper.loop();//開啟訊息輪詢
......
}

//子執行緒
publicclassLooperThreadextendsThread{
@Override
publicvoidrun() {
// 將當前執行緒初始化為Looper執行緒
Looper.prepare();
// ...其他處理,如例項化handler

// 開始迴圈處理訊息佇列
Looper.loop();
}

Loop()函式:

 @SuppressWarnings("AndroidFrameworkBinderIdentity")
publicstaticvoidloop() {

//這裡面根據當前執行緒從ThreadLocal中返回looper物件
finalLooperme=myLooper();
if(me==null) {
thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if(me.mInLoop) {
Slog.w(TAG,"Loop again would have the queued messages be executed"
+" before this one completed.");
}

me.mInLoop=true;

// 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();
finallongident=Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
finalintthresholdOverride=
SystemProperties.getInt("log.looper."
+Process.myUid()+"."
+Thread.currentThread().getName()
+".slow",0);

me.mSlowDeliveryDetected=false;

//可以看出 每次迴圈都loopOnce獲取訊息佇列中的物件
for(;;) {
if(!loopOnce(me,ident,thresholdOverride)) {
return;
}
}
}

LoopOnce函式:挑重點

@SuppressWarnings("AndroidFrameworkBinderIdentity")
privatestaticbooleanloopOnce(finalLooperme,
finallongident,finalintthresholdOverride) {
Messagemsg=me.mQueue.next();// might block 具體看messagequeuezhong
if(msg==null) {
// No message indicates that the message queue is quitting.
returnfalse;//如果執行到這一步將會退出,主執行緒結束
}

-------
-------
//分發Message,msg.target 是一個Handler物件,哪個Handler把這個Message發到佇列裡,
//這個Message會持有這個Handler的引用,並放到自己的target變數中,這樣就可以回撥我們重寫
//的handler的handleMessage方法。
msg.target.dispatchMessage(msg);

/*
//Handle system messages here
public void dispatchMessage(Message msg) {
//優先呼叫callback方法
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最後會回撥我們重寫的handleMessage 方法
handleMessage(msg);
}
}

private static void handleCallback(Message message) {
message.callback.run();
}
*/

msg.recycleUnchecked();

returntrue;
}

Loop迴圈不會卡死的原因:

主執行緒的任何程式碼都是被looper從佇列中取出來的執行的,也就是說主執行緒的是接受所有其他執行緒給他傳送訊息來執行動作的,生命週期的回撥也是通過系統服務系統服務ActivityManagerService通過Binder傳送IPC呼叫給APP程序App程序接到到呼叫後,通過App程序的Binder執行緒給主執行緒的訊息佇列插入一條訊息來實現的。而Binder執行緒是在主執行緒進入無限迴圈的時候建立的,而當呼叫messageQueue方法的next如果沒有訊息的時候,主執行緒會釋放當前cpu資源並且進入相當於wait狀態(之後版本實現方法不一樣)


其中LooperThreadLocal息息相關:不妨先看一下ThreadLocal<T>

ThreadLocal: 執行緒本地儲存區(Thread Local Storage,簡稱為TLS),每個執行緒都有自己的私有的本地儲存區域,不同執行緒之間彼此不能訪問對方的TLS區域。這裡執行緒自己的本地儲存區域存放是執行緒自己的Looper。

不同的執行緒執行相對應的函式的時候都會呼叫Looper類中的sThreadLoacl.get獲得對應的執行緒例項

publicTget() {
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null) {
ThreadLocalMap.Entrye=map.getEntry(this);
if(e!=null) {
@SuppressWarnings("unchecked")
Tresult=(T)e.value;
returnresult;
}
}
returnsetInitialValue();
}

//為執行緒設定Looper, prepare中呼叫
publicvoidset(Tvalue) {
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null)
map.set(this,value);
else
createMap(t,value);
}

小結一下:

Looper中管理這一個ThreadLocal物件,用該物件來實現不同執行緒間的looper物件切換以及使用其他功能

Handler

作用:用於同一個程序間的執行緒通訊,

handler為了防止記憶體洩漏會有檢查

//可以隨時獲得主執行緒的handler
publicstaticHandlergetMain() {
if(MAIN_THREAD_HANDLER==null) {
MAIN_THREAD_HANDLER=newHandler(Looper.getMainLooper());
}
returnMAIN_THREAD_HANDLER;
}

//Callback,這也表明了callback函式是執行在當前執行緒上的,不可以執行ui工作
publicinterfaceCallback{
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
booleanhandleMessage(@NonNullMessagemsg);
}

構造方法:新建一個Handler會得到當前執行緒的looper物件以及裡面的MessageQueue,並且獲得一個傳進來的callback物件

publicHandler() {
this(null,false);
}
publicHandler(Callbackcallback,booleanasync) {
//不是static 發出可能記憶體洩露的警告!
if(FIND_POTENTIAL_LEAKS) {
finalClass<?extendsHandler>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物件
thrownewRuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//讓Handler 持有當前執行緒訊息佇列的引用
mQueue=mLooper.mQueue;
//這些callback先不管,主要用於handler的訊息傳送的回撥,優先順序是比handlerMessage高,但是不常用
mCallback=callback;
mAsynchronous=async;
}

Handler 在sendMessage的時候就通過mQueue引用往訊息佇列裡插入新訊息。Handler 的另外一個作用,就是能統一處理訊息的回撥這樣一個Handler發出訊息又確保訊息處理也是自己來做,這樣的設計非常的贊。具體做法就是在佇列裡面的Message持有Handler的引用(哪個handler 把它放到佇列裡,message就持有了這個handler的引用),然後等到主執行緒輪詢到這個message的時候,就來回調我們經常重寫的Handler的handleMessage(Message msg)方法。

在準備啟動一個Activity的時候,系統服務程序下的ActivityManagerService(簡稱AMS)執行緒會通過Binder傳送IPC呼叫給APP程序,App程序接到到呼叫後,通過App程序下的Binder執行緒最終呼叫ActivityThread類下面的scheduleLaunchActivity方法來準備啟動Activity,看下scheduleLaunchActivity方法:

注:Binder執行緒:具體是指ApplicationThread,在App程序中接受系統程序傳遞過來的資訊的執行緒(在主執行緒進入死迴圈之前建立了這個執行緒)。

最後呼叫!!!

publicvoidhandleMessage(@NonNullMessagemsg) {
}


MessageQueue

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

 booleanenqueueMessage(Messagemsg,longwhen) {
// msg 必須有target也就是必須有handler
if(msg.target==null) {
thrownewIllegalArgumentException("Message must have a target.");
}
if(msg.isInUse()) {
thrownewIllegalStateException(msg+" This message is already in use.");
}
//插入訊息佇列的時候需要做同步,因為會有多個執行緒同時做往這個佇列插入訊息
synchronized(this) {
if(mQuitting) {
IllegalStateExceptione=newIllegalStateException(
msg.target+" sending message to a Handler on a dead thread");
Log.w(TAG,e.getMessage(),e);
msg.recycle();
returnfalse;
}

msg.markInUse();
//when 表示這個訊息執行的時間,佇列是按照訊息執行時間排序的
//如果handler 呼叫的是postDelay 那麼when=SystemClock.uptimeMillis()+delayMillis
msg.when=when;
Messagep=mMessages;
booleanneedWake;
if(p==null||when==0||when<p.when) {
// p==null 表示當前訊息佇列沒有訊息
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();
Messageprev;
//將訊息放到佇列的確切位置,佇列是按照msg的when 排序的,連結串列操作自己看咯,從連結串列頭開始放訊息,根據時間塞進去
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;
}

// 如果需要喚醒Looper執行緒,這裡呼叫native的方法實現epoll機制喚醒執行緒,我們就不在深入探討了
if(needWake) {
nativeWake(mPtr);
}
}
returntrue;
}

//兩個版本!!!
privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis) {
//這句話很重要,讓訊息持有當前Handler的引用,在訊息被Looper執行緒輪詢到的時候
//回撥handler的handleMessage方法
msg.target=this;
if(mAsynchronous) {
msg.setAsynchronous(true);
}
//呼叫MessageQueue 的enqueueMessage 方法把訊息放入佇列
returnqueue.enqueueMessage(msg,uptimeMillis);
}

最後執行dispatchMessage

/**
* Handle system messages here.
*/
publicvoiddispatchMessage(Messagemsg) {
//優先呼叫callback方法
if(msg.callback!=null) {
handleCallback(msg);
}else{
if(mCallback!=null) {
if(mCallback.handleMessage(msg)) {
return;
}
}
//最後會回撥我們重寫的handleMessage 方法
handleMessage(msg);
}
}

Message

message又叫task,封裝了任務攜帶的資訊和處理該任務的handler

注意事項: 1.儘管Message有public的預設構造方法,但是你應該通過Message.obtain()來從訊息池中獲得空訊息物件,以節省資源。 2.如果你的message只需要攜帶簡單的int資訊,請優先使用Message.arg1和Message.arg2來傳遞資訊,這比用Bundle更省記憶體 3.擅用message.what來標識資訊,以便用不同方式處理message。

message中的target是一個handler物件

Message中的callBack是runnable物件

Messagenext()

finallongptr=mPtr;
if(ptr==0) {
returnnull;
}
intpendingIdleHandlerCount=-1;// -1 only during first iteration

//nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis
//才會返回
intnextPollTimeoutMillis=0;
for(;;) {
if(nextPollTimeoutMillis!=0) {
Binder.flushPendingCommands();
}
//讀取訊息,隊裡裡沒有訊息有可能會堵塞,兩種情況該方法才會返回(程式碼才能往下執行)
//一種是等到有訊息產生就會返回,
//另一種是當等了nextPollTimeoutMillis時長後,nativePollOnce也會返回
nativePollOnce(ptr,nextPollTimeoutMillis);
//nativePollOnce 返回之後才能往下執行
synchronized(this) {
// Try to retrieve the next message. Return if found.
finallongnow=SystemClock.uptimeMillis();
MessageprevMsg=null;
Messagemsg=mMessages;
//若當前msg不空但是handler已經被釋放,這時需要繼續找到一條能夠執行的handler
if(msg!=null&&msg.target==null) {
// 迴圈找到一條不是非同步而且msg.target不為空的message
do{
prevMsg=msg;
msg=msg.next;
}while(msg!=null&&!msg.isAsynchronous());
}
if(msg!=null) {
if(now<msg.when) {
// 雖然有訊息,但是還沒有到執行的時候,像我們經常用的postDelay,
//計算出離執行時間還有多久賦值給nextPollTimeoutMillis,
//表示nativePollOnce方法要等待nextPollTimeoutMillis時長後返回
nextPollTimeoutMillis=(int)Math.min(msg.when-now,Integer.MAX_VALUE);
}else{
// 獲取到訊息
mBlocked=false;
//連結串列一些操作,獲取msg並且刪除該節點
if(prevMsg!=null)
prevMsg.next=msg.next;
}else{
mMessages=msg.next;
}
msg.next=null;
msg.markInUse();
//返回拿到的訊息
returnmsg;
}
}else{
//沒有訊息,nextPollTimeoutMillis復位
nextPollTimeoutMillis=-1;
}
.....
.....

}

nativePollOnce()實現原理?

總結:

  1. 每一個Thread都能夠成為LooperThread 前提是首先呼叫prepare方法,建立自己的handler,然後呼叫loop()

  2. looperThread擁有自身的MessageQueue物件,loop()方法本質上是每次都呼叫next()方法中獲得下一條訊息,如果沒有獲得的話會阻塞並讓出cpu。

  3. Looper對應的執行緒擁有自己handler物件之後,可以被別的執行緒引用該物件,改handler物件初始化的時候就已經擁有了Looper物件的messageQueue物件,從而可以根據sendMessage()方法呼叫enqueueMessage()將訊息入隊。

  4. Looper物件獲得Message物件之後就呼叫dispatchMessage()方法進行訊息分發,實質上是實現各種回撥,包括message物件以及自身寫好的介面。有一個特殊的CallBack叫mCallback可以在初始化的時候傳入,每次都會呼叫。

一些問題:

1.為什麼一個執行緒只有一個Looper、只有一個MessageQueue?

因為執行緒對應的Looper是在ThreadLocal裡面儲存,它是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定執行緒中可以獲取到儲存的資料,對於其它執行緒來說無法獲取到資料。ThreadLocal它的作用是可以在不同的執行緒之中互不干擾地儲存並提供資料(就相當於一個Map集合,鍵位當前的Thead執行緒,值為Looper物件)。另外,在looper建立的方法looper.prepare()中,會有一個判斷如果當前執行緒存在Looper物件,就會報RunTimeException,所以一個執行緒只有一個Looper,而MQ作為Looper的成員變數自然也就只有一個。

2.如何獲取當前執行緒的Looper?是怎麼實現的?(理解ThreadLocal)

3.是不是任何執行緒都可以例項化Handler?有沒有什麼約束條件?

任何執行緒都可以例項化Handler,handler建立時會關聯一個looper,預設的構造方法將關聯當前執行緒的looper,如果當前執行緒還沒有初始化Looper,或者說當前執行緒還不是looper執行緒,會報RuntimeException。

4.Looper.loop是一個死迴圈,拿不到需要處理的Message就會阻塞,那在UI執行緒中為什麼不會導致ANR?

在應用程式的入口ActivityThread裡面的main方法中會建立一個主執行緒的looper物件和一個大Handler,(這也是為什麼直接在主執行緒拿Handler就有Looper的原因,在其他執行緒是要自己Looper.prepare()) Android是基於事件驅動的,通過looper.looper()不斷接收事件,處理事件,每一個觸控事件或者是Activity的生命週期都是執行在Looper.looper()的控制之下,當收到不同Message時則採用相應措施:在H.handleMessage(msg)方法中,根據接收到不同的msg,執行相應的生命週期。如果它停止了,應用也就停止了。也就是說我們的程式碼其實就是執行在這個迴圈裡面去執行的,當然就不會阻塞。

而所謂ANR便是Looper.loop沒有得到及時處理,一旦沒有訊息,Linux的epoll機制則會通過管道寫檔案描述符的方式來對主執行緒進行喚醒與睡眠,Android裡呼叫了linux層的程式碼實現在適當時會睡眠主執行緒。

拓展

訊息迴圈(死迴圈)的必要性:

對於執行緒既然是一段可執行的程式碼,當可執行程式碼執行完成後,執行緒生命週期便該終止了,執行緒退出。而對於主執行緒,我們是絕不希望會被執行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行程式碼是能一直執行下去的,死迴圈便能保證不會被退出 ActivityThread的main方法主要就是做訊息迴圈,一旦退出訊息迴圈,那麼你的應用也就退出了。如果main方法中沒有looper進行迴圈,那麼主執行緒一執行完畢就會退出。

主執行緒在沒有事件需要處理的時候就是處於阻塞的狀態。想讓主執行緒活動起來一般有兩種方式: 第一種 :是系統喚醒主執行緒,並且將點選事件傳遞給主執行緒; 第二種 :是其他執行緒使用主執行緒的Handler向MessageQueue中存放了一條訊息,導致loop被喚醒繼續執行。

總結 Looer.loop()方法可能會引起主執行緒的阻塞,但只要它的訊息迴圈沒有被阻塞,能一直處理事件就不會產生ANR異常。

looper.looper()阻塞會不會消耗大量的cpu資源

主執行緒Looper從訊息佇列讀取訊息,當讀完所有訊息時,主執行緒阻塞。子執行緒往訊息佇列傳送訊息,並且往管道檔案寫資料,主執行緒即被喚醒,從管道檔案讀取資料,主執行緒被喚醒只是為了讀取訊息,當訊息讀取完畢,再次睡眠,此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生。因此loop的迴圈並不會對CPU效能有過多的消耗。

5.Handler.sendMessageDelayed()怎麼實現延遲的?結合Looper.loop()迴圈中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

Handler.sendMessageDelayed()內部呼叫sendMessageAtTime()把傳入的時間轉化成絕對時間when(延時的時間加上系統當前的時間),然後呼叫MessageQueue的enqueueMessage(),採用執行緒安全的方式將Message插入到訊息佇列中,訊息佇列的插入是由when順序排列,插入的新訊息有三種可能成為訊息佇列的head: (1)訊息佇列為空; (2)引數when為0,因為此時when已經轉成絕對時間,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把訊息插入到佇列的頭部)系列的API才會滿足這個條件; (3)當前的head Message執行時間在when之後,即訊息佇列中無需要在此Message之前執行的Message。 接著就是Looper.looper()啟動訊息迴圈,迴圈開始呼叫messageQueue.next()從訊息佇列中取出一個合理的訊息。如果next()返回null,則looper()直接return,本次訊息迴圈結束。如果訊息不為空則呼叫msg.target.dispatchMessage(msg)處理訊息(msg.target就是Handler)

.next()取下一個訊息的實際執行時間取決於上一個訊息什麼時候處理完 在MessageQueue.next()中,如果在訊息佇列中順序找到了一個訊息msg(前文分析過,訊息佇列的插入是由when順序排列,所以如果當前的訊息沒有到執行時間,其後的也一定不會到),當前的系統時間小於msg.when,那麼會計算一個timeout,以便在到執行時間時wake up;如果當前系統時間大於或等於msg.when,那麼會返回msg給Looper.loop()。所以這個邏輯只能保證在when之前訊息不被處理,不能夠保證一定在when時被處理。 (1)在Loop.loop()中是順序處理訊息,如果前一個訊息處理耗時較長,完成之後已經超過了when,訊息不可能在when時間點被處理。 (2)即使when的時間點沒有被處理其他訊息所佔用,執行緒也有可能被排程失去cpu時間片。 (3)在等待時間點when的過程中有可能入隊處理時間更早的訊息,會被優先處理,又增加了(1)的可能性。 所以由上述三點可知,Handler提供的指定處理時間的api諸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保證在指定時間之前不被執行,不能保證在指定時間點被執行。

關於activityThread的執行

https://blog.csdn.net/shifuhetudi/article/details/52089562