HandlerThread 相信大家都比較熟悉了,從名字上看是一個帶有Handler訊息迴圈機制的一個執行緒,比一般的執行緒多了訊息迴圈的機制,可以說是Handler+Thread的結合,從原始碼上看也是如此的設計,一般情況下如果需要子執行緒和主執行緒之間相互互動,可以用HandlerThread來設計,這比單純的Thread要方便,而且更容易管理,因為大家都知道Thread的生命週期在一些情況下是不可控制的,比如直接new Thread().start(),這種方式在專案中是不推薦使用的,實際上Android的原始碼中也有很多地方用到了HandlerThread,下面我將分析一下HandlerThread以及涉及到的一些其他相關的問題。
HandlerThread的原始碼簡單分析
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
複製程式碼
首先欄位有優先順序,可以看到HandlerThread裡面的優先順序是預設的,當然了,你也可以修改,呼叫有二個引數的建構函式就可以了,在建構函式裡面有個String型別,代表HandlerThread名字的,這個一般情況下可以取執行緒的名稱。
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
複製程式碼
這裡面有一個方法onLooperPrepared(),在實際中,我們可以重寫這個方法做一些初始化的操作,這個run()是重點,可以看到Looper進行了初始化並且開始迴圈接收訊息了,並且喚醒了當前所有等待的執行緒,由於run方法是在start之後才會啟動的,因此在用HandlertThread的時候,在初始化了例項之後就必須呼叫start方法開啟訊息迴圈了。
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
複製程式碼
這個方法是獲取當前的looper,可以看到如果沒有獲取的時候就一直等待直到獲取,而前面也提到了獲取到了就喚醒了所有的執行緒,看來這是執行緒的等待-喚醒機制應用。
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
複製程式碼
這個是獲取HandlerThread繫結的Looper執行緒的Handler
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
複製程式碼
可以看到這兩個方法去退出執行緒的Looper迴圈,那麼這兩個方法有什麼區別呢,實際上都是呼叫了MessageQueue的quit()方法,原始碼如下:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
複製程式碼
可以看到: 當我們呼叫quit方法的時候,實際上執行了MessageQueue中的removeAllMessagesLocked方法,該方法的作用是把MessageQueue訊息池中所有的訊息全部清空,無論是延遲訊息(延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法傳送的需要延遲執行的訊息,只要不是立即執行的訊息都是延遲訊息)還是非延遲訊息。
而quitSafely方法時,實際上執行了MessageQueue中的removeAllFutureMessagesLocked方法,通過名字就可以看出,該方法只會清空MessageQueue訊息池中所有的延遲訊息,並將訊息池中所有的非延遲訊息派發出去讓Handler去處理,quitSafely相比於quit方法安全之處在於清空訊息之前會派發所有的非延遲訊息,一句話,就是清除未來需要執行的訊息。
這兩個方法有一個共同的特點就是:Looper不再接收新的訊息了,訊息迴圈就此結束,此時通過Handler傳送的訊息也不會在放入訊息杜佇列了,因為訊息佇列已經退出了。 應用這2個方法的時候需要注意的是:quit方法從API 1就開始存在了,比較早,而quitSafely直到API 18才新增進來.
到此原始碼分析結束,下面看一個實際的簡單的例子:
private static class UserHandlerThread extends HandlerThread implements Handler.Callback {
private Handler mHandler;
public UserHandlerThread(String name) {
super(name);
start();
mHandler = new Handler(getLooper(), this);
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what==0x123){
Log.d("[app]","收到訊息了");
}
return false;
}
public Handler getHandler() {
return mHandler;
}
}
private UserHandlerThread handlerThread;
handlerThread = new UserHandlerThread("handlerThread");
handlerThread.getHandler().sendEmptyMessage(0x123);
複製程式碼
OK,大家都知道結果了吧,一般情況下,在OnDestroy方法中需要退出迴圈,比如下面程式碼:
@Override
protected void onDestroy() {
super.onDestroy();
if (handlerThread != null) {
handlerThread.getHandler().removeCallbacksAndMessages(null);
handlerThread.quit();
handlerThread = null;
}
}
//Looper.getMainLooper().quit();
複製程式碼
大家先忽略註釋的那句程式碼, 好了,下面分析3個涉及到相關的問題:
● 既然子執行緒可以退出訊息迴圈佇列,那麼UI執行緒,也就是主執行緒是否也一樣可以在onDestroy方法退出訊息迴圈呢,就是取消剛才程式碼的註釋.
先說答案:不能退出主執行緒的訊息佇列,不然會丟擲Main thread not allowed to quit.錯誤,是不是很熟悉,沒錯,上面的程式碼中已經貼出來了,為什麼呢,MessageQueue有一個欄位:mQuitAllowed
// True if the message queue can be quit.
private final boolean mQuitAllowed;
複製程式碼
這個欄位代表:是否可以退出訊息佇列的意思,那麼主執行緒的這個欄位是什麼呢,是false,就是不能退出當前訊息的訊息佇列的意思,來看主執行緒的訊息初始化程式碼:Looper.prepareMainLooper();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製程式碼
可以看到prepare引數中,主執行緒傳入的是false,而在Looper的建構函式裡面被賦值了,也就是主執行緒的是false,所以這就決定了主執行緒是不能退出訊息佇列,那為什麼子執行緒就可以退出訊息佇列呢,因為子執行緒傳入的是true,程式碼如下:
Looper.prepare();
public static void prepare() {
prepare(true);
}
複製程式碼
看到沒,子執行緒傳入的是true,這說明子執行緒是可以退出訊息佇列的, 實際上真正退出主執行緒的訊息佇列是FrameWork層做的事情,下面的第三個問題會提到。
● Handler裡面發出去的訊息,究竟是在哪個執行緒執行的呢?
本文並不打算長篇分析Handler的原始碼,只簡單討論跟Handler執行緒相關的問題,實際上,對於Handler,如果能理解下面的圖,然後再自己去分析原始碼,我想理解會更深
我們知道,handler的建構函式中有一個引數,就是設定當前Looper的,程式碼如下:
/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
複製程式碼
可以看到傳入的Looper最終會被賦值到mLooper欄位,如果沒有呼叫這個建構函式,那麼mLooper由當前執行緒獲取,而訊息佇列MessageQueue是由Looper得到的,換句話說:Handler的訊息在哪個Looper迴圈,那意味著所有的訊息將會被哪個Looper的訊息佇列所儲存和處理,而一個執行緒只能繫結一個Looper,因此一個Handler只能繫結一個Looper,也就意味著,Handler繫結了哪個執行緒的Looper,那麼所有的訊息都會在哪個執行緒執行,這跟上面的圖是一致的,當然了有同學說我在Handler裡面開執行緒就另外說了,但Handler操作的本身一定是在對應的Looper所在的執行緒的,就是如果Handler繫結了主執行緒的Looper,那麼所有的訊息都會在主執行緒處理,如果繫結的是子執行緒,那麼所有的訊息都會在子執行緒處理。
●在Android一個應用程式"退出"的真正含義是什麼呢?
對於這個問題,有同學可能說我殺掉程式,應用程式掛了,也就相當於退出了。實際上這個說法是不正確的,因為這裡的殺掉程式,不僅僅是程式本身,而且連程式的記憶體空間也一併被處理,我們前面說了,子執行緒可以退出訊息佇列,意味著子執行緒就再也無法接收到任何訊息了,這就是子執行緒的退出含義,實際上,主執行緒也是一樣的,只是這個過程對我們開發者不可見而已,在主執行緒中,如果退出了訊息佇列,那麼意味著主執行緒也無法接收到任何訊息,下面是程式碼,在ActivityThread.java裡面:
public final void scheduleExit() {
sendMessage(H.EXIT_APPLICATION, null);
}
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
複製程式碼
看到這裡,我想熟悉IPC過程的同學應該都知道了,沒錯,scheduleExit()不是本地程式呼叫的,而是由服務端程式ActivityAManagerService服務進行呼叫的,這也是我為什麼說退出主執行緒是由FrameWork呼叫的原因,在AMS裡面有2處地方呼叫了退出的程式碼,分別是繫結本地程式和記憶體回收工作的時候呼叫的,下面是程式碼(在ActivityManagerService.Java裡面):
在APP本地程式繫結到AMS程式的時候呼叫
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
// Find the application record that is being attached... either via
// the pid if we are running in multiple processes, or just pull the
// next app record if we are emulating process with anonymous threads.
ProcessRecord app;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
} else {
app = null;
}
if (app == null) {
Slog.w(TAG, "No pending application record for pid " + pid
+ " (IApplicationThread " + thread + "); dropping process");
EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
if (pid > 0 && pid != MY_PID) {
Process.killProcessQuiet(pid);
//TODO: killProcessGroup(app.info.uid, pid);
} else {
try {
//這裡也呼叫了
thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
return false;
}
//省略一些非必要程式碼
}
//i清理記憶體的時候呼叫
final void trimApplications() {
synchronized (this) {
int i;
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.toShortString() + " ("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
//殺程式
app.kill("empty", false);
} else {
try {
//這裡呼叫了退出主執行緒訊息佇列程式碼
app.thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
mRemovedProcesses.remove(i);
if (app.persistent) {
addAppLocked(app.info, false, null /* ABI override */);
}
}
}
// Now update the oom adj for all processes.
updateOomAdjLocked();
}
}
複製程式碼
看到了吧,在首次App繫結程式的時候,如果發生app==null這個錯誤的時候就呼叫了退出主執行緒訊息佇列,另一個就是在清理記憶體的時候呼叫,這兩個過程對我們開發者不可見,我們瞭解就好,在原始碼面前,並沒有什麼是神奇的地方。
感謝大家閱讀,如果有什麼錯誤的地方,請歡迎指出,謝謝。