版權聲明:轉載必須註明本文轉自嚴振杰的博客: http://blog.yanzhenjie.com
本文主要講的是Android消息機制的Java層,Android消息機制對Android開發者來說是一個基礎知識,網絡上介紹Android消息機制的文章很多,爲了本文不顯得多餘,我爭取從不同的角度來做一個解析,包括一些基礎和源碼分析。
我們知道Android的消息機制主要指Handler
、MessageQueue
和Looper
的運作機制、要想完全搞清楚Android的消息機制勢必要先理解Binder IPC
機制、Linux pipe/epoll
機制,由於篇幅會過長,本文不會深入講IPC
機制和pipe/epoll
機制,涉及到以上兩點時會講述它的基本作用和原理。
本文主要介紹的內容
本文主要要介紹的類:
- ThreadLocal
- Handler、Looper和MessageQueue
- HandlerThread
- ActivityThread
- ApplicationThread
- IntentService
讀完本文後可以瞭解到的主要內容:
- 子線程爲什麼不能直接
new Handler()
? Handler
是如何切換線程的?Looper.loop()
死循環爲什麼不會導致主線程發生ANR?- ANR是如何發生的?
Activity
的生命週期是如何在Looper.loop()
死循環外執行的?
ThreadLocal
看到ThreadLocal
的第一感覺就是該類和線程有關,確實如此,但是要注意它不是線程,否則它就該叫LocalThread
了。
ThreadLocal
是用來存儲指定線程的數據的,當某些數據的作用域是該指定線程並且該數據需要貫穿該線程的所有執行過程時就可以使用ThreadnLocal
存儲數據,當某線程使用ThreadnLocal
存儲數據後,只有該線程可以讀取到存儲的數據,除此線程之外的其他線程是沒辦法讀取到該數據的。
一些讀者看完上面這段話應該還是不理解ThreadLocal
的作用,我們舉個栗子:
ThreadLocal<Boolean> local = new ThreadLocal<>();
// 設置初始值爲true.
local.set(true);
Boolean bool = local.get();
Logger.i("MainThread讀取的值爲:" + bool);
new Thread() {
@Override
public void run() {
Boolean bool = local.get();
Logger.i("SubThread讀取的值爲:" + bool);
// 設置值爲false.
local.set(false);
}
}.start():
// 主線程睡1秒,確保上方子線程執行完畢再執行下面的代碼。
Thread.sleep(1000);
Boolean newBool = local.get();
Logger.i("MainThread讀取的新值爲:" + newBool);
讀者朋友先不要往下看,請接合上面方的ThreadLocal
介紹猜測一下打印結果。
我想讀者朋友應該都猜中了結果,第一條Log無可置疑,因爲設置了值爲true
,因此打印結果是:
MainThread讀取的值爲:true
對於第二條Log,根據上方介紹,某線程使用ThreadLocal
存儲的數據,只能被該線程讀取,因此第二條Log的結果是:
SubThread讀取的值爲:null
緊接着在子線程中設置了ThreadLocal
的值爲false
,然後第三條Log將被打印,原理同上,子線程中設置了ThreadLocal
的值並不影響主線程的數據:
MainThread讀取的值爲:true
實驗結果證實:就算是同一個ThreadLocal
對象,任一線程對其的set()
和get()
方法的操作都是相互獨立互不影響的。
這就是開篇第一個問題的答案的基礎知識,但是要完全理解第一問題還需要後面的內容的鋪墊,因此我們先不揭曉第一個問題的完整答案。
Handler、Looper和MessageQueue
Handler
和Looper
組成了一個生產者消費者模式,Handler
作爲生產者向MessageQueue
添加產物Message
,Looper
作爲消費者,在Looper#loop()
方法的死循環中從MessageQueue#next()
循環取出Message
進行消費。
Handler
我們從Handler
的使用開始一步步翻閱源碼做個簡單的解析。我們在日常開發中使用Handler最多的場景是子線程向主線程發送消息更新UI,一般我們是這樣向主線程發送消息的:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 這裏是主線程
...
}
};
class UIThread implements Runnable {
@Override
public void run() {
// 這裏是主線程
...
}
}
class SubThread extends Thread {
private Handler mHandler;
SubThread(Handler handler) {
this.mHandler = handler;
}
@Override
public void run() {
... // 耗時操作。
// 第一種方法
mHandler.obtainMessage(1).sendToTarget();
... // 耗時操作。
// 第二種方法
Message message = new Message();
message.what = 2;
message.obj = ...
mHandler.sendMessage(message);
... // 耗時操作。
// 第三種方法
mHandler.post(new UIThread());
}
}
上述代碼看起來比較笨拙,但是列出了Handler向主線程發送消息的常用方法,不過這都是Handler
爲了使發送消息更加簡單而提供的封裝方法,他們都會產生一個Message
對象,包括Handler#post(Runnable)
最終也是剩成一個Message
:
public class Handler {
...
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message message = Message.obtain();
message.callback = r;
return message;
}
public final boolean sendMessageDelayed(Message msg, long delay) {
if (delay < 0) {
delay = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delay);
}
最終它們都走向了Handler#enqueueMessage()
然後轉而調用MessageQueue#enqueueMessage()
,其目的都是爲了向MessageQueue
添加一個Message
:
public class Handler {
...
boolean sendMessageAtTime(Message msg, long uptime) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
boolean enqueueMessage(MessageQueue queue, Message msg, long uptime) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
看了上述代碼,勤奮好學的我們得拋出一個問題,MessageQueue
中的消息是如何被讀取的呢?可想而知的是,MessageQueue
中有消息進入,肯定是有消息出去的,想必是有成對的方法提供出來吧。
MessageQueue
我們在MessageQueue
的源碼中發現了成對出現的進出方法(以下代碼經過大量精簡,讀者應該自行翻閱一下源碼,以免產生誤導):
public class MessageQueue {
...
Message mMessages;
boolean needWake;
/**
* 消息進入的方法。
*/
boolean enqueueMessage(Message msg, long when) {
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
// 上面省去了對needWake的賦值邏輯
if (needWake) {
// 喚醒阻塞
nativeWake(mPtr);
}
return true;
}
/**
* 消息出去的方法。
*/
Message next() {
int nextPollTimeMls = 0;
for (;;) {
// 嘗試阻塞
nativePollOnce(ptr, nextPollTimeMls);
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null);
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeMls = Math.min(msg.when - now, MAX_VALUE);
} else {
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
return msg;
}
} else {
nextPollTimeMls = -1;
}
nextPollTimeMls = 0;
}
}
從上述兩個方法結合更多源碼可以看出,MessageQueue
採用的是單向鏈表數據結構,mMessage
是鏈表的第一個元素,Message
的next
字段保存鏈表的下一個元素。
消息出去的方法在本文中非常重要,因此我們主要看一下MessageQueue#next()
方法,next()
裏面有一個for(;;)
循環,循環體內調用了nativePollOnce(long, int)
方法,這是一個Native方法,實際作用是通過Native層的MessageQueue
阻塞當前調用棧線程nextPollTimeMls
毫秒的時間。
下面是nextPollTimeMls
取值的不同情況的阻塞表現:
- 小於0,一直阻塞,直到被喚醒
- 等於0,不會阻塞
- 大於0,最長阻塞
nextPollTimeMls
毫秒,期間如被喚醒會立即返回
MessageQueue
中有一個nativeWake(long)
的Native方法,可以喚醒nativePollOnce()
的阻塞。
現在回到next()
方法體中,我們看到循環開始前nextPollTimeMls
的值是0
,那麼nativePollOnce()
方法將會立刻返回,此時嘗試取出下一個Message
元素,如果沒有下一個元素,nextPollTimeMls
的值被修改爲-1
,此時nativePollOnce()
進入阻塞狀態,等待下一個Message
的進入並喚醒阻塞,然後取出Message
對象返回。
Looper
現在知道消息從哪裏出去的,那麼接下來就看看Looper
是如何消費,Looper
類的代碼很少,我貼出關鍵部分的代碼(經過大量精簡):
public class Looper {
...
static ThreadLocal<Looper> sThreadLocal = ...;
private static Looper sMainLooper;
MessageQueue mQueue;
// 獲取當前線程的的Looper
public static Looper myLooper() {
return sThreadLocal.get();
}
// 初始化當前線程的Looper
public static void prepare() {
if (myLooper() == null) {
sThreadLocal.set(new Looper());
}
}
// 初始化主線程的Looper
public static void prepareMainLooper() {
if (sMainLooper == null) {
prepare();
sMainLooper = myLooper();
}
}
// 獲取主線程的Looper
public static Looper getMainLooper() {
return sMainLooper;
}
// Looper的構造方法中初始化MessageQueue
private Looper() {
mQueue = new MessageQueue();
}
// 循環處理當前線程的消息隊列中的消息
public static void loop() {
final Looper nowLooper = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare()
wasn't called on this thread.");
}
final MessageQueue nowQueue = nowLooper.mQueue;
for (;;) {
Message msg = nowQueue.next(); // 從消息隊列讀取下一條消息
if (msg == null) {
// 如果讀取到空消息,退出循環,退出該方法
return;
}
...
// 通過Handler分發消息
msg.target.dispatchMessage(msg);
...
}
}
上述代碼中,在Looper
的構造方法中,初始化了Looper
的MessageQueue
對象;初始化Looper
和獲取Looper
的方法使用到了ThreadLocal
,在ThreaqLocal
中我們介紹了ThreadLocal#get()
只能獲取到當前線程保存的數據;在Looper#loop()
方法中首先判斷了當前線程的Looper
是否爲空,爲空就拋出運行時異常,中斷當前操作,不爲空則進入死循環讀取消息隊列中的消息,把消息發回發送消息的Handler
去分發。
因此到這裏我們可以得出第一個問題的答案了:
子線程爲什麼不能直接
new Handler()
?
我們回到Handler
中看看調用Handler
的空構造發生了什麼:
public class Handler {
...
final MessageQueue mQueue;
final Callback mCallback;
public Handler() {
this(null, false);
}
public Handler(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;
mCallback = callback;
}
我們看到調用new Handler()
時會判斷Looper.myLooper()
方法獲取當前線程的Looper
,如果爲空則會拋出運行時異常中斷當前線程,不爲空則拿當前線程的Looper
對象中的MessageQueue
對象,等待Handler#sendMessage()
等方法向消息隊列中添加消息。
因此,在子線程中直接new Handler()
時,當前子線程的Looper
對象勢必爲空,爲空則不能繼續消費Handler
產生的Message
了,自然得拋出一個異常。
Handler/Looper運行機制梳理
簡單的學習了Handler
、MessageQueue
和Looper
後我們不難發現,當某個線程要使用Android的Handler
消息機制時,首先要調用Looper#prepare()
靜態方法爲當前線程生成一個Looper
對象,緊接着調用Looper#loop()
靜態方法後,會拿出該線程的Looper
對象的MessageQueue
開始循環調用MessageQueue#next()
方法獲取消息隊列的下一個Message
並處理。
根據上面MessageQueue
中的分析,當MessageQueue
中沒有下一個Message
時,next()
方法會調用MessageQueue#nativePollOnce()
阻塞當前線程,直到下一個Message
被加入並通過MessageQueue#nativeWake()
喚醒阻塞,此時便可以拿出下一個Message
返回給Looper
,Looper
通過msg.target.dispatchMessage(msg)
分發消息。
爲了理解的更加全面,接下來看看Handler#dispatchMessage(Message)
方法:
public class Handler {
...
final MessageQueue mQueue;
final Callback mCallback;
public interface Callback {
public boolean handleMessage(Message msg);
}
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
...
mQueue = mLooper.mQueue;
mCallback = callback;
}
// Looper中調用的分發方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
// 處理Runnable的Message
private static void handleCallback(Message message) {
message.callback.run();
}
// 處理非Runnable的Message
public void handleMessage(Message msg) {
}
看到這裏也就知道了,文章開頭的Handler
的接受消息爲什麼那麼用了。在Handler#dispatchMessage(Message)
中,首先判斷了該Message
是否是Runnable
,如果是,則直接執行Runnable#run()
方法,如果不是則看當前Handler
是否有Callback
對象,如果有的話就回調到Callback#handleMessage(Message)
方法去,如果沒有則調用Handler#handleMessage(Message)
方法。
明白了以上的流程,到了我們得出第二個問題的答案的時候了:
Handler
是如何切換線程的?
上面提到了,當某個線程要使用Android的消息機制時,首先必須要調用Looper#prepare()
方法爲當前線程生成一個Looper
對象,然後在該線程中調用Looper#loop()
拿出該線程的Looper
對象的MessageQueue
開始循環處理其中的消息,如果消息隊列爲空,那麼該線程就會被MessageQueue#nativePollOnce()
阻塞起來,只要該隊列中進來消息時,該線程同時被MessageQueue#nativeWake()
喚醒。其他線程要向該線程發送消息時,只要拿到該線程的Looper
並在其他線程實例化Handler
,在其他線程中使用Handler
發送消息即可向該線程的MessageQueue
中添加一個消息,此時該線程的Looper#loop()
方法即可獲取到消息並在該線程中處理了。
HandlerThread
根據以上原理我們來模擬一下使用流程,先封裝一段代碼:
public class HandlerThread extends Thread {
private Looper mLooper;
private Handler mHandler;
public HandlerThread() {
}
@Override
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
Looper.loop();
}
public Looper getLooper() {
return mLooper;
}
public Handler getHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public void quit() {
if (mLooper != null) {
mLooper.quit();
}
}
}
上述類中,在線程運行時,初始化了該線程的Looper
,並作爲成員變量保存了起來,然後調用Looper#loop()
靜態方法讓該Looper
去處理髮送到該線程的消息。
怎麼用這個封裝類呢?首先初始化一下讓這個線程運行起來:
public class AbcThread implements Runnable {
...
HandlerThread thread = new HandlerThread();
thread.start();
此時該線程應該阻塞在Looper#Loop()
處,如果我們發送一個Runnable
在這個線程執行,那麼我們可以:
public class AbcThread implements Runnable {
...
thread.getHandler().post(new Runnable() {
@Ovvride
public void run() {
// 該處代碼執行在 HandleThread#run() 方法中
}
});
另一種使用方式:
public class AbcThread implements Runnable {
...
Looper looper = thread.getLooper();
Handler mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
// 該處代碼執行在 HandlerThread#run() 方法中
}
};
Message message = new Message();
...
mHandler.sendMessage(message);
上述代碼都是僞代碼,讀者明白原理即可,不要照抄。Android SDK中也提供了HandlerThread
類,原理和上述類相同,但是考慮更加全面,具體讀者可以查看SDK源代碼。
ActivityThread
到此很多人也應該明白了Android主線程的工作原理,下面我們通過ActivityThread
深入理解Android主線程的工作原理。
ActivityThread
是Android應用程序的入口,也就是任何一個進程的主線程入口。爲了不增加讀者理解上的複雜度,此處我們以單進程爲例。對於單進程來說,ActivityThread
就是Android應用的主線程啓動的類,我摘抄了一段最主要代碼:
public final class ActivityThread {
...
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們看到了Java應用程序的main(String[])
方法,Android系統把每一個應用當做一個Java應用來看待,從main(String[])
方法中我們可以看到初始化了主線程Looper
並調用主線程Looper#loop()
循環處理髮送到主線程的消息。
此時,我們可以得出第三個問題的答案了:
Looper.loop()
死循環爲什麼不會導致主線程發生ANR?
我們知道,如果main()
方法執行完,那麼也意味着主線程生命週期結束,主線程即退出,應用程序也隨即死掉。當然我們是絕不希望應用在手機上點一下icon後沒啥反應,所以就要保證主線程一直或者。
那麼如何保證能主線程一直存活呢?就是讓該方法的代碼一直執行下去的,也就出現了Looper#loop()
死循環,簡單來說它保證了代碼一直在執行,在消息隊列中沒有消息的時候該線程被MessageQueue#nativePollOnce()
阻塞,但是該線程還是活着的,所以我們的主線程活着意味着我們的應用程序是正在運行的。
因此,Looper.loop()
中的死循環和阻塞保證了主線程一直在運行,而不是掛掉,它運行過程就是主線程的運行過程,因此Looper.loop()
中的死循環和MessageQueue#nativePollOnce()
不會導致主線程發生ANR。
主線程內
MessageQueue#nativePollOnce()
一直阻塞,是否會特別消耗CPU資源呢?這裏其實是利用了Linux pipe/epoll
機制,當MessageQueue#nativePollOnce()
阻塞時,此時主線程會釋放CPU資源進入休眠狀態,直到下一條消息被加入消息隊列,並調用MessageQueue#nativeWake()
後,通過往pipe
管道寫端寫入數據來喚醒主線程工作。因此主線程在阻塞時,其實是處於休眠狀態,並不會消耗大量CPU資源。
現在該總結第四個問題的答案了:
ANR是怎麼發生的呢?
其實Android所有的UI操作都通過Handler
來發消息操作的,包括屏幕刷新,各種點擊事件,Activity的生命週期等。因此當Looper.loop()
取到任一消息後,處理該消息的時間過長,影響到屏幕刷新速率,此時造成UI卡頓現象,乃至發生ANR。
主線程的運行機制大概如上,其中涉及到Linux pipe/epoll
機制可能是讀者比較陌生的,但是隻要知道它的作用即可理解這個過程。綜上所述,我們在日常開發中,想從子線程往主線程發送消息時一般這樣做:
private Handler mHandler = new Handler(Looper.getMainLooper());
...
mHandler.post(new Runnable(){
...
});
這樣就可以讓Runnable#run()
運行在主線程啦。
ApplicationThread
機智的讀者們又發現了另一個問題,既然是死循環一直在執行,那麼屏幕刷新,各種點擊事件和Activity的生命週期等又是如何在主線程執行的?如果是其他地方發送了消息到MainLooper
後被執行,而主線程執行句柄一直在死循環和阻塞中,是否開啓了新的線程來發送消息?也就是我們要探索的第五個問題:
Activity
的生命週期是如何在Looper.loop()
死循環外執行的?
答案是它確實開啓了新線程來執行,其實在上文ActivityThread
中我們注意到:
public final class ActivityThread {
...
private ApplicationThread mAppThread = new ApplicationThread();
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
// 建立Binder通道,創建新線程
thread.attach(false);
...
}
private void attach(boolean system) {
if (!system) {
...
IActivityManager mgr = ActivityManager.getService();
mgr.attachApplication(mAppThread);
...
}
...
}
看了源碼我們注意到,當Android應用啓動時代碼會執行到attach(boolean)
中system
爲false
的情況下。比較明顯的是mAppThrea
是ApplicationThread
類型,我們跟蹤源碼看一下ActivityManager.getService()
返回的IActivityManager
的實現類是什麼:
public class ActivityManager {
...
public static IActivityManager getService() {
return IActMngService.get();
}
private static final Singleton<IActivityManager> IActMngService
= new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
其中Singleton
是幫助方便開發者實現的單例模式的類,ActivityManager#getService()
最終拿到的是ActivityManagerService
,既Binder
的客戶端(C),也就是我們常說的AMS,它在SystemServer#run()
時被創建,並且運行一個獨立的進程中。
:
private class ApplicationThread extends IApplicationThread.Stub {
@Override
public final void scheduleLaunchActivity(IBinder token, ...) {
...
sendMessage(H.LAUNCH_ACTIVITY, token, ...);
}
public final void scheduleResumeActivity(IBinder token, ...) {
...
sendMessage(H.RESUME_ACTIVITY, token,
}
public final void schedulePauseActivity(IBinder token, ...) {
...
sendMessage(H.PAUSE_ACTIVITY, token, ...);
}
public final void scheduleStopActivity(IBinder token, ...) {
...
sendMessage(H.STOP_ACTIVITY, token, ...);
}
public final void scheduleDestroyActivity(IBinder token, ...) {
...
sendMessage(H.DESTROY_ACTIVITY, token, ...);
}
...
}
顯而易見的是,ApplicationThread
是Binder
的服務端,也就是說這裏遠程方法被調用時,都運行在非主線程中,而是Binder
的線程池的線程中。
我們看到的上述方法都是在執行Activity
的生命週期,當ApplicationThread
的遠程方法被調用時,就調用sendMessage()
發送一條消息,先來看一下源代碼:
public final class ActivityThread {
...
final H mH = new H();
private void sendMessage(int what, ...) {
Message msg = Message.obtain();
msg.what = what;
...
mH.sendMessage(msg);
}
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
...
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
...
handleLaunchActivity(...);
break;
}
case PAUSE_ACTIVITY: {
...
handlePauseActivity(...);
break;
}
...
}
}
在ApplicationThread
中調用sendMessage()
後最終H
類會收到消息並做出響應。根據我們前面的分析,H
被調用午餐構造時會獲取Looper.myLooper()
作爲自己的Looper
,那麼H
又是ActivityThread
被實例化時初始化的,因此H
持有主線程的Looper
,這樣一來H#handleMessage()
就回到主線程運行啦。
上方代碼中,當H#handleMessage()
運行時,通過handleLaunchActivity()
和handlePauseActivity()
等方法反射創建Activity
實例,然後調用其生命週期方法。
上述就是Android消息機制Java層的所有內容,如果有沒講清楚的地方,還請讀者朋友留言指教。
IntentService
接下來我們學習一個應用實例,IntentService
的本質也是四大組件之一的Service
,它與普通Service
不同的是,IntentService#onStart()
執行後會將操作發送到子線程去執行,其內部使用的就是我們上面提到的HandleThread
:
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread();
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
protected abstract void onHandleIntent(@Nullable Intent intent);
}
該例中使用HandlerThread
的方式與我們上面用的方式一致,先是啓動HandlerThread
線程,然後使用該子線程的Looper
重新實例化了一個Handler
,等到onStart()
響應時,將操作通過新實例化的Handler
發送到HandlerThread
這個子線程去操作,由於消息隊列在不設置優先級和執行時間時,遵循FIFO
的原則,也就是說它是以單線程的形式一個個的執行隊列中的任務。
因此,如果我們平常使用的Service
中有耗時任務,不妨把它換成IntentService
來試試。
本文就到這裏,覺得有幫助的朋友,請給我一個評論或者點贊,鼓勵和批評都是我前進的動力,謝謝讀者朋友耐心看完,告辭!
本文參考了以下鏈接:
- https://www.zhihu.com/question/34652589/answer/90344494
- https://pqpo.me/2017/05/03/learn-messagequeue/
版權聲明:轉載必須註明本文轉自嚴振杰的博客: http://blog.yanzhenjie.com