Android進(jìn)階之Handle和Looper消息機(jī)制原理和源碼分析(不走彎路)
本文轉(zhuǎn)載自微信公眾號「Android開發(fā)編程」,作者Android開發(fā)編程 。轉(zhuǎn)載本文請聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號。
前言
App中一般多會有多個線程,多線程之間難免需要進(jìn)行通信。在我們平時開發(fā)中線程通信用的最多的就是Handler,例如子線程進(jìn)行數(shù)據(jù)處理,在主線程中進(jìn)行UI更新。
當(dāng)然了除了Handler這種通信方式外,線程間的通信還有其他幾種方式:管道Pip、共享內(nèi)存、通過文件及數(shù)據(jù)庫等。
我們主要來看下Handler以及其實現(xiàn)原理
一、Looper死循環(huán)詳解
1、死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死ANR
線程默認(rèn)沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper。
我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread,ActivityThread被創(chuàng)建時就會初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。
- public static void main(String[] args) {
- Looper.prepareMainLooper();//創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息
- ActivityThread thread = new ActivityThread();
- thread.attach(false);//建立Binder通道 (創(chuàng)建新線程)
- if (sMainThreadHandler == null) {
- sMainThreadHandler = thread.getHandler();
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Looper.loop();
- //如果能執(zhí)行下面方法,說明應(yīng)用崩潰或者是退出了...
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
這個死循環(huán)會不會導(dǎo)致應(yīng)用卡死,即使不會的話,它會慢慢的消耗越來越多的資源嗎?
①對于線程即是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對于主線程,我們是絕不希望會被運行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出,例如,binder線程也是采用死循環(huán)的方法,通過循環(huán)方式不同與Binder驅(qū)動進(jìn)行讀寫操作,當(dāng)然并非簡單地死循環(huán),無消息時會休眠。但這里可能又引發(fā)了另一個問題,既然是死循環(huán)又如何去處理其他事務(wù)呢?通過創(chuàng)建新線程的方式。真正會卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時間過長,會導(dǎo)致掉幀,甚至發(fā)生ANR,looper.loop本身不會導(dǎo)致應(yīng)用卡死。
②主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?其實不然,這里就涉及到Linux pipe/epoll機(jī)制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時主線程會釋放CPU資源進(jìn)入休眠狀態(tài),直到下個消息到達(dá)或者有事務(wù)發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時監(jiān)控多個描述符,當(dāng)某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)同步I/O,即讀寫是阻塞的。所以說,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資源
2、主線程的消息循環(huán)機(jī)制是什么
主線程進(jìn)入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:
- public static void main(String[] args) {
- //創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息
- Looper.prepareMainLooper();
- //創(chuàng)建ActivityThread對象
- ActivityThread thread = new ActivityThread();
- //建立Binder通道 (創(chuàng)建新線程)
- thread.attach(false);
- Looper.loop(); //消息循環(huán)運行
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
- Activity的生命周期都是依靠主線程的Looper.loop,當(dāng)收到不同Message時則采用相應(yīng)措施:一旦退出消息循環(huán),那么你的程序也就可以退出了。從消息隊列中取消息可能會阻塞,取到消息會做出相應(yīng)的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現(xiàn)象。
- thread.attach(false)方法函數(shù)中便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務(wù)端,用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件),該Binder線程通過Handler將Message發(fā)送給主線程。「Activity 啟動過程」比如收到msg=H.LAUNCH_ACTIVITY,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會通過反射機(jī)制,創(chuàng)建Activity實例,然后再執(zhí)行Activity.onCreate()等方法;
- 再比如收到msg=H.PAUSE_ACTIVITY,則調(diào)用ActivityThread.handlePauseActivity()方法,最終會執(zhí)行Activity.onPause()等方法。
- 主線程的消息又是哪來的呢?當(dāng)然是App進(jìn)程中的其他線程通過Handler發(fā)送給主線程
二、Handler機(jī)制原理詳解
Handler機(jī)制,主要牽涉到的類有如下四個,它們分工明確,但又相互作用
Message:消息
Hanlder:消息的發(fā)起者
Looper:消息的遍歷者
MessageQueue:消息隊列
1、 Looper.prepare()
- public static void prepare() {
- prepare(true);
- }
- private static void prepare(boolean quitAllowed) {
- // 規(guī)定了一個線程只有一個Looper,也就是一個線程只能調(diào)用一次Looper.prepare()
- if (sThreadLocal.get() != null) {
- throw new RuntimeException("Only one Looper may be created per thread");
- }
- // 如果當(dāng)前線程沒有Looper,那么就創(chuàng)建一個,存到sThreadLocal中
- sThreadLocal.set(new Looper(quitAllowed));
- }
從上面的代碼可以看出,一個線程最多只有一個Looper對象。當(dāng)沒有Looper對象時,去創(chuàng)建一個Looper,并存放到sThreadLocal中,sThreadLocal是一個static的ThreadLocal對象,關(guān)于它的詳細(xì)使用,以后有機(jī)會再介紹,這里只要知道,它存儲了Looper對象的副本,并且可以通過它取得當(dāng)前線程在之前存儲的Looper的副本。如下圖:
接下來看Looper的構(gòu)造方法:
- private Looper(boolean quitAllowed) {
- // 創(chuàng)建了MessageQueue,并供Looper持有
- mQueue = new MessageQueue(quitAllowed);
- // 讓Looper持有當(dāng)前線程對象
- mThread = Thread.currentThread();
- }
這里主要就是創(chuàng)建了消息隊列MessageQueue,并讓它供Looper持有,因為一個線程最大只有一個Looper對象,所以一個線程最多也只有一個消息隊列。然后再把當(dāng)前線程賦值給mThread。
MessageQueue的構(gòu)造方法沒有什么可講的,它就是一個消息隊列,用于存放Message。
所以Looper.prepare()的作用主要有以下三點:
- 創(chuàng)建Looper對象
- 創(chuàng)建MessageQueue對象,并讓Looper對象持有
- 讓Looper對象持有當(dāng)前線程
2、new Handler()
Handler有很多構(gòu)造方法,主要是提供自定義Callback、Looper等,我們先從最簡單的無參構(gòu)造方法看起:
- public Handler() {
- this(null, false);
- }
- public Handler(Callback callback, boolean async) {
- // 不相關(guān)代碼
- ......
- //得到當(dāng)前線程的Looper,其實就是調(diào)用的sThreadLocal.get
- mLooper = Looper.myLooper();
- // 如果當(dāng)前線程沒有Looper就報運行時異常
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- // 把得到的Looper的MessagQueue讓Handler持有
- mQueue = mLooper.mQueue;
- // 初始化Handler的Callback,其實就是最開始圖中的回調(diào)方法的2
- mCallback = callback;
- mAsynchronous = async;
- }
首先,調(diào)用了Looper.myLooper,其實就是調(diào)用sThreadLocal.get方法,會得到當(dāng)前線程調(diào)用sThreadLocal.set保存的Looper對象,讓Handler持有它。接下來就會判斷得到的Looper對象是否為空,如果為空,就會報
"Can't create handler inside thread that has not called Looper.prepare(),這不就是我們之前在沒有調(diào)用Looper.prepare就在子線程中創(chuàng)建Handler時報的錯誤嘛。的確,當(dāng)我們沒有調(diào)用Looper.prepare(),則當(dāng)前線程中是沒有Looper對象的。
然后,讓Handler持有得到的Looper對象的MessageQueue和設(shè)置處理回調(diào)的Callback對象(最開始圖中的回調(diào)方法2)。
到這里,默認(rèn)的Handler的創(chuàng)建過程就結(jié)束了,主要有以下幾點:
- 創(chuàng)建Handler對象
- 得到當(dāng)前線程的Looper對象,并判斷是否為空
- 讓創(chuàng)建的Handler對象持有Looper、MessageQueu、Callback的引用
3、Looper.loop()
- public static void loop() {
- // 得到當(dāng)前線程的Looper對象
- final Looper me = myLooper();
- if (me == null) {
- throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
- }
- // 得到當(dāng)前線程的MessageQueue對象
- final MessageQueue queue = me.mQueue;
- // 無關(guān)代碼
- ......
- // 死循環(huán)
- for (;;) {
- // 不斷從當(dāng)前線程的MessageQueue中取出Message,當(dāng)MessageQueue沒有元素時,方法阻塞
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- // Message.target是Handler,其實就是發(fā)送消息的Handler,這里就是調(diào)用它的dispatchMessage方法
- msg.target.dispatchMessage(msg);
- // 回收Message
- msg.recycleUnchecked();
- }
- }
首先還是判斷了當(dāng)前線程是否有Looper,然后得到當(dāng)前線程的MessageQueue。接下來,就是最關(guān)鍵的代碼了,寫了一個死循環(huán),不斷調(diào)用MessageQueue的next方法取出MessageQueue中的Message,注意,當(dāng)MessageQueue中沒有消息時,next方法會阻塞,導(dǎo)致當(dāng)前線程掛起,后面會講到。
拿到Message以后,會調(diào)用它的target的dispatchMessage方法,這個target其實就是發(fā)送消息時用到的Handler。所以就是調(diào)用Handler的dispatchMessage方法,代碼如下:
- public void dispatchMessage(Message msg) {
- // 如果msg.callback不是null,則調(diào)用handleCallback
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- // 如果 mCallback不為空,則調(diào)用mCallback.handleMessage方法
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- // 調(diào)用Handler自身的handleMessage,這就是我們常常重寫的那個方法
- handleMessage(msg);
- }
- }
可以看出,這個方法就是從MessageQueue中取出Message以后,進(jìn)行分發(fā)處理。
首先,判斷msg.callback是不是空,其實msg.callback是一個Runnable對象,是Handler.post方式傳遞進(jìn)來的參數(shù),后面會講到。而hanldeCallback就是調(diào)用的Runnable的run方法。
然后,判斷mCallback是否為空,這是一個Handler.Callback的接口類型,之前說了Handler有多個構(gòu)造方法,可以提供設(shè)置Callback,如果這里不為空,則調(diào)用它的hanldeMessage方法,注意,這個方法有返回值,如果返回了true,表示已經(jīng)處理 ,不再調(diào)用Handler的handleMessage方法;如果mCallback為空,或者不為空但是它的handleMessage返回了false,則會繼續(xù)調(diào)用Handler的handleMessage方法,該方法就是我們經(jīng)常重寫的那個方法。
關(guān)于從MessageQueue中取出消息以后的分發(fā),如下面的流程圖所示:
所以Looper.loop的作用就是:
從當(dāng)前線程的MessageQueue從不斷取出Message,并調(diào)用其相關(guān)的回調(diào)方法。
4、發(fā)送消息
使用Handler發(fā)送消息主要有兩種,一種是sendXXXMessage方式,還有一個postXXX方式,不過兩種方式最后都會調(diào)用到sendMessageDelayed方法,所以我們就以最簡單的sendMessage方法來分析。
我們先來看Handler的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) {
- // 這里拿到的MessageQueue其實就是創(chuàng)建時的MessageQueue,默認(rèn)情況是當(dāng)前線程的Looper對象的MessageQueue
- // 也可以指定
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- // 調(diào)用enqueueMessage,把消息加入到MessageQueue中
- return enqueueMessage(queue, msg, uptimeMillis);
- }
- 主要實現(xiàn)是調(diào)用enqueueMessage來實現(xiàn)的,看看該方法:
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- // 把當(dāng)前Handler對象,也就是發(fā)起消息的handler作為Message的target屬性
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- // 調(diào)用MessageQueue中的enqueueMessage方法
- return queue.enqueueMessage(msg, uptimeMillis);
- }
首先,把當(dāng)前Handler作為Message的target屬性,方便Looper從MessageQueue中取出Message時進(jìn)行消息處理。然后調(diào)用了MessageQueue的enqueueMessage方法,把handler發(fā)送的消息加入到MessageQueue,供Looper去取出來處理。我們記下來看看。
MessageQueue的enqueueMessage方法:
- MessageQueue的enqueueMessage方法:
- boolean enqueueMessage(Message msg, long when) {
- if (msg.target == null) {
- throw new IllegalArgumentException("Message must have a target.");
- }
- // 一個Message,只能發(fā)送一次
- 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;
- }
- // 標(biāo)記Message已經(jīng)使用了
- msg.markInUse();
- msg.when = when;
- // 得到當(dāng)前消息隊列的頭部
- Message p = mMessages;
- boolean needWake;
- // 我們這里when為0,表示立即處理的消息
- if (p == null || when == 0 || when < p.when) {
- // 把消息插入到消息隊列的頭部
- msg.next = p;
- mMessages = msg;
- needWake = mBlocked;
- } else {
- // 根據(jù)需要把消息插入到消息隊列的合適位置,通常是調(diào)用xxxDelay方法,延時發(fā)送消息
- 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;
- }
首先,判斷了Message是否已經(jīng)使用過了,如果使用過,則直接拋出異常,這是可以理解的,如果MessageQueue中已經(jīng)存在一個Message,但是還沒有得到處理,這時候如果再發(fā)送一次該Message,可能會導(dǎo)致處理前一個Message時,出現(xiàn)問題。
然后,會判斷when,它是表示延遲的時間,我們這里沒有延時,所以為0,滿足if條件。把消息插入到消息隊列的頭部。如果when不為0,則需要把消息加入到消息隊列的合適位置。
最后會去判斷當(dāng)前線程是否已經(jīng)阻塞了,如果阻塞了,則需要調(diào)用本地方法去喚醒它。
以上是sendMessage的全部過程,其實就是把Message加入到MessageQueue的合適位置。那我們來簡單看看post系列方法:
- public final boolean post(Runnable r)
- {
- return sendMessageDelayed(getPostMessage(r), 0);
- }
- private static Message getPostMessage(Runnable r) {
- // 構(gòu)造一個Message,并讓其callback執(zhí)行傳來的Runnable
- Message m = Message.obtain();
- m.callback = r;
- return m;
- }
可以看到,post方法只是先調(diào)用了getPostMessage方法,用Runnable去封裝一個Message,然后就調(diào)用了sendMessageDelayed,把封裝的Message加入到MessageQueue中。
所以使用handler發(fā)送消息的本質(zhì)都是:把Message加入到Handler中的MessageQueue中去。
三、Handler 是如何能夠線程切換
Handler創(chuàng)建的時候會采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng),Looper在哪個線程創(chuàng)建,就跟哪個線程綁定,并且Handler是在他關(guān)聯(lián)的Looper對應(yīng)的線程中處理消息的。
那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢—–ThreadLocal。
ThreadLocal可以在不同的線程中互不干擾的存儲并提供數(shù)據(jù),通過ThreadLocal可以輕松獲取每個線程的Looper。當(dāng)然需要注意的是:
①線程是默認(rèn)沒有Looper的,如果需要使用Handler,就必須為線程創(chuàng)建Looper。我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread;
②ActivityThread被創(chuàng)建時就會初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。
系統(tǒng)為什么不允許在子線程中訪問UI?這是因為Android的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對UI控件的訪問加上鎖機(jī)制呢?缺點有兩個:①首先加上鎖機(jī)制會讓UI訪問的邏輯變得復(fù)雜 ②鎖機(jī)制會降低UI訪問的效率,因為鎖機(jī)制會阻塞某些線程的執(zhí)行。所以最簡單且高效的方法就是采用單線程模型來處理UI操作。
四、Handler造成內(nèi)存泄露
1、引起內(nèi)存泄露原因
Java使用有向圖機(jī)制,通過GC自動檢查內(nèi)存中的對象(什么時候檢查由虛擬機(jī)決定),如果GC發(fā)現(xiàn)一個或一組對象為不可到達(dá)狀態(tài),則將該對象從內(nèi)存中回收。也就是說,一個對象不被任何引用所指向,則該對象會在被GC發(fā)現(xiàn)的時候被回收;另外,如果一組對象中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個對象A和B互相持有引用,但沒有任何外部對象持有指向A或B的引用),這仍然屬于不可到達(dá),同樣會被GC回收。
Android中使用Handler造成內(nèi)存泄露的原因
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- mImageView.setImageBitmap(mBitmap);
- }
- }
上面是一段簡單的Handler的使用。當(dāng)使用內(nèi)部類(包括匿名類)來創(chuàng)建Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用(不然你怎么可能通過Handler來操作Activity中的View?)。而Handler通常會伴隨著一個耗時的后臺線程(例如從網(wǎng)絡(luò)拉取圖片)一起出現(xiàn),這個后臺線程在任務(wù)執(zhí)行完畢(例如圖片下載完畢)之后,通過消息機(jī)制通知Handler,然后Handler把圖片更新到界面。然而,如果用戶在網(wǎng)絡(luò)請求過程中關(guān)閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由于這時線程尚未執(zhí)行完,而該線程持有Handler的引用(不然它怎么發(fā)消息給Handler?),這個Handler又持有Activity的引用,就導(dǎo)致該Activity無法被回收(即內(nèi)存泄露),直到網(wǎng)絡(luò)請求結(jié)束(例如圖片下載完畢)。另外,如果你執(zhí)行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,那么在你設(shè)定的delay到達(dá)之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導(dǎo)致你的Activity被持有引用而無法被回收。
2、解決方法
①在關(guān)閉Activity的時候停掉你的后臺線程。線程停掉了,就相當(dāng)于切斷了Handler和外部連接的線,Activity自然會在合適的時候被回收;
②如果你的Handler是被delay的Message持有了引用,那么使用相應(yīng)的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行了;
③將Handler聲明為靜態(tài)類,由于Handler不再持有外部類對象的引用,導(dǎo)致程序不允許你在Handler中操作Activity中的對象了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference)
- private static class MyHandler extends Handler {
- WeakReference<MainActivity> mActivity;
- MyHandler(MainActivity mActivity){
- this.mActivity = new WeakReference<MainActivity>(mActivity);
- }
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what){
- case IMAGE_FAILURE:
- Toast.makeText(mActivity.get()
- , "Image Failure", Toast.LENGTH_LONG).show();
- break;
- }
- }
- }
總結(jié)
Handler消息機(jī)制主要的四個類的功能:
- Message:信息的攜帶者,持有了Handler,存在MessageQueue中,一個線程可以有多個。
- Hanlder:消息的發(fā)起者,發(fā)送Message以及消息處理的回調(diào)實現(xiàn),一個線程可以有多個Handler對象。
- Looper:消息的遍歷者,從MessageQueue中循環(huán)取出Message進(jìn)行處理,一個線程最多只有一個。
- MessageQueue:消息隊列,存放了Handler發(fā)送的消息,供Looper循環(huán)取消息,一個線程最多只有一個。