Handler實現(xiàn)機制淺析

Handler是Android中的消息處理機制议蟆,是一種線程間通信的解決方案闷沥,同時你也可以理解為它天然的為我們在主線程創(chuàng)建一個隊列,隊列中的消息順序就是我們設(shè)置的延遲的時間咐容。

簡單使用

一般是在主線程中實現(xiàn)一個Handler舆逃,然后在子線程中使用它。

class HandlerActivity: AppCompatActivity() {
 
    private val mHandler = MyHandler()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在子線程中通過自定義的 Handler 發(fā)消息
        thread {
            mHandler.sendEmptyMessageDelayed(1, 1000)
        }
    }
 
    // 自定義一個 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
        }
    }
}

或者有時候需要在子線程中創(chuàng)建運行在主線程中的Handler

class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread {
            //獲得main looper 運行在主線程
            mHandler = MyHandler(Looper.getMainLooper())
            mHandler!!.sendEmptyMessageDelayed(1, 1000)
        }
    }
     // 自定義一個 Handler
    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
        }
    }
}

在第二個用法中出現(xiàn)了一個Looper.getMainLooper()戳粒,使用它作為參數(shù)路狮,即使MyHandler是在子線程中定義的,但是它的handleMessage方法依然運行在主線程蔚约。我們看一下Handler的構(gòu)造方法之一:

public Handler(@NonNull Looper looper) {
       this(looper, null, false);
}

handleMessage方法具體運行在哪個線程是和這個Looper息息相關(guān)的奄妨。

概述

image

這就是整個Handler在Java層的流程示意圖∑凰睿可以看到砸抛,在Handler調(diào)用sendMessage方法以后,Message對象會被添加到MessageQueue中去苔咪。而這個MessageQueue就是被包裹在了Looper中锰悼。那么Looper對象是干什么的呢?它和Handler是什么關(guān)系呢团赏?我們來看一下他們具體的職責吧~

  • Handler

消息機制中作為一個對外暴露的工具箕般,其內(nèi)部持有了一個 Looper負責Message的發(fā)送及處理舔清。

Handler.post(Runnable r)/Handler.sendMessage(Message msg) :向消息隊列發(fā)送各種消息事件丝里;
Handler.handleMessage():處理相應(yīng)的消息事件

  • Looper

作為消息循環(huán)的核心曲初,其內(nèi)部包含了一個消息隊列 MessageQueue ,用于記錄所有待處理的消息杯聚;通過Looper.loop()不斷地從MessageQueue中抽取Message臼婆,按分發(fā)機制將消息分發(fā)給目標處理者,可以看成是消息泵幌绍。注意颁褂,線程切換就是在這一步完成的。

  • MessageQueue

則作為一個消息隊列傀广,則包含了一系列鏈接在一起的 Message 颁独;不要被這個Queue的名字給迷惑了,就以為它是一個隊列伪冰,但其實內(nèi)部通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表誓酒,等待Looper的抽取。

  • Message

則是消息體贮聂,內(nèi)部又包含了一個目標處理器 target 靠柑,這個 target 正是最終處理它的 Handler。

Handler

從我們大家最熟悉的sendMessage方法說起吓懈。sendMessage方法見名思意歼冰,就是發(fā)送一個信息,可是要發(fā)送到哪里去呢骄瓣,這是代碼:

# Handler.java
public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}


public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
       // focus -1 
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

不管我們是通過post/send發(fā)送消息停巷,最終都會調(diào)用到enqueueMessage方法,在這個方法中做了兩件事:
1.給Message賦值
2.將Message按照時間順序放入隊列

最后的入隊列是在MessageQueue中完成的榕栏,已經(jīng)不再是Handler的方法了畔勤,也就是說,調(diào)用走到了這里扒磁。事件的流向已經(jīng)不歸Handler管了庆揪。Handler只負責把Message發(fā)送出去,然后等待時機處理這條Message妨托,至于Message存取的過程跟Handler沒有關(guān)系缸榛。

注意focus1處的代碼,Message將當前的Handler對象賦值給了target字段兰伤,所以說:

Handler在發(fā)送Message(消息)時内颗,每個發(fā)出去的Message都持有把它發(fā)出去的Handler的引用。

MessageQueue

MessageQueue是一個由單鏈表構(gòu)成的優(yōu)先級隊列(取的都是頭部敦腔,所以說是隊列)找前。前面提到Handler發(fā)送消息入隊是由MessageQueue來完成的,那么MessageQueue是在哪里定義好的呢躺盛?

答案是在Handler的構(gòu)造函數(shù)中:

public Handler(@Nullable Callback callback, boolean async) {
    // ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    // ...
}                                                     
 

可以看到项戴,Handler中持有的MessageQueue是從Looper中拿到的。關(guān)于Looper稍后再講槽惫。

上面說到周叮,最后發(fā)送消息都調(diào)用的是MessageQueuequeue.enqueueMessage(msg, uptimeMillis)方法。現(xiàn)在我們已經(jīng)拿到了queue,進去看看這個方法它做了什么。

// MessageQueue.java
//省略部分代碼
boolean enqueueMessage(Message msg, long when) {
 
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }
 
        msg.markInUse();
        msg.when = when;
 
        //【1】拿到隊列頭部
        Message p = mMessages;
        boolean needWake;
 
        //【2】如果消息不需要延時北秽,或者消息的執(zhí)行時間比頭部消息早畴蹭,插到隊列頭部
        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 {
            //【3】消息插到隊列中間
            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;
}

從以上代碼也可看出來:

在多個HandlerMessageQueue中添加數(shù)據(jù)(發(fā)送消息時各個Handler可能處于不同線程)時得糜,其內(nèi)部通過synchronized關(guān)鍵字保證線程安全。同時messagequeue.next()內(nèi)部也會通過synchronized加鎖晰洒,確保取的時候線程安全朝抖,同時插入也會加鎖。

1.mMessages 是隊列的第一消息谍珊,獲取到它判斷消息隊列是不是空的侮邀,是則將當前的消息放到隊列頭部;

2.如果當前消息不需要延時贝润,或當前消息的執(zhí)行時間比頭部消息早绊茧,也是放到隊列頭部。

3.如果不是以上情況打掘,說明當前隊列不為空华畏,并且隊列的頭部消息執(zhí)行時間比當前消息早,需要將它插入到隊列的中間位置尊蚁。

如何判斷這個位置呢亡笑?依然是通過消息被執(zhí)行的時間。
通過遍歷整個隊列横朋,當隊列中的某個消息的執(zhí)行時間比當前消息晚時仑乌,將消息插到這個消息的前面。

可以看到,消息隊列是一個根據(jù)消息【執(zhí)行時間先后】連接起來的單向鏈表绝骚。想要獲取可執(zhí)行的消息耐版,只需要遍歷這個列表,對比當前時間與消息的執(zhí)行時間压汪,就知道消息是否需要執(zhí)行了粪牲。

Looper

Handler中的MessageQueue對象其實就是Handler中的Looper它的MessageQueueHandlerMessageQueue中添加消息止剖,其實就是往HandlerLooper所持有的MessageQueue中添加對象腺阳。
簡單來說,LooperMessageQueue是一對一的關(guān)系,一個Looper持有一個MessageQueue對象穿香。

回到之前Handler構(gòu)造函數(shù)的代碼:

//Handler.java
//省略部分代碼
public Handler(@Nullable Callback callback, boolean async) {
    //敲黑板亭引,劃重點就是這一句!Fせ瘛1候尽!
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
}

在這一句中Handler通過Looper.myLooper方法獲取到了Looper對象洒宝,當然购公,也有可能沒獲取到。不過雁歌,你如果沒獲取到就要拋異常了宏浩。

# Looper.java
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
TreadLocal
//sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

sThreadLocal是一個ThreadLocal類,并且它的泛型是Looper對象靠瞎。ThreadLocal提供了線程的局部變量比庄,每個線程都可以通過set()和get()來對這個局部變量進行操作,但不會和其他線程的局部變量進行沖突乏盐,實現(xiàn)了線程的數(shù)據(jù)隔離佳窑。簡要言之:往ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的父能。

可以看到Looper對象是通過ThreadLocal獲取的华嘹,那么ThreadLocal是何時放進去的呢?查看sThreadLoacl.set()方法的調(diào)用位置:

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

可以看得出法竞,最后調(diào)用了是prepare(boolean quitAllowed)方法耙厚,而這個方法首先判斷,如果sThreadLocal有值岔霸,就拋異常薛躬,沒有值才會塞進去一個值。其實很好理解呆细,就是說prepare方法必須調(diào)用但也只能調(diào)用一次型宝,不調(diào)用沒有值八匠,拋異常,調(diào)用多次也還拋異常趴酣。

如果Looper為空就拋異常梨树,現(xiàn)在我們知道了,什么時候Looper為空呢岖寞?沒有調(diào)用prepare方法的時候會為null抡四。

也就是說在構(gòu)造Handler之前,必須得有Looper對象仗谆,換言之指巡,在構(gòu)造Handler之前,必須調(diào)用Looperprepare方法創(chuàng)建Looper隶垮。

接下來再看看這行sThreadLocal.set(new Looper(quitAllowed));做了什么吧藻雪,它是如何塞進去的呢?

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

set方法首先獲取到了當前的線程狸吞,然后獲取一個map勉耀。這個map是以鍵值對形式存儲內(nèi)容的。如果獲取的map為空蹋偏,就創(chuàng)建一個map瑰排。如果不為空就塞進去值。要注意的是暖侨,這里面的key是當前的線程,這里面的value就是Looper崇渗。也就是說字逗,線程和Looper是一一對應(yīng)的。也就是很多人說的Looper和線程綁定了宅广,其實就是以鍵值對形式存進了一個map中葫掉。

Looper中的MessageQueue是在Looper的構(gòu)造函數(shù)中創(chuàng)建的:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
Looper是和當前線程綁定的。并且每個線程中只有一個LooperMessageQueue跟狱。

前面提到俭厚,創(chuàng)建Handler對象之前需要調(diào)用Looper.prepare()方法先創(chuàng)建一個Looper對象,但我們在activity頁面中創(chuàng)建Handler的時候并沒有這么做驶臊,那是因為主線程默認已經(jīng)創(chuàng)建Looper了挪挤。因此我們先來看看主線程中是如何處理的。

查看ActivityThread類关翎,這是整個app的入口扛门,內(nèi)部有一個main方法,它是程序的入口:

//ActivityThread.java  
public static void main(String[] args) {  
    ···  
    Looper.prepareMainLooper();  
    ··· 
     ActivityThread thread = new ActivityThread();  
    thread.attach(false, startSeq);  
    if (sMainThreadHandler == null) {  
        sMainThreadHandler = thread.getHandler();  
    }  
    if (false) {  
        Looper.myLooper().setMessageLogging(new  
                LogPrinter(Log.DEBUG, "ActivityThread"));  
    }  
    // End of event ActivityThreadMain.  
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
    Looper.loop();  
    throw new RuntimeException("Main thread loop unexpectedly exited");  
} 

可以看到在ActivityThread中的main方法中纵寝,我們先調(diào)用了Looper.prepareMainLooper()方法论寨,然后獲取當前線程的Handler,最后調(diào)用Looper.loop()。先來看一下Looper.prepareMainLooper()方法

//Looper.java    
/**  
* Initialize the current thread as a looper, marking it as an  
* application's main looper. The main looper for your application  
* is created by the Android environment, so you should never need  
* to call this function yourself.  See also: {@link #prepare()}  
*/  
public static void prepareMainLooper() {  
    // 設(shè)置不可以退出的Looper
     prepare(false);  
     synchronized (Looper.class) {  
         if (sMainLooper != null) {  
             throw new IllegalStateException("The main Looper has already been prepared.");  
         } 
          sMainLooper = myLooper();  
     }  
}  
//prepare  
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.loop

Looper雖說要分發(fā)消息葬凳,但是它又不知道你什么時候會發(fā)送消息绰垂,只能開啟一個死循環(huán),不斷的嘗試從隊列中拿數(shù)據(jù)火焰。這個死循環(huán)在哪里開始的劲装?Looper.loop()開啟了一個死循環(huán),然后不斷的嘗試去隊列中拿消息荐健。

// Looper.java
public static void loop() {
 
    //拿到當前線程的Looper
    final Looper me = myLooper();
    ...
    //拿到Looper的消息隊列
    final MessageQueue queue = me.mQueue;
    ...
    //1 這里開啟了死循環(huán)
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
        // 回收復用
        msg.recycleUnchecked();
    }
}

在循環(huán)中Looper不停的取出消息酱畅,拿到Message對象以后,會去調(diào)用Message的target字段的dispatchMessage方法;target正是發(fā)送這條消息的Handler江场。

public void dispatchMessage(@NonNull Message msg) {  
     if (msg.callback != null) {  
         handleCallback(msg);  
     } else {  
         //如果 callback 處理了該 msg 并且返回 true纺酸, 就不會再回調(diào) handleMessage  
         if (mCallback != null) { 
              if (mCallback.handleMessage(msg)) {  
                 return;  
             }  
         }  
         handleMessage(msg);  
     }  
 } 

如果Message這個對象有CallBack回調(diào)的話,這個CallBack實際上是個Runnable址否,就只執(zhí)行這個回調(diào)餐蔬,然后就結(jié)束了。

如果Message對象沒有CallBack回調(diào)佑附,進入else分支判斷Handler的CallBack是否為空樊诺,不為空執(zhí)行CallBack的handleMessage方法,然后return音同,構(gòu)建Handler的CallBack代碼如下:

Handler.Callback callback = new Handler.Callback() {  
    @Override  
    public boolean handleMessage(@NonNull Message msg) {  
        //retrun true词爬,就不執(zhí)行下面的邏輯了,可以用于做優(yōu)先級的處理  
        return false;  
    }  
}; 

最后才調(diào)用到Handler的handleMessage()函數(shù)权均,也就是我們經(jīng)常去重寫的函數(shù)顿膨,在該方法中做消息的處理。

消息攔截

可以看到Handler.Callback 有優(yōu)先處理消息的權(quán)利 叽赊,當一條消息被 Callback 處理并攔截(返回 true)恋沃,那么 Handler 的 handleMessage(msg) 方法就不會被調(diào)用了;
如果 Callback 處理了消息必指,但是并沒有攔截囊咏,那么就意味著一個消息可以同時被 Callback 以及 Handler 處理。我們可以利用CallBack這個攔截來攔截Handler的消息塔橡。

Handler是如何進行線程切換的

線程間是共享資源的梅割,子線程通過handler.sendXXX,handler.postXXX等方法發(fā)送消息葛家,然后通過Looper.loop()在消息隊列中不斷的循環(huán)檢索消息炮捧,最后交給handle.dispatchMessage方法進行消息的分發(fā)處理。

子線程可以更新UI嗎惦银?

查看以下代碼:

@Override  
  protected void onCreate(@Nullable Bundle savedInstanceState) {  
      super.onCreate(savedInstanceState);  
      setContentView(R.layout.activity_three);  
      new Thread(new Runnable() {  
          @Override  
          public void run() {  
              //創(chuàng)建Looper咆课,MessageQueue  
              Looper.prepare();  
              new Handler().post(new Runnable() {  
                  @Override  
                  public void run() {  
                      Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();  
                  }  
              });  
              //開始處理消息  
              Looper.loop();  
          }  
      }).start();  
  } 

這里需要注意在所有事情處理完成后應(yīng)該調(diào)用quit方法來終止消息循環(huán)末誓,否則這個子線程就會一直處于循環(huán)等待的狀態(tài),因此不需要的時候終止Looper书蚪,調(diào)用Looper.myLooper().quit()喇澡。

以上代碼是否能正確彈出Toast呢?答案是肯定的殊校。

在ViewRootImpl中的checkThread方法會校驗mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構(gòu)造器中晴玖,也就是說一個創(chuàng)建ViewRootImpl線程必須和調(diào)用checkThread所在的線程一致,UI的更新并非只能在主線程才能進行为流。

線程中更新UI的重點是創(chuàng)建它的ViewRootImpl和checkThread所在的線程是否一致呕屎。
系統(tǒng)為什么不建議在子線程中訪問UI?

這是因為 Android 的UI控件不是線程安全的敬察,如果在多線程中并發(fā)訪問可能會導致UI控件處于不可預期的狀態(tài)秀睛。

那么為什么系統(tǒng)不對UI控件的訪問加上鎖機制呢?

缺點有兩個:

1.首先加上鎖機制會讓UI訪問的邏輯變得復雜
2.鎖機制會降低UI訪問的效率莲祸,因為鎖機制會阻塞某些線程的執(zhí)行蹂安。

所以最簡單且高效的方法就是采用單線程模型來處理UI操作。

子線程如何通知主線程更新UI(都是通過Handle發(fā)送消息到主線程操作UI的)
  • 主線程中定義 Handler锐帜,子線程通過 mHandler 發(fā)送消息田盈,主線程 Handler 的 handleMessage 更新UI。
  • 用 Activity 對象的 runOnUiThread 方法缴阎。
  • 創(chuàng)建 Handler允瞧,傳入 getMainLooper。
  • View.post(Runnable r) 蛮拔。
Looper死循環(huán)為什么不會導致應(yīng)用卡死述暂,會耗費大量資源嗎?

應(yīng)用被卡死本質(zhì)上不是阻塞了主線程语泽,而是阻塞了Looper的loop方法。導致loop方法無法處理其他事件视卢,導致出現(xiàn)了ANR事件踱卵。

從前面的主線程、子線程的分析可以看出据过,Looper會在線程中不斷的檢索消息惋砂,如果是子線程的Looper死循環(huán),一旦任務(wù)完成绳锅,用戶應(yīng)該手動退出西饵,而不是讓其一直休眠等待。

線程其實就是一段可執(zhí)行的代碼鳞芙,當可執(zhí)行的代碼執(zhí)行完成后眷柔,線程的生命周期便該終止了期虾,線程退出。而對于主線程驯嘱,我們是絕不希望會被運行一段時間镶苞,自己就退出,那么如何保證能一直存活呢鞠评?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的茂蚓,死循環(huán)便能保證不會被退出,例如剃幌,binder 線程也是采用死循環(huán)的方法聋涨,通過循環(huán)方式不同與 Binder 驅(qū)動進行讀寫操作,當然并非簡單地死循環(huán)负乡,無消息時會休眠牍白。Android是基于消息處理機制的,用戶的行為都在這個Looper循環(huán)中敬鬓,我們在休眠時點擊屏幕淹朋,便喚醒主線程繼續(xù)進行工作。

主線程的死循環(huán)一直運行是不是特別消耗 CPU 資源呢钉答?其實不然础芍,這里就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時数尿,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里仑性,此時主線程會釋放 CPU 資源進入休眠狀態(tài),直到下個消息到達或者有事務(wù)發(fā)生右蹦,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作诊杆。這里采用的 epoll 機制,是一種IO多路復用機制何陆,可以同時監(jiān)控多個描述符晨汹,當某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進行讀或?qū)懖僮鞔ぃ举|(zhì)同步I/O淘这,即讀寫是阻塞的。所以說巩剖,主線程大多數(shù)時候都是處于休眠狀態(tài)铝穷,并不會消耗大量CPU資源。

主線程的Looper何時退出

在App退出時佳魔,ActivityThread中的mH(Handler)收到消息后曙聂,執(zhí)行退出。

//ActivityThread.java  
case EXIT_APPLICATION:  
    if (mInitialApplication != null) {  
        mInitialApplication.onTerminate();  
    }  
    Looper.myLooper().quit();  
    break; 
如何處理Handler使用不當造成的內(nèi)存泄漏鞠鲜?
class HandlerActivity: AppCompatActivity() {
 
    private val mHandler = MyHandler()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在子線程中通過自定義的 Handler 發(fā)消息
        thread {
             mHandler.sendEmptyMessageDelayed(1, 1000)
        }
    }
 
    // 自定義一個 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主線程:handleMessage: ${msg.what}")
        }
    }
}

再發(fā)送延時消息之前宁脊,app推出了断国,那么handleMessage方法還會執(zhí)行嗎?答案是會的朦佩。

MyHandler 是 HandlerActivity 的內(nèi)部類并思,會持有 HandlerActivity 的引用。在進入頁面以后语稠,發(fā)送了一個延時 1s 的消息宋彼,如果 HandlerActivity 在 1s 內(nèi)退出了,由于 Handler 會被 Message 持有仙畦,保存在其 target 變量中输涕,而 Message 又會被保存在消息隊列中,這一系列關(guān)聯(lián)慨畸,導致 HandlerActivity 在退出的時候莱坎,依然會被持有,因此不能被 GC 回收寸士,這就是內(nèi)存泄漏檐什!當這個 1s 延時的消息被執(zhí)行完以后,HandlerActivity 會被回收弱卡。

有延時消息乃正,在界面關(guān)閉后及時移除Message/Runnable,調(diào)用handler.removeCallbacksAndMessages(null)

內(nèi)部類導致的內(nèi)存泄漏改為靜態(tài)內(nèi)部類婶博,并對上下文或者Activity/Fragment使用弱引用瓮具。

正確創(chuàng)建Message實例

1.通過 Message 的靜態(tài)方法 Message.obtain() 獲取凡人;
2.通過 Handler 的公有方法 handler.obtainMessage()

// Message.java
 
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
} 

sPool是消息池名党,obtain會先從消息池中獲取Message對象,避免通過new創(chuàng)建過多的對象挠轴。

參考:
Handler的初級传睹、中級、高級問法岸晦,你都掌握了嗎欧啤?
Handler原理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市委煤,隨后出現(xiàn)的幾起案子堂油,更是在濱河造成了極大的恐慌修档,老刑警劉巖碧绞,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吱窝,居然都是意外死亡讥邻,警方通過查閱死者的電腦和手機迫靖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兴使,“玉大人系宜,你說我怎么就攤上這事》⑵牵” “怎么了盹牧?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長励幼。 經(jīng)常有香客問我汰寓,道長,這世上最難降的妖魔是什么苹粟? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任有滑,我火速辦了婚禮,結(jié)果婚禮上嵌削,老公的妹妹穿的比我還像新娘毛好。我一直安慰自己,他們只是感情好苛秕,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布肌访。 她就那樣靜靜地躺著,像睡著了一般想帅。 火紅的嫁衣襯著肌膚如雪场靴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天港准,我揣著相機與錄音旨剥,去河邊找鬼。 笑死浅缸,一個胖子當著我的面吹牛轨帜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衩椒,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚌父,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毛萌?” 一聲冷哼從身側(cè)響起苟弛,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阁将,沒想到半個月后膏秫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡做盅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年缤削,在試婚紗的時候發(fā)現(xiàn)自己被綠了窘哈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡亭敢,死狀恐怖滚婉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帅刀,我是刑警寧澤让腹,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扣溺,受9級特大地震影響哨鸭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娇妓,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一像鸡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哈恰,春花似錦只估、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荠医,卻和暖如春吁脱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彬向。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工兼贡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娃胆。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓遍希,卻偏偏與公主長得像,于是被迫代替她去往敵國和親里烦。 傳聞我的和親對象是個殘疾皇子凿蒜,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容