學(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
- 主線程中使用
本地測試代碼如下:
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)方法。
- 后臺線程中使用
查了下 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)饺蚊,找到的原因:
- Timer 構(gòu)造時候會自己創(chuàng)建一個自定義的優(yōu)先隊列容器 TaskQueue 類對象(其中的任務(wù)抽象為 TimerTask)萍诱,然后創(chuàng)建一個自定義的線程類 TimerThread(重寫了其中的 run,自己實現(xiàn)了一個 loop 輪詢 queue 中下一個到達的定時任務(wù)污呼,然后處理)裕坊;
- 上面的 TaskQueue 類不是線程安全的,是 Timer 和 TimerThread 中通過 synchronized(queue) 實現(xiàn)線程安全的燕酷;
- timer.schedule 系列方法只是構(gòu)造一個 TimerTask 任務(wù)放入該優(yōu)先隊列中籍凝;
Thread/Looper/MessageQueue/Handler 一些總結(jié)
綜上,Thread/Looper/MessageQueue/Handler 的關(guān)系類圖和一些知識點總結(jié)如下:
- 主線程默認(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();
}
}
LiveData 中 postValue() 方法中則是放到主線程中執(zhí)行,方便訂閱者(一般是 View 層)直接操作 UI泻肯。
自己創(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)琉朽。
一個線程只有啟用了 looper,才能構(gòu)造相應(yīng)的 handler 在這個后臺線程通過 handler 發(fā)消息和處理消息稚铣。一個 looper 可用于創(chuàng)建多個 handler箱叁,類似于任務(wù)分組。
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 條)
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)類鸦泳。
可以使用 HandlerThread 方便創(chuàng)建一個開啟了 looper 的線程,并提供方法方便獲取通過該 looper 構(gòu)造的 handler永品。
為了方便的創(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)建并定制消息處理谋作。自己創(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)退出了会钝。
MessageQueue 出了支持同步消息伐蒋,還支持屏障消息和異步消息,可以滿足優(yōu)先調(diào)度一些比較急迫的任務(wù)迁酸。
Looper 還支持一些比較特殊的 IdleHander先鱼,用于在線程空閑的時候執(zhí)行一些不是很急迫的任務(wù),感覺可以用于主線程性能優(yōu)化奸鬓、啟動過程中一些性能優(yōu)化焙畔。