帶著問(wèn)題學(xué)習(xí) Android Handler 消息機(jī)制

學(xué)習(xí) Android Handler 消息機(jī)制

一蛉艾、提出問(wèn)題

面試時(shí)常被問(wèn)到的問(wèn)題:

  • 簡(jiǎn)述 Android 消息機(jī)制
  • Android 中 Handler照棋,Looper,MessageQueue泳梆,Message 有什么關(guān)系鳖悠?

這倆問(wèn)題其實(shí)是一個(gè)問(wèn)題,其實(shí)只要搞清楚了 Handler优妙,Looper乘综,MessageQueue,Message 的作用和聯(lián)系套硼,就理解了 Android 的 Handler 消息機(jī)制卡辰。那么再具體一點(diǎn):

  1. 為什么在主線程可以直接使用 Handler?
  2. Looper 對(duì)象是如何綁定 MessageQueue 的邪意?
  3. MessageQueue 里的消息從哪里來(lái)九妈?Handler是如何往MessageQueue中插入消息的?
  4. Message 是如何綁定 Handler 的雾鬼?
  5. Handler 如何綁定 MessageQueue萌朱?
  6. 關(guān)于 handler,在任何地方 new handler 都是什么線程下策菜?
  7. Looper 循環(huán)拿到消息后怎么處理晶疼?

二、解決問(wèn)題

那么又憨,我們從主線程的消息機(jī)制開(kāi)始分析:

2.1 主線程 Looper 的創(chuàng)建和循環(huán)

Android 應(yīng)用程序的入口是 main 函數(shù)翠霍,主線程 Looper 的創(chuàng)建也是在這里完成的。

ActivityThread --> main() 函數(shù)

public static void main(){
        // step1: 創(chuàng)建主線程Looper對(duì)象
        Looper.prepareMainLooper();
        
        ActivityThread thread = new ActivityThread();
        // 綁定應(yīng)用進(jìn)程蠢莺,布爾標(biāo)記是否為系統(tǒng)進(jìn)程
        thread.attach(false);
        // 實(shí)例化主線程 Handler
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 開(kāi)始循環(huán)
        Loop.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper()用來(lái)創(chuàng)建主線程的 Looper 對(duì)象壶运,接下來(lái)先看這個(gè)方法的實(shí)現(xiàn)。

2.1.1 創(chuàng)建主線程 Looper

Looper --> prepareMainLooper()

private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper(){
        // step1: 調(diào)用本類(lèi) prepare 方法
        prepare(false);
        // 線程同步浪秘,如果變量 sMainLooper 不為空拋出主線程 Looper 已經(jīng)創(chuàng)建
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // step2: 調(diào)用本類(lèi) myLooper 方法
            sMainLooper = myLooper();
        }
}

prepareMainLooper() 方法主要是使用 prepare(false) 創(chuàng)建當(dāng)前線程的 Looper 對(duì)象蒋情,再使用 myLooper() 方法來(lái)獲取當(dāng)前線程的 Looper 對(duì)象。

step1: Looper --> prepare()

// ThreadLocal 為每個(gè)線程保存單獨(dú)的變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// Looper 類(lèi)的 MessageQueue 變量
final MessageQueue mQueue;
// quitAllowed 是否允許退出耸携,這里是主線程的 Looper 不可退出
private static void prepare(boolean quitAllowed) {
        // 首先判定 Looper 是否存在
        if(sThreadLocal.get() != null){
                throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 保存線程的副本變量
        sThreadLoacal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • prepare() 方法中用 ThreadLocal 來(lái)保存主線程的 Looper 對(duì)象棵癣。ThreadLocal 可以看作是一個(gè)用來(lái)儲(chǔ)存數(shù)據(jù)的類(lèi),類(lèi)似 HashMap夺衍、ArrayList等集合類(lèi)狈谊,它存放著屬于當(dāng)前線程的變量。

  • ThreadLocal 提供了 get/set 方法分別用來(lái)獲取和保存變量。
    比如在主線程通過(guò) prepare() 方法來(lái)創(chuàng)建 Looper 對(duì)象河劝,并使用 sThreadLoacal.set(new Looper(quitAllowed)) 來(lái)保存主線程的 Looper 對(duì)象壁榕,那么在主線程調(diào)用 myLooper()(實(shí)際調(diào)用了 sThreadLocal.get() 方法) 就是通過(guò) ThreadLocal 來(lái)獲取主線程的 Looper 對(duì)象。如果在子線程調(diào)用這些方法就是通過(guò) ThreadLocal 保存和獲取屬于子線程的 Looper 對(duì)象赎瞎。

更多關(guān)于 ThreadLocal 的原理:

深入剖析ThreadLocal實(shí)現(xiàn)原理以及內(nèi)存泄漏問(wèn)題

問(wèn)題1:為什么在主線程可以直接使用 Handler牌里?
因?yàn)橹骶€程已經(jīng)創(chuàng)建了 Looper 對(duì)象并開(kāi)啟了消息循環(huán),通過(guò)上文的代碼就可以看出來(lái)务甥。

問(wèn)題2:Looper 對(duì)象是如何綁定 MessageQueue 的牡辽?或者說(shuō) Looper 對(duì)象創(chuàng)建 MessageQueue 過(guò)程。
很簡(jiǎn)單敞临,Looper 有個(gè)一成員變量 mQueue态辛,它就是 Looper 對(duì)象默認(rèn)保存的 MessageQueue。上面代碼中 Looper 有一個(gè)構(gòu)造器挺尿,新建 Looper 對(duì)象時(shí)會(huì)直接創(chuàng)建 MessageQueue 并賦值給 mQueue奏黑。
問(wèn)題2解決:在 new Looper 時(shí)就創(chuàng)建了 MessageQueue 對(duì)象并賦值給 Looper 的成員變量 mQueue。

step2: Looper --> myLooper()

// 也就是使用本類(lèi)的ThreadLocal對(duì)象獲取之前創(chuàng)建保存的Looper對(duì)象
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

這個(gè)方法就是通過(guò) sThreadLocal 變量獲取當(dāng)前線程的 Looper 對(duì)象编矾,比較常用的一個(gè)方法攀涵。上文主線程 Looper 對(duì)象創(chuàng)建后使用該方法獲取了 Looper 對(duì)象。

2.1.2 開(kāi)始循環(huán)處理消息

回到最開(kāi)始的 main() 函數(shù)洽沟,在創(chuàng)建了 Looper 對(duì)象以后就調(diào)用了 Looper.loop() 來(lái)循環(huán)處理消息,貼一下大致代碼:

public static void main(){
        // step1: 創(chuàng)建主線程Looper對(duì)象
        Looper.prepareMainLooper();
        ...
        // step2: 開(kāi)始循環(huán)
        Loop.loop();
}

Looper --> loop()

public static void loop() {
    // step1: 獲取當(dāng)前線程的 Looper 對(duì)象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // step2: 獲取 Looper 保存的 MessageQueue 對(duì)象
    final MessageQueue queue = me.mQueue;

    ...
    // step3: 循環(huán)讀取消息蜗细,如果有則調(diào)用消息對(duì)象中儲(chǔ)存的 handler 進(jìn)行發(fā)送
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        try {
            // step4: 使用 Message 對(duì)象保存的 handler 對(duì)象處理消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}
  • step1 : myLooper() 方法就是通過(guò) ThreadLocal 獲取當(dāng)前線程的 Looper 對(duì)象裆操,注意在哪個(gè)線程使用該方法就獲取的該線程的 Looper 對(duì)象。
  • step2 :me.mQueue炉媒,這個(gè) mQueue 就是上面問(wèn)題2所說(shuō)的在 Looper 對(duì)象創(chuàng)建時(shí)新建的 MessageQueue 變量踪区。
  • step3 :接下來(lái)是一個(gè) for 循環(huán)些己,首先通過(guò) queue.next() 來(lái)提取下一條消息晨逝,具體是怎么提取的可以參考下面文章的 4.2 節(jié):

Android消息機(jī)制1-Handler(Java層)

獲取到下一條消息,如果 MessageQueue 中沒(méi)有消息莺褒,就會(huì)進(jìn)行阻塞白粉。那么如果存在消息传泊,它又是怎么放入 MessageQueue 的呢?或者說(shuō)MessageQueue 里的消息從哪里來(lái)鸭巴?Handler是如何往MessageQueue中插入消息的眷细?先不說(shuō)這個(gè),把這個(gè)問(wèn)題叫作問(wèn)題3后面分析鹃祖。

  • step4 :msg.target.dispatchMessage(msg);這個(gè)方法最終會(huì)調(diào)用 Handler 的 handleMessage(msg) 方法溪椎。同時(shí)這里又產(chǎn)生個(gè)問(wèn)題:msg.target 是何時(shí)被賦值的?,也就是說(shuō)Message 是如何綁定 Handler 的校读?先稱(chēng)之為問(wèn)題4沼侣。那么接著看 Handler 的 dispatchMessage 方法:

Handler --> dispatchMessage

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

public void handleMessage(Message msg) {
}

可以看到該方法最后執(zhí)行了 handleMessage() 方法,這是一個(gè)空方法也就是需要我們覆寫(xiě)并實(shí)現(xiàn)的歉秫。另外 dispatchMessage() 也體現(xiàn)出一個(gè)問(wèn)題:

消息分發(fā)的優(yōu)先級(jí):

  • Message 的回調(diào)方法:message.callback.run(); 優(yōu)先級(jí)最高蛾洛;
  • Handler 的回調(diào)方法:mCallback.handleMessage(msg)優(yōu)先級(jí)次于上方;
  • Handler 的回調(diào)方法:handleMessage() 優(yōu)先級(jí)最低端考。

到這里 Looper 循環(huán)并通過(guò) Handler 發(fā)送消息有一個(gè)整體的流程了雅潭,接下來(lái)分析 Handler 在消息機(jī)制中的主要作用以及和 Looper、Message 的關(guān)系却特。

2.2 Handler 的創(chuàng)建和作用

上面說(shuō)到 loop() 方法在不斷從消息隊(duì)列 MessageQueue 中取出消息(queue.next() 方法)扶供,如果沒(méi)有消息則阻塞,反之交給 Message 綁定的 Handler 處理裂明〈慌ǎ回顧一下沒(méi)解決的兩個(gè)問(wèn)題:

  • 問(wèn)題3:MessageQueue 里的消息從哪里來(lái)?Handler 是如何往 MessageQueue 中插入消息的闽晦?
  • 問(wèn)題4:msg.target 是何時(shí)被賦值的扳碍?,也就是說(shuō)Message 是如何綁定 Handler 的仙蛉?

既然要解決 Handler 插入消息的問(wèn)題笋敞,就要看 Handler 發(fā)送消息的過(guò)程。

2.2.1 Handler 發(fā)送消息

Handler --> sendMessage(Message msg);

final MessageQueue mQueue;

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
// 發(fā)送延時(shí)消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 指定時(shí)間發(fā)送消息
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);
}
// 處理消息荠瘪,賦值 Message 對(duì)象的 target夯巷,消息隊(duì)列插入消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到調(diào)用 sendMessage(Message msg) 方法最終會(huì)調(diào)用到 enqueueMessage() 方法,這個(gè)方法主要有兩個(gè)作用:賦值 Message 對(duì)象的 target哀墓、消息隊(duì)列插入消息趁餐。

  • 賦值 msg 的 target:msg.target = this 把發(fā)送消息的 Handler 賦值給 msg 對(duì)象的 target。那么問(wèn)題 4 就解決了:Handler 執(zhí)行發(fā)送消息的過(guò)程中將自己綁定給了 Message 的 target篮绰,這樣兩者之間就產(chǎn)生了聯(lián)系后雷;
  • 消息隊(duì)列插入消息:queue.enqueueMessage(msg, uptimeMillis) queue 是 MessageQueue 的一個(gè)實(shí)例,queue.enqueueMessage(msg, uptimeMillis)是執(zhí)行 MessageQueue 的enqueueMessage方法來(lái)插入消息吠各。這樣問(wèn)題 3 就找到答案:Handler 在發(fā)送消息的時(shí)候執(zhí)行 MessageQueue 的enqueueMessage方法來(lái)插入消息臀突;關(guān)于 MessageQueue 是怎么執(zhí)行插入消息的過(guò)程,參考下方文章 4.3 節(jié)

Android消息機(jī)制1-Handler(Java層)

  • 上面 Handler 發(fā)送消息使用了 MessageQueue 的實(shí)例 queue贾漏,可以看到這個(gè) queue 是上一個(gè)方法 sendMessageAtTime 中由 Handler 的成員變量 mQueue 賦值的惧辈,那么 mQueue 是哪來(lái)的?問(wèn)題 5:Handler 如何綁定 MessageQueue磕瓷?先劇透一下 Handler 綁定的是 Looper 的 MessageQueue 對(duì)象盒齿,Looper 的 MessageQueue 對(duì)象是在 Looper 創(chuàng)建時(shí)就 new 的念逞。

要了解 Handler 的 MessageQueue 對(duì)象是怎么賦值的就要看 Handler 的構(gòu)造函數(shù)了,Handler 創(chuàng)建的時(shí)候作了一些列操作比如獲取當(dāng)前線程的 Looper边翁,綁定 MessageQueue 對(duì)象等翎承。

2.2.2 Handler 的創(chuàng)建

下面是 Handler 無(wú)參構(gòu)造器和主要的構(gòu)造器,另外幾個(gè)重載的構(gòu)造器有些是通過(guò)傳遞不同參數(shù)調(diào)用包含兩個(gè)參數(shù)的構(gòu)造器符匾。兩個(gè)參數(shù)構(gòu)造函數(shù)第一個(gè)參數(shù)為 callback 回調(diào)叨咖,第二個(gè)函數(shù)用來(lái)標(biāo)記消息是否異步。

// 無(wú)參構(gòu)造器
public Handler() {
     this(null, false);
}

public Handler(Callback callback, boolean async) {
    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());
        }
    }
    // step1:獲取當(dāng)前線程 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // step2:獲取 Looper 對(duì)象綁定的 MessageQueue 對(duì)象并賦值給 Handler 的 mQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
  • step1:調(diào)用myLooper() 方法啊胶,該方法是使用 sThreadLocal 對(duì)象獲取當(dāng)前線程的 Looper 對(duì)象甸各,回顧一下:
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

如果獲取的 Looper 對(duì)象為 null,說(shuō)明沒(méi)有執(zhí)行 Looper.prepare() 為當(dāng)前線程保存 Looper 變量焰坪,就會(huì)拋出 RuntimeException趣倾。這里又說(shuō)明了Handler 必須在有 Looper 的線程中使用,報(bào)錯(cuò)不說(shuō)某饰,沒(méi)有 Looper 就無(wú)法綁定 MessageQueue 對(duì)象也就無(wú)法進(jìn)行更多有關(guān)消息的操作儒恋。

  • step2:mQueue = mLooper.mQueue 說(shuō)明了 Handler 的 MessageQueue 對(duì)象是由當(dāng)前線程 Looper 的 MessageQueue 對(duì)象賦值的。這里問(wèn)題 5 解決:Handler 在創(chuàng)建時(shí)綁定了當(dāng)前線程 Looper 的 MessageQueue 對(duì)象黔漂。
  • 由于 Handler 和 Looper 可以看作使用的是同一個(gè) MessageQueue 對(duì)象诫尽,所以 Handler 和 Looper 可以共享消息隊(duì)列 MessageQueue。Handler 發(fā)送消息(用 mQueue 往消息對(duì)列插入消息)炬守,Looper 可以方便的循環(huán)使用 mQueue 查詢(xún)消息牧嫉,如果查詢(xún)到消息,就可以用 Message 對(duì)象綁定的 Handler 對(duì)象 target 去處理消息减途,反之則阻塞酣藻。

既然說(shuō)到了 Handler 的構(gòu)造器,就想到一個(gè)問(wèn)題:問(wèn)題 6:關(guān)于 handler观蜗,在任何地方 new handler 都是什么線程下?這個(gè)問(wèn)題要分是否傳遞 Looper 對(duì)象來(lái)看衣洁。

  1. 不傳遞 Looper 創(chuàng)建 Handler:Handler handler = new Handler();上文就是 Handler 無(wú)參創(chuàng)建的源碼墓捻,可以看到是通過(guò) Looper.myLooper() 來(lái)獲取 Looper 對(duì)象,也就是說(shuō)對(duì)于不傳遞 Looper 對(duì)象的情況下坊夫,在哪個(gè)線程創(chuàng)建 Handler 默認(rèn)獲取的就是該線程的 Looper 對(duì)象砖第,那么 Handler 的一系列操作都是在該線程進(jìn)行的。
  2. 傳遞 Looper 對(duì)象創(chuàng)建 Handler:Handler handler = new Handler(looper);那么看看傳入 Looper 的構(gòu)造函數(shù):
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
// 第一個(gè)參數(shù)是 looper 對(duì)象环凿,第二個(gè) callback 對(duì)象梧兼,第三個(gè)消息處理方式(是否異步)
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看出來(lái)傳遞 Looper 對(duì)象 Handler 就直接使用了。所以對(duì)于傳遞 Looper 對(duì)象創(chuàng)建 Handler 的情況下智听,傳遞的 Looper 是哪個(gè)線程的羽杰,Handler 綁定的就是該線程渡紫。

到這里 Looper 和 Handler 就有一個(gè)大概的流程了,接下來(lái)看一個(gè)簡(jiǎn)單的子線程 Handler 使用例子:

new Thread() {
    @Override
    public void run() {
        // step1
        Looper.prepare();
         // step2
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == 1){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
                        }
                    });
                     // step5
                    Looper.myLooper().quit();
                }
            }
        };
         // step3
        handler.sendEmptyMessage(1);
         // step4
        Looper.loop();
    }
}.start();
  • step1: 調(diào)用 Looper.prepare(); 為當(dāng)前線程創(chuàng)建 Looper 對(duì)象考赛,同時(shí)也就創(chuàng)建了 MessageQueue惕澎,之后將該線程的 Looper 對(duì)象保存在 ThreadLocal 中。注意這里的一切操作都在子線程中颜骤,如果不調(diào)用 Looper.prepare() 就使用 Handler 會(huì)報(bào)錯(cuò)唧喉。
  • step2: 創(chuàng)建 Handler 對(duì)象,覆寫(xiě) handleMessage 處理消息忍抽,等待該 Handler 發(fā)送的消息處理時(shí)會(huì)調(diào)用該方法八孝。
  • step3: 使用 handler 發(fā)送消息,這里只是示例鸠项,畢竟自己給自己發(fā)送消息沒(méi)啥必要干跛。發(fā)送的過(guò)程中會(huì)將自己賦值給 msg.target,然后再將消息插入到 Looper 綁定的 MessageQueue 對(duì)象中锈锤。
  • step4: 調(diào)用 Looper.loop(); 首先獲取當(dāng)前線程的 Looper 對(duì)象驯鳖,根據(jù) Looper 對(duì)象就可以拿到 Looper 保存的 MessageQueue 對(duì)象 mQueue。有了 MessageQueue 對(duì)象就可以 for 循環(huán)獲取它保存的消息 Message 對(duì)象久免,如果消息不存在就返回 null 阻塞浅辙,反之則使用 Message 中保存的 Handler:msg.target 來(lái)處理消息,最終調(diào)用 handleMessage 也就是之前覆寫(xiě)的方法來(lái)處理消息阎姥。
  • step5: 邏輯處理完畢以后记舆,應(yīng)在最后使用 quit 方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待的狀態(tài)呼巴,而如果退出Looper以后泽腮,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper衣赶。

三诊赊、總結(jié)和其它

3.1 Handler、Looper府瞄、MessageQueue碧磅、Message

  1. Handler 用來(lái)發(fā)送消息,創(chuàng)建時(shí)先獲取默認(rèn)或傳遞來(lái)的 Looper 對(duì)象遵馆,并持有 Looper 對(duì)象包含的 MessageQueue鲸郊,發(fā)送消息時(shí)使用該 MessageQueue 對(duì)象來(lái)插入消息并把自己封裝到具體的 Message 中;
  2. Looper 用來(lái)為某個(gè)線程作消息循環(huán)货邓。Looper 持有一個(gè) MessageQueue 對(duì)象 mQueue秆撮,這樣就可以通過(guò)循環(huán)來(lái)獲取 MessageQueue 所維護(hù)的 Message。如果獲取的 MessageQueue 沒(méi)有消息時(shí)换况,便阻塞在 loop 的queue.next() 中的 nativePollOnce() 方法里职辨,反之則喚醒主線程繼續(xù)工作盗蟆,之后便使用 Message 封裝的 handler 對(duì)象進(jìn)行處理。
  3. MessageQueue 是一個(gè)消息隊(duì)列拨匆,它不直接添加消息姆涩,而是通過(guò)與 Looper 關(guān)聯(lián)的 Handler 對(duì)象來(lái)添加消息。
  4. Message 包含了要傳遞的數(shù)據(jù)和信息惭每。

3.2 Android中為什么主線程不會(huì)因?yàn)長(zhǎng)ooper.loop()里的死循環(huán)卡死骨饿?

這是知乎上的問(wèn)題,感覺(jué)問(wèn)的挺有意思台腥。平時(shí)可能不太會(huì)太深究這些問(wèn)題宏赘,正好有大神回答那就記錄一下吧。

  1. 為什么不會(huì)因?yàn)樗姥h(huán)卡死黎侈?
    線程可以看作是一段可執(zhí)行代碼察署,當(dāng)代碼執(zhí)行完畢線程的生命周期就該終止了。對(duì)于主線程來(lái)說(shuō)我們不希望它執(zhí)行一段時(shí)間后退出峻汉,所以簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的贴汪,死循環(huán)便能保證不會(huì)被退出。既然是死循環(huán)那么怎么去處理消息呢休吠,通過(guò)創(chuàng)建新線程的方式扳埂。
  2. 為這個(gè)死循環(huán)準(zhǔn)備了一個(gè)新線程
    在進(jìn)入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:
public static void main(){
        ...
        Looper.prepareMainLooper();

        //創(chuàng)建ActivityThread對(duì)象
        ActivityThread thread = new ActivityThread();

        //建立Binder通道 (創(chuàng)建新線程)
        thread.attach(false);

        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 開(kāi)始循環(huán)
        Loop.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

thread.attach(false)瘤礁;便會(huì)創(chuàng)建一個(gè)Binder線程(具體是指ApplicationThread阳懂,Binder的服務(wù)端,用于接收系統(tǒng)服務(wù)AMS發(fā)送來(lái)的事件)柜思,該Binder線程通過(guò)Handler將Message發(fā)送給主線程岩调。

  1. 主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢?

其實(shí)不然赡盘,這里就涉及到Linux pipe/epoll機(jī)制号枕,簡(jiǎn)單說(shuō)就是在主線程的MessageQueue沒(méi)有消息時(shí),便阻塞在loop的queue.next()中的nativePollOnce()方法里陨享,詳情見(jiàn)Android消息機(jī)制1-Handler(Java層)葱淳,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生霉咨,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作蛙紫。這里采用的epoll機(jī)制拍屑,是一種IO多路復(fù)用機(jī)制途戒,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w)僵驰,則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮髋缯举|(zhì)同步I/O唁毒,即讀寫(xiě)是阻塞的。所以說(shuō)星爪,主線程大多數(shù)時(shí)候都是處于休眠狀態(tài)浆西,并不會(huì)消耗大量CPU資。

  1. Activity的生命周期是怎么實(shí)現(xiàn)在死循環(huán)體外能夠執(zhí)行起來(lái)的顽腾?
    上文 main 函數(shù)有一部分獲取 sMainThreadHandler 的代碼:
final H mH = new H();

public static void main(){
        ...
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        ...
}

final Handler getHandler() {
    return mH;
}

類(lèi) H 繼承了 Handler近零,在主線程創(chuàng)建時(shí)就創(chuàng)建了這個(gè) Handler 用于處理 Binder 線程發(fā)送來(lái)的消息。

Activity的生命周期都是依靠主線程的Looper.loop抄肖,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施:

在H.handleMessage(msg)方法中久信,根據(jù)接收到不同的msg,執(zhí)行相應(yīng)的生命周期漓摩。
比如收到msg=H.LAUNCH_ACTIVITY裙士,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會(huì)通過(guò)反射機(jī)制管毙,創(chuàng)建Activity實(shí)例腿椎,然后再執(zhí)行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY夭咬,則調(diào)用ActivityThread.handlePauseActivity()方法啃炸,最終會(huì)執(zhí)行Activity.onPause()等方法。 上述過(guò)程皱埠,我只挑核心邏輯講肮帐,真正該過(guò)程遠(yuǎn)比這復(fù)雜。

3.3 Handler 使用造成內(nèi)存泄露

  1. 有延時(shí)消息边器,要在Activity銷(xiāo)毀的時(shí)候移除Messages
  2. 匿名內(nèi)部類(lèi)導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類(lèi)训枢,并且對(duì)上下文或者Activity使用弱引用。

具體操作可以參考文章:

Handler內(nèi)存泄露原理及解決方法

參考資料:

《Android 開(kāi)發(fā)藝術(shù)探索》
Android消息機(jī)制1-Handler(Java層)
Android Handler消息機(jī)制實(shí)現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忘巧,一起剝皮案震驚了整個(gè)濱河市恒界,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砚嘴,老刑警劉巖十酣,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異际长,居然都是意外死亡耸采,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)工育,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)虾宇,“玉大人,你說(shuō)我怎么就攤上這事如绸≈鲂啵” “怎么了旭贬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)搪泳。 經(jīng)常有香客問(wèn)我稀轨,道長(zhǎng),這世上最難降的妖魔是什么岸军? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任奋刽,我火速辦了婚禮,結(jié)果婚禮上艰赞,老公的妹妹穿的比我還像新娘杨名。我一直安慰自己,他們只是感情好猖毫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布台谍。 她就那樣靜靜地躺著,像睡著了一般吁断。 火紅的嫁衣襯著肌膚如雪趁蕊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天仔役,我揣著相機(jī)與錄音掷伙,去河邊找鬼。 笑死又兵,一個(gè)胖子當(dāng)著我的面吹牛任柜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沛厨,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宙地,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了逆皮?” 一聲冷哼從身側(cè)響起宅粥,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎电谣,沒(méi)想到半個(gè)月后秽梅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剿牺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年企垦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晒来。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞诡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭增,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布竹习,位于F島的核電站誊抛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏整陌。R本人自食惡果不足惜拗窃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泌辫。 院中可真熱鬧随夸,春花似錦、人聲如沸震放。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)殿遂。三九已至诈铛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墨礁,已是汗流浹背幢竹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恩静,地道東北人焕毫。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驶乾,于是被迫代替她去往敵國(guó)和親邑飒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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