初識 Thread/Looper/MessageQueue/Handler

學(xué)習(xí) Android 中定時器實現(xiàn)方式時候猪勇,發(fā)現(xiàn)有下面這些方式踱承。

實現(xiàn)方式 優(yōu)點 缺點 使用場景 所用的API
普通線程sleep的方式 簡單易用排作,可用于一般的輪詢Polling 不精確乡革,不可靠寇僧,容易被系統(tǒng)殺死或者休眠 需要在App內(nèi)部執(zhí)行短時間的定時任務(wù) Thread.sleep(long)
Timer定時器 簡單易用摊腋,可以設(shè)置固定周期或者延遲執(zhí)行的任務(wù) 不精確,不可靠嘁傀,容易被系統(tǒng)殺死或者休眠 需要在App內(nèi)部執(zhí)行短時間的定時任務(wù) Timer.schedule(TimerTask,long)
ScheduledExecutorService 靈活強大兴蒸,可以設(shè)置固定周期或者延遲執(zhí)行的任務(wù),并支持多線程并發(fā) 不精確细办,不可靠橙凳,容易被系統(tǒng)殺死或者休眠 需要在App內(nèi)部執(zhí)行短時間且需要多線程并發(fā)的定時任務(wù) Executors.newScheduledThreadPool(int).schedule(Runnable,long,TimeUnit)
Handler中的postDelayed方法 簡單易用,可以設(shè)置延遲執(zhí)行的任務(wù)笑撞,并與UI線程交互 不精確岛啸,不可靠,容易被系統(tǒng)殺死或者休眠 需要在App內(nèi)部執(zhí)行短時間且需要與UI線程交互的定時任務(wù) Handler.postDelayed(Runnable,long)
Service + AlarmManger + BroadcastReceiver 可靠穩(wěn)定茴肥,可以設(shè)置精確或者不精確的鬧鐘值戳,并在后臺長期運行 需要聲明相關(guān)權(quán)限,并受系統(tǒng)時間影響 需要在App外部執(zhí)行長期且對時間敏感的定時任務(wù) AlarmManager.set(int,PendingIntent), BroadcastReceiver.onReceive(Context,Intent), Service.onStartCommand(Intent,int,int)
WorkManager 可靠穩(wěn)定炉爆,不受系統(tǒng)時間影響,并可以設(shè)置多種約束條件來執(zhí)行任務(wù) 需要添加依賴卧晓,并不能保證準(zhǔn)時執(zhí)行 需要在App外部執(zhí)行長期且對時間不敏感且需要滿足特定條件才能執(zhí)行的定時任務(wù) WorkManager.enqueue(WorkRequest), Worker.doWork()
RxJava 簡潔芬首、靈活、支持多線程逼裆、支持背壓郁稍、支持鏈?zhǔn)讲僮?/td> 學(xué)習(xí)曲線較高、內(nèi)存占用較大 需要處理復(fù)雜的異步邏輯或數(shù)據(jù)流 io.reactivex:rxjava:2.2.21
CountDownTimer 簡單易用胜宇、不需要額外的線程或handler 不支持取消或重置倒計時耀怜、精度受系統(tǒng)時間影響 需要實現(xiàn)簡單的倒計時功能 android.os.CountDownTimer
協(xié)程+Flow 語法簡潔、支持協(xié)程作用域管理生命周期桐愉、支持流式操作和背壓 需要引入額外的依賴庫财破、需要熟悉協(xié)程和Flow的概念和用法 需要處理異步數(shù)據(jù)流或響應(yīng)式編程 kotlinx-coroutines-core:1.5.0
使用downTo關(guān)鍵字和Flow實現(xiàn)一個定時任務(wù) 1、可以使用簡潔的語法創(chuàng)建一個倒數(shù)的范圍 2 从诲、可以使用Flow異步地發(fā)射和收集倒數(shù)的值3左痢、可以使用onEach等操作符對倒數(shù)的值進行處理或轉(zhuǎn)換 1、需要注意倒數(shù)的范圍是否包含0系洛,否則可能會出現(xiàn)偏差 2俊性、需要注意倒數(shù)的間隔是否與delay函數(shù)的參數(shù)一致,否則可能會出現(xiàn)不準(zhǔn)確 3描扯、需要注意取消或停止Flow的時機定页,否則可能會出現(xiàn)內(nèi)存泄漏或資源浪費 1、適合于需要實現(xiàn)簡單的倒計時功能绽诚,例如顯示剩余時間或進度 2典徊、適合于需要在倒計時過程中執(zhí)行一些額外的操作杭煎,例如播放聲音或更新UI 3、適合于需要在倒計時結(jié)束后執(zhí)行一些額外的操作宫峦,例如跳轉(zhuǎn)頁面或彈出對話框 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
Kotlin 內(nèi)聯(lián)函數(shù)的協(xié)程和 Flow 實現(xiàn) 很容易離開主線程岔帽,樣板代碼最少,協(xié)程完全活用了 Kotlin 語言的能力导绷,包括 suspend 方法犀勒。可以處理大量的異步數(shù)據(jù)妥曲,而不會阻塞主線程贾费。 可能會導(dǎo)致內(nèi)存泄漏和性能問題。 處理 I/O 阻塞型操作檐盟,而不是計算密集型操作褂萧。 kotlinx.coroutines 和 kotlinx.coroutines.flow

在學(xué)習(xí)其中兩種實現(xiàn)方式時候遇到多線程問題。

CountDownTimer

  1. 主線程中使用

本地測試代碼如下:

private CountDownTimer countDownTimer;

private void initCountDownTimer() {
    System.out.println("construct and start countDownTimer in thread: " + Thread.currentThread().getName();

    countDownTimer = new CountDownTimer(3000, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            System.out.println("count down onTick: " + Thread.currentThread().getName());
        }

        @Override
        public void onFinish() {
            System.out.println("count down onFinish: " + Thread.currentThread().getName());
        }
    };

    countDownTimer.start();
}

public static void main(String[] args) {
    initCountDownTimer();
}

日志如下:

construct and start countDownTimer in thread: main
count down onTick: main
count down onTick: main
count down onFinish: main

跟自己預(yù)期的一樣葵萎,主線程中構(gòu)造并調(diào)用start导犹,能滿足回調(diào)方法也是在主線程中執(zhí)行相應(yīng)的回調(diào)方法。

  1. 后臺線程中使用
    查了下 Thread 類的簡單說明羡忘,構(gòu)造時候傳入一個 Runnable 對象谎痢,再調(diào)用 start 方法,其中就會從系統(tǒng)申請創(chuàng)建一個線程并執(zhí)行 runnable 對象中的 run 方法卷雕。
    于是修改本地測試代碼如下(由于只想在新建的線程中一次性構(gòu)造 CountDownTimer 對象并調(diào)用 start 即可节猿,于是不持有 thread 對象,讓其執(zhí)行完成則自己銷毀):
public static void main(String[] args) {
    //initCountDownTimer();
    new Thread(new Runnable() {
        @Override
        public void run() {
            initCountDownTimer();
        }
    }).start();
}

日志如下:

construct and start countDownTimer in thread: Thread-121

發(fā)現(xiàn)并沒有出發(fā)定時器的回調(diào)方法(自己隱約也有這種感覺漫雕,因為這個臨時線程只是完成上面的構(gòu)造和調(diào)用 start 則銷毀滨嘱,定時器的實現(xiàn)邏輯如果不在主線程就不可能會回調(diào))

于是看了下 CountDownTimer 的實現(xiàn),驗證下自己的想法浸间。發(fā)現(xiàn)其中有個私有的成員變量 mHandler太雨,在其中實現(xiàn)了定時器的處理回調(diào)邏輯(主要就是一旦收到消息,就判斷時間決定是 onFinish() 還是 onTick()魁蒜,并計算下一次應(yīng)該 onTick() / onFinish() 回調(diào)的時間點躺彬,發(fā)送一個 delayMessage 給 handler)。

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        ...
        onTick();
        ...
        onFinish();
        ...
    }
}

CountDownTimer 的 start 方法實現(xiàn)如下:

public synchronized final CountDownTimer start() {
    mCancelled = false;
    if (mMillisInFuture <= 0) {
        onFinish();
        return this;
    }
    mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
    mHandler.sendMessage(mHandler.obtainMessage(MSG));
    return this;
}

可以看到這要是立即向 mHandler 發(fā)送消息梅惯,讓其開始第一次回調(diào)判斷處理宪拥。
于是產(chǎn)生了一個疑問,這個 handler 的 handleMessage 代碼到底會在那個線程中執(zhí)行呢铣减?
閱讀 Handler 源碼中構(gòu)造函數(shù)發(fā)現(xiàn)她君,其中通過 Looper.myLooper() 獲取并持有為 mLooper,并通過該 looper 對象獲取并持有隊列 mQueue:

public Handler(@Nullable 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());
        }
    }

    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;
    mCallback = callback;
    mAsynchronous = async;
    mIsShared = false;
}

于是我們需要繼續(xù)查找
handler.sendMessage()葫哗、handler.sendMessageDelayed()缔刹、handler.post(runnable)球涛、handler.postDelayed(runnable) 這些方法時候,這些任務(wù)會在那個線程執(zhí)行校镐,跟上述的 mLooper 對象和 mQueue 又有什么關(guān)系呢亿扁?

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) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

通過 Handler 的源碼發(fā)現(xiàn)上述調(diào)用最終都是往 mQueue 中加入一個 msg 消息(Message 有個全局的復(fù)用對象池,使用雙向鏈表)

msg.target = handler
msg.callback = r;  // 如果是 post(runnable) 相關(guān)方法鸟廓,callback 即為該 runnable 對象从祝,否則為 NULL
msg.what = 用戶傳入;
msg.args1 = 用戶傳入引谜;
msg.args2 = 用戶傳入牍陌;
msg.obj = 用戶傳入;

handler 中的 handleMessage() 方法會在 dispatchMessage() 中被調(diào)用:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

接著查找是誰調(diào)用了 handler.dispatchMessage() 方法员咽,就能知道為什么上述的 CountDownTimer 為什么不能按照設(shè)想的在后臺線程正扯窘В回調(diào),怎樣才能讓其正潮词遥回調(diào)契讲。通過搜索發(fā)現(xiàn) Looper 中的關(guān)鍵源碼如下:

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

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("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();
    final long ident = Binder.clearCallingIdentity();

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

    me.mSlowDeliveryDetected = false;

    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }

    // This must be in a local variable, in case a UI event sets the logger
    final Printer logging = me.mLogging;
    if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " "
                + msg.callback + ": " + msg.what);
    }
    // Make sure the observer won't change while processing a transaction.
    final Observer observer = sObserver;

    final long traceTag = me.mTraceTag;
    long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;

    final boolean hasOverride = thresholdOverride >= 0;
    if (hasOverride) {
        slowDispatchThresholdMs = thresholdOverride;
        slowDeliveryThresholdMs = thresholdOverride;
    }
    final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
            && (msg.when > 0);
    final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);

    final boolean needStartTime = logSlowDelivery || logSlowDispatch;
    final boolean needEndTime = logSlowDispatch;

    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
        Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    }

    final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
    final long dispatchEnd;
    Object token = null;
    if (observer != null) {
        token = observer.messageDispatchStarting();
    }
    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    try {
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    if (logSlowDelivery) {
        if (me.mSlowDeliveryDetected) {
            if ((dispatchStart - msg.when) <= 10) {
                Slog.w(TAG, "Drained");
                me.mSlowDeliveryDetected = false;
            }
        } else {
            if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                    msg)) {
                // Once we write a slow delivery log, suppress until the queue drains.
                me.mSlowDeliveryDetected = true;
            }
        }
    }
    if (logSlowDispatch) {
        showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    }

    if (logging != null) {
        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

    // Make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    final long newIdent = Binder.clearCallingIdentity();
    if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
                + Long.toHexString(ident) + " to 0x"
                + Long.toHexString(newIdent) + " while dispatching to "
                + msg.target.getClass().getName() + " "
                + msg.callback + " what=" + msg.what);
    }

    msg.recycleUnchecked();

    return true;
}

發(fā)現(xiàn)其中的 loop() 方法中會一直輪詢自己的 mQueue 的消息,然后調(diào)用 msg.target.dispatchMessage() 即為上述的 handler.dispatchMessage()滑频。
但是需要調(diào)用 Looper.prepare() 方法才會為當(dāng)前線程創(chuàng)建一個 looper 對象(構(gòu)造時創(chuàng)建唯一的一個 mQueue 對象)并寫入到當(dāng)前線程的線程變量中怀泊,key 為:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

經(jīng)過上面的代碼閱讀,CountDownTimer 的 handler 中 handleMessage 方法會在創(chuàng)建 Handler 時候误趴,其中獲取到的當(dāng)前線程的 looper 中執(zhí)行,往該 handler 發(fā)送的消息也都是放到了 looper 對應(yīng)的 mQueue 中务傲,該線程在 looper 輪詢到消息后通過調(diào)用 handler.dispatchMessage() 分發(fā)給消息對應(yīng)的 handler凉当。

所以上面放在后臺線程構(gòu)造和調(diào)用 start 為什么不能正常定時器回調(diào)的原因,就是這個臨時后臺線程沒有創(chuàng)建對應(yīng)的 Looper 對象并在創(chuàng)建好后售葡、調(diào)用loop() 之前調(diào)用該 CountDownTimer 的構(gòu)造方法和 start(主要是構(gòu)造看杭,因為其中有 mHandler 的初始化會獲取當(dāng)前線程的 looper),即在后臺線程使用 CountDownTimer 的前提是需要后臺線程的 Looper 已創(chuàng)建挟伙,并且在該后臺線程中調(diào)用 CountDownTimer 的構(gòu)造方法楼雹。

線程開啟 Looper 并執(zhí)行 loop() 方法才會一直輪詢其 messageQueue。然后在該線程中處理注冊到這個線程 looper 的 handler 中 handler.post(runnable) 或 handler.sendMessage() 等放入的消息尖阔。通過那個 handler 發(fā)送到 messageQueue 的消息則調(diào)用相應(yīng)的 handler.dispatchMessage 回調(diào)(如果是 post(runnable) 則直接執(zhí)行 runnable.run 方法贮缅;否則執(zhí)行其委托者或者自己的 handleMessage() 實現(xiàn))。
注意:每個線程只能有 0 或者 1 個 looper 對象/messageQueue對象介却,但是一個線程的 looper 可以創(chuàng)建多個 handler谴供,相當(dāng)于任務(wù)分組。
所以上述的代碼修改成下面這樣齿坷,就可以使 CountDownTimer 在后臺線程也能正彻鸺。回調(diào)了数焊。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();

        initCountDownTimer();

        Looper.loop();
    }
}).start();

日志如下:

construct and start countDownTimer in thread: Thread-4875
count down onTick: Thread-4875
count down onTick: Thread-4875
count down onTick: Thread-4875
count down onFinish: Thread-4875

但是這個定時器即使 onFinish(),該后臺線程也不會停止崎场,要注意防止線程泄漏(詳見下面總結(jié)中第 9 條)佩耳。

Timer

在使用 Timer 實現(xiàn)中發(fā)現(xiàn),schedule 的 TimerTask 的 run 方法會在一個后臺線程中執(zhí)行谭跨,不管是不是在主線中調(diào)用 schedule 方法干厚,這點需要注意一下。

查了下 Timer 的源碼實現(xiàn)饺蚊,找到的原因:

  1. Timer 構(gòu)造時候會自己創(chuàng)建一個自定義的優(yōu)先隊列容器 TaskQueue 類對象(其中的任務(wù)抽象為 TimerTask)萍诱,然后創(chuàng)建一個自定義的線程類 TimerThread(重寫了其中的 run,自己實現(xiàn)了一個 loop 輪詢 queue 中下一個到達的定時任務(wù)污呼,然后處理)裕坊;
  2. 上面的 TaskQueue 類不是線程安全的,是 Timer 和 TimerThread 中通過 synchronized(queue) 實現(xiàn)線程安全的燕酷;
  3. timer.schedule 系列方法只是構(gòu)造一個 TimerTask 任務(wù)放入該優(yōu)先隊列中籍凝;

Thread/Looper/MessageQueue/Handler 一些總結(jié)

綜上,Thread/Looper/MessageQueue/Handler 的關(guān)系類圖和一些知識點總結(jié)如下:

類圖關(guān)系
  1. 主線程默認(rèn)開啟 looper苗缩,可以通過 Looper.getMainLooper() 獲取饵蒂。操作放入主線程執(zhí)行,常見使用如下:
// 1
new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        xxxx;
    }
});

// 2
new Handler(Looper.getMainLooper()).post(() -> { xxxx; });

// 3酱讶、調(diào)用已有的工具方法退盯,例如 Activity 中的
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
  1. LiveData 中 postValue() 方法中則是放到主線程中執(zhí)行,方便訂閱者(一般是 View 層)直接操作 UI泻肯。

  2. 自己創(chuàng)建的后臺線程默認(rèn)不開啟 looper渊迁,需要自己在線程的 run 實現(xiàn)中調(diào)用 Looper.prepare() 和 Looper.loop() 來開啟 looper,注意 loop() 方法會一直循環(huán)輪詢消息隊列中的消息分發(fā)給相應(yīng)的 handler 處理直到線程異常中止或相應(yīng)中斷退出機制退出循環(huán)灶挟。在該線程中 Looper.prepare() 后則可以通過 Looper.myLooper() 獲取到該線程已經(jīng)啟用的 looper(在主線程調(diào)用 Looper.myLooper() 則獲取到的是主線程的 looper)琉朽。

  3. 一個線程只有啟用了 looper,才能構(gòu)造相應(yīng)的 handler 在這個后臺線程通過 handler 發(fā)消息和處理消息稚铣。一個 looper 可用于創(chuàng)建多個 handler箱叁,類似于任務(wù)分組。

  4. handler 的 sendMessage惕医、sendMessageDelayed耕漱、post(runnable)、postDelayed(runnable) 都是構(gòu)造一個消息體往相應(yīng)的線程的消息隊列中插入抬伺。消息各屬性如下:

msg.target = handler
msg.callback = r;  // 如果是 post(runnable) 相關(guān)方法孤个,callback 即為該 runnable 對象,否則為 NULL
msg.what = 用戶傳入沛简;
msg.args1 = 用戶傳入齐鲤;
msg.args2 = 用戶傳入斥废;
msg.obj = 用戶傳入;

已經(jīng)通過上述方法插入消息隊列的消息给郊,如果還未調(diào)度執(zhí)行牡肉,則可以通過調(diào)用 handler 相應(yīng)的 removeCallbacks()、removeMessages() 等接口從隊列中移除淆九。

線程 looper 輪詢 messageQueue 后調(diào)用相應(yīng)的 handler.dispatchMessage(msg) 方法统锤,如果是 Runnable 的任務(wù)則直接調(diào)用,否則調(diào)用委托對象或者重載的 handleMessage 來處理消息(注意:messageQueue 還支持屏障消息和異步消息炭庙,詳見下面總結(jié)的第 10 條)

  1. handler.post(runnable)饲窿、sendMessage() 接口與 iOS 開發(fā)中的 dispatch(queue, block) 接口不同,handler 只能關(guān)聯(lián)某個線程的 queue(開啟looper后)焕蹄,但是 handler 不僅支持 runnable 對象逾雄,也支持定制消息體和處理邏輯,方便用戶定制一些共同的處理(變化的地方用消息的參數(shù)區(qū)分)腻脏,想要并發(fā)調(diào)度執(zhí)行請使用 android 中提供的 ExecutorService 等線程池相關(guān)類鸦泳。

  2. 可以使用 HandlerThread 方便創(chuàng)建一個開啟了 looper 的線程,并提供方法方便獲取通過該 looper 構(gòu)造的 handler永品。

  3. 為了方便的創(chuàng)建一個后臺線程并開啟其 looper 后做鹰,創(chuàng)建一個用其 looper 構(gòu)造的 handler 并且可以自定義 handleMessage《悖可以實現(xiàn)一個基類 BackgroundHandler() 其中創(chuàng)建一個 HandlerThread 后臺線程钾麸,然后使用該線程創(chuàng)建好的 looper 去構(gòu)造一個內(nèi)部類 InnerHandler 對象(繼承自 Handler,重寫其 handleMessage 為調(diào)用 BackgroundHandler 中的 handleBackgroundMessage 抽象方法)(總結(jié):即對 HandlerThread 和 Handler 做了一層封裝炕桨,方便構(gòu)造和使用)饭尝。
    這樣我們只需要重寫一個子類繼承自 BackgroundHandler,并自定義 handleBackgroundMessage 實現(xiàn)即可快速實現(xiàn)一個后臺線程創(chuàng)建并定制消息處理谋作。

  4. 自己創(chuàng)建的線程開啟 looper 一定要注意其生命周期,在適當(dāng)?shù)臅r候(例如:頁面 onDestroy乎芳、onStop 時候)考慮 handler 撤銷已發(fā)送未處理消息遵蚜、線程銷毀,否則可能會出現(xiàn) handler 所引用的對象奈惑、創(chuàng)建的線程的內(nèi)存泄漏吭净。

  • Thread 持有 looper,looper 持有 queue肴甸,多個 handler 可以持有 looper/queue寂殉,已經(jīng)放入隊列中的消息會持有 handler,而 handler 所引用的對象(通常寫成內(nèi)部類原在,還會引用其所在的類對象)則不能得到及時釋放造成內(nèi)存泄漏友扰。
  • 另外這個線程如果只是用于某個頁面彤叉,則頁面退出時候也需要注意銷毀該線程,否則該開啟 looper 的線程會一直不會停止輪詢村怪,需要退出機制讓線程停止秽浇,避免內(nèi)存泄漏。
  • Looper 中提供了一些退出線程的方法甚负。
    柬焕。quitSafely 方法:實際上執(zhí)行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,消息隊列會清除 when 晚于當(dāng)前時間的所有同步/異步消息與同步障礙器梭域,留下本應(yīng)處理完的消息繼續(xù)處理斑举。
    。quit 方法:實際上執(zhí)行了 MessageQueue 中的 removeAllMessageLocked 方法病涨,消息隊列中會清除所有的消息富玷。
    。無論是調(diào)用了 quitSafely 還是 quit 方法没宾,Looper 就不再接收新的消息凌彬。即在調(diào)用了 Looper 的 quitSafely 或 quit 方法之后,消息循環(huán)就終結(jié)了循衰,這時候再通過 Handler 調(diào)用 sendMessage 或 post 等方法發(fā)送消息時均返回 false铲敛,表示消息沒有成功放入消息隊列 MessageQueue 中,因為消息隊列已經(jīng)退出了会钝。
  1. MessageQueue 出了支持同步消息伐蒋,還支持屏障消息和異步消息,可以滿足優(yōu)先調(diào)度一些比較急迫的任務(wù)迁酸。

  2. Looper 還支持一些比較特殊的 IdleHander先鱼,用于在線程空閑的時候執(zhí)行一些不是很急迫的任務(wù),感覺可以用于主線程性能優(yōu)化奸鬓、啟動過程中一些性能優(yōu)化焙畔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市串远,隨后出現(xiàn)的幾起案子宏多,更是在濱河造成了極大的恐慌,老刑警劉巖澡罚,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伸但,死亡現(xiàn)場離奇詭異,居然都是意外死亡留搔,警方通過查閱死者的電腦和手機更胖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人却妨,你說我怎么就攤上這事饵逐。” “怎么了管呵?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵梳毙,是天一觀的道長。 經(jīng)常有香客問我捐下,道長账锹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任坷襟,我火速辦了婚禮奸柬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婴程。我一直安慰自己廓奕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布档叔。 她就那樣靜靜地躺著桌粉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衙四。 梳的紋絲不亂的頭發(fā)上铃肯,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音传蹈,去河邊找鬼押逼。 笑死,一個胖子當(dāng)著我的面吹牛惦界,可吹牛的內(nèi)容都是我干的挑格。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼沾歪,長吁一口氣:“原來是場噩夢啊……” “哼漂彤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灾搏,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挫望,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后确镊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體士骤,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡范删,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年蕾域,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡旨巷,死狀恐怖巨缘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情采呐,我是刑警寧澤若锁,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站斧吐,受9級特大地震影響又固,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煤率,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一仰冠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝶糯,春花似錦洋只、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妒茬,卻和暖如春担锤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背郊闯。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工妻献, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人团赁。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓育拨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親欢摄。 傳聞我的和親對象是個殘疾皇子熬丧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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