Handler機制源碼分析

目錄

image

前言

Handler使用,請參見:Handler總結和補充_fdsafwagdagadg6576的專欄-CSDN博客

正文

Hander源碼機制包括四部分源碼:
Handler源碼,Looper源碼Message源碼,Messagequeue源碼.
分成java層和native層.
消息流程
handler---messageQueue---Looper---hander,消息發送---消息接收---消息分發---消息處理.
類圖關系

handler_java

1 Handler類

1.1 Handler 構造函數初始化

  1. 無參數構造函數
public class Handler {
    /**我們通常用于創建Handler的構造方法之一*/
    public Handler() {
        this(null, false);
    }
    //===========step1: 構造函數====================
    public Handler(Callback callback, boolean async) {
        ......
        //=======step2://重點;獲取Looper(messagequeue管理者)=== /
        mLooper = Looper.myLooper();
        //=======step3: 獲取Looper的Messagequeue=====
        mQueue = mLooper.mQueue;  
        //回調函數默認是Null  
        mCallback = callback; 
        //設置消息是否為異步處理方式 
        mAsynchronous = async; 
    }

根據調用關系:
step1: Handler構造函數中會去創建一個Looper對象。handler和Looper綁定,同時綁定Looper的messageQueue。
step2: Looper.myLooper()獲取Looper.
step3: mLooper.mQueue 獲取Looper的MessageQueue.

  1. 有參構造函數
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Handler類在構造方法中,可指定Looper,Callback回調方法以及消息的處理方式(同步或異步),對于無參的handler,默認是當前線程的Looper.
mainHandler = new Handler() 等價于 new Handler(Looper.myLooper())
Looper.myLooper():獲取當前進程的looper對象。詳見下文.
1.2 Handler send message

call laddder:
sendEmptyMessage
--sendEmptyMessageDelayed
----sendMessageDelayed
------sendMessageAtTime
--------enqueueMessage
/////////////////////////////////////////////////////////////////////////
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    ......
    return enqueueMessage(queue, msg, uptimeMillis);
}
// 最后調用此方法添加到消息隊列中
private boolean enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis) {
    msg.target = this;// 設置發送目標對象是Handler本身
    ......
    return queue.enqueueMessage(msg, uptimeMillis);// 添加到消息隊列中
}

step1: Message.obtain
step2:enqueueMessage(queue, msg, uptimeMillis)
step3: msg.target = this;// 設置發送目標對象是Handler本身
return queue.enqueueMessage(msg, uptimeMillis);// 添加到消息隊列中
1.3 消息處理
case1: handleMessage(msg);
是派生一個Hanlder子類并重寫其handleMessage方法來處理具體的消息。
發送發是sendMessage.
case 2:handleCallback & mCallback.handleMessage(msg)
用callback來創建一個Handler的實例而無需派生Handler的子類。而Callback給我們提供了另外一種方式,那就是當我們不想派生子類的時候,可以通過Callback來實現.

call ladder: 
post
--sendMessageDelayed
----getPostMessage
------m.callback = r//Runnable對象
--------new Handler(callback)

Message的callback是什么?其實就是一個Runnable對象,實際上就是Handler的post方法所傳遞的Runnable參數

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

發送方:

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;
}

最后要注意的是寫在Looper.loop()之后的代碼不會被執行,這個函數內部應該是一個循環,當調用mHandler.getLooper().quit()后,loop()才會中止,其后的代碼才能得以運行.
1.4 異常處理
內存泄漏

//檢查Handler是否是static的;如果不是的,那么有可能導致內存泄露
        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());  
            }  
        }

小結:handler類:1) init 綁定looper,queue; 2) send msg 3) handle msg .
handler模型是以消息為中心,將消息放入主線程,消息綁定了handler對象,進而調用了handler執行函數。實現了對象,消息和函數綁定.
handler.sendmessage(msg)--msg.target.handlemsg(msg).
改寫成以對象為中心的實現call ladder:Looper.enqueuemessage(handler)---handler.handlemessage(handler.msg).

2 Looper類

looper類:1) create queue 2) 循環讀取 3) 分發
looper是兩個線程,一個線程send message放入msg,一個線程epoll_wait 獲取消息.
Looper.loop()循環取Message中的消息,回調msg.target.dispatchMessage(msg)。
Handler sendMessage 調用enqueueMessage,enqueueMessage第一行msg.target = this;這個this是什么呢?這個this在handler方法中自然是handler本身了,所以msg.target.dispatchMessage(msg);可以找到正確的handler,消息分開不會出錯.
2.1 創建MessageQueue

//Looper類的構造方法創建了消息隊列MessageQueue對象
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mRun = true;
    mThread = Thread.currentThread();
}

一個線程一個Looper一個MessageQueue.
2.2 獲取Looper對象
通過ThreadLocal獲取Looper對象

//handler調用的獲取Looper對象的方法。實際是在ThreadLocal中獲取。
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

2.3 Looper 主循環
Looper 讀取message & 分發message

//looper中最重要的方法loop(),該方法是個死循環,會不斷去消息隊列MessageQueue中獲取消息,
//然后調dispatchMessage(msg)方法去執行
public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        //進入loop的主循環方法
        for (;;) {
            Message msg = queue.next(); // might block
            ......
            //msg.target is handler;分發消息
            msg.target.dispatchMessage(msg);
            msg.recycle();
         }
}
  • Message msg= queue.next() ; 讀取MessageQueue的下一條Message;
  • msg.target.dispatchMessage(msg);把Message分發給相應的target;
    next源碼分析,參見MessageQueue類分析
    下面是dispatchMessage分析
// 在looper類中的loop()方法內部調用的方法
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }

分發給handleMessage 和handleCallback 處理.

2.4 子線程創建Looper(擴展)

如果子線程中使用Looper.prepare()和Looper.loop()創建了消息隊列,就可以讓消息處理在該線程中完成.
Looper.prepare() 通過map ThreadLocal綁定線程和新建的Looper(包含MessageQueue).判斷looper在哪個線程內運行。
Looper.loop()不斷地從當前線程對應Looper的MessageQueue中取出消息進行處理.

//perpare()方法,用來初始化一個Looper對象
public static void prepare() {
    prepare(true);
}       
private static void prepare(boolean quitAllowed) {
    ......
    sThreadLocal.set(new Looper(quitAllowed));
}

在非主線程中直接new Handler() 會報如下的錯誤: Can't create handler inside thread that has not called Looper.prepare() 原因是非主線程中默認沒有創建Looper對象,需要先調用Looper.prepare()啟用Looper,然后再調用Looper.loop()。
子線程自己創建Looper

class childThread extends Thread{
        public Handler mHandler;
        @Override
        public void run() {
            //子線程中必須先創建Looper
            Looper.prepare();

            mHandler =new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //處理消息
                }
            };
            //啟動looper循環
            Looper.loop();
        }
    }

2.5 ThreadLocal
ThreadLocal機制:通常的全局變量,各個子線程是共享的。ThreadLocal變量特殊性是,它是global變量,但是每個子線程的ThreadLocal值是私有的.
原理是內部有個map管理每個線程和對應的變量.感興趣的同學可以在網上搜顯示源碼.
ThreadLocal和線程局部變量又什么區別?線程局部變量完全可以替代ThreadLocal,但是ThreadLocal使用簡單,java采用局部變量的方法比較少.兩者的關系錢存在自己家和銀行,實際是一樣. Threadlocal 可以使一個Looper類統一管理所有線程的looper.
ThreadLocal與局部變量 ThreadLocal與局部變量_Jack Cheung-CSDN博客

3 MessageQueue類

線程同步在messageQueue的native層實現.
消息的循環,發送和處理
1) 生產者線程和消費者線程同步:
如果messageQueue為空: messageQueued.next 通過epoll監聽messagequeue綁定的文件fd,實現讀寫同步.
如果messageQueue不為空: 生產者線程寫,消費者線程讀
nativePollOnce 阻塞,nativeWake 喚醒

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    //...
    synchronized (this) {
        boolean needWake;
        Message p = mMessages;
         //如果處于阻塞狀態,并且鏈表頭部是一個同步屏障,并且插入消息是最早的異步消息,需要喚醒
            needWake = mBlocked && p.target == null && msg.isAsynchronous();            
            Message prev;
         //下面是一個鏈表的插入操作,將消息按時間順序插入到mMessages中
            for (;;) {
                prev = p;
                p = p.next;
               //如果在找到插入位置之前,發現了異步消息的存在,不需要喚醒
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }        
        //nativeWake方法會喚醒當前線程的MessageQueue
        if (needWake) {
            nativeWake(mPtr);
        }     
}

next() :提取下一條message.在這里messagequeue 阻塞. epoll 監聽與messagequeue綁定的文件fd,詳見:玩Android必看:從源碼角度看Handler - 嗶哩嗶哩

Message next() {
    final long ptr = mPtr;
    ......
    for (;;) {
        //阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
    ......
    }
}
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jint ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(timeoutMillis);
}
void NativeMessageQueue::pollOnce(int timeoutMillis) {
    mLooper->pollOnce(timeoutMillis);
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
        result = pollInner(timeoutMillis);
}
int Looper::pollInner(int timeoutMillis) {
    ......
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    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) {
                awoken();
            }
    }
}
void Looper::awoken() {
    ......
    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}

2) 多個生產者線程同時寫:使用messageQueued.enqueueMessage中的鎖同步.
messageQueue 從java-jni-native c++流程,參見:Android消息機制2-Handler(Native層) - Gityuan博客 | 袁輝輝的技術博客

4 Message.java源碼:

讀寫Parcel數據

    private void readFromParcel(Parcel source) {
        what = source.readInt();
        arg1 = source.readInt();
        arg2 = source.readInt();
        if (source.readInt() != 0) {
            obj = source.readParcelable(getClass().getClassLoader());
        }
        when = source.readLong();
        data = source.readBundle();
        replyTo = Messenger.readMessengerOrNullFromParcel(source);
        sendingUid = source.readInt();
    }
public void writeToParcel(Parcel dest, int flags) 
{ 
     Parcelable p = (Parcelable)obj;   
     dest.writeInt(1); 
     dest.writeParcelable(p, flags); 
     ......
}

QA:

  1. 主線程中的looper是一直輪詢嗎?
    不是。如果MessageQueue沒有消息時,就會阻塞在loop的queue.next()方法里. 這時候主線程會釋放CPU資源進入休眠狀態,直到有下個消息進來時候就會喚醒主線程. 使用Linux pipe/epoll機制,通過往pipe管道寫端寫入數據來喚醒主線程工作。原理類似于I/O,讀寫是堵塞的,不占用CPU資源。 \
  2. handler中發送消息線程安全嗎?
    線程安全。因為使用lock進行了線程同步.
    MessageQueue中插入消息enqueueMessage方法以及取消息next都是線程安全的,都使用了synchronized進行加鎖.
    小結:
    Handler源碼:主要是綁定looper,sendmessage,handlemessage.
    Looper源碼: 主要是prepare-->Loop { queue.next;msg.target.dispatchMessage(msg); -->sThreadLocal.set(new Looper)}
Messagequeue.java源碼:
Message next() {
    nativePollOnce//epoll wait
}
enqueueMessage {
   nativeWake //epoll wake 
}
Message.java源碼
readFromParcel,writeToParcel

附錄源碼:Handler.java, Looper.java , Message.java,MessageQueue.java

類似文章: Android的消息機制(java層) Android的消息機制(java層) - 簡書
Android消息機制(native層) Android消息機制(native層) - 簡書

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容