一、如何在Thread中使用Handler?
- 在UI Thread中使用Handler
通常,開發者會在UI Thread
直接初始化Handler
,用於處理各種Message
消息,實際上是用Looper
主循環器,從MessageQueue
消息隊列中循環獲取消息。那麼這個Looper
對象是怎麼來的?大家很清楚可以通過Looper.getMainLooper
獲取,Looper.java
源代碼如下:
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
那麼sMainLooper
又是什麼時候被初始化的,Looper.java
源代碼如下:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
註解中已經講解的很清楚:
調用prepareMainLooper初始化一個Looper,作爲Application的main looper,prepareMainLooper會被Android FrameWork直接調用,所以不需要開發者關心。
那麼,OK,在UI Thread
中,Android FrameWork 會幫助我們初始化main looper
,那麼我們other Thread
中如何使用Handler
。
-
non-UI Thread
使用Handler
首先看如下代碼執行結果
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "non-ui thread start, thread id: " + Thread.currentThread().getId());
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "runnable run() be called, thread id: " + Thread.currentThread().getId());
}
});
ESLog.d(TAG, "non-ui thread end");
}
}).start();
我們期望runnable run() be called...
能夠被打印,這樣就完成了我們的目標,但是Log輸出的內容如下:
30659 30827 E AndroidRuntime: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
30659 30827 E AndroidRuntime: at android.os.Handler.<init>(Handler.java:200)
30659 30827 E AndroidRuntime: at android.os.Handler.<init>(Handler.java:114)
30659 30827 E AndroidRuntime: at java.lang.Thread.run(Thread.java:818)
找到上面的異常輸出內容,是在Handler.java
源代碼中:
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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
使用爲Looper.mylooper
沒有獲取到當前線程的looper
對象,OK,看一下此方法的實現。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
因爲ThreadLocal
用來提供線程局部變量,多個線程之間相互隔離,所有說sThreadLocal
中,沒有當前線程的Looper
實例,另外錯誤輸出中已經提示,咱沒調用Looper.prepare()
,看一下此方法的源碼實現。
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));
}
向ThreadLocal中添加一份Looper的新實例。OK,我們更新一下程序:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 第一次改動新添加一行
Log.d(TAG, "non-ui thread start, thread id: " + Thread.currentThread().getId());
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "runnable run() be called, thread id: " + Thread.currentThread().getId());
}
});
if (BuildConfig.DEBUG_LOG) {
ESLog.d(TAG, "non-ui thread end");
}
}
}).start();
執行程序,Log輸出如下:
31676 31775 D TestHandler: non-ui thread start, thread id: 556
31676 31775 D ES-File : {Thread-556}[TestHandler] non-ui thread end
什麼鬼,我的Handler#post中的輸出runnable run() be called, thread id: ...
哪裏去了?繼續看源碼,發現Looper.java
中有loop()
函數,關鍵代碼如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
---省略部分---
}
}
OK,使用Handler#post會向MessageQueue
中添加一個Message
,但是我們上面實現的代碼,沒有實現從消息隊列中取消息去執行的邏輯,但是Looper#loop可以實現。所以我們在更新一下代碼:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 第一次改動新添加代碼
Log.d(TAG, "non-ui thread start, thread id: " + Thread.currentThread().getId());
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "runnable run() be called, thread id: " + Thread.currentThread().getId());
}
});
if (BuildConfig.DEBUG_LOG) {
ESLog.d(TAG, "non-ui thread end");
}
Looper.loop(); // 第二次改動新添加代碼
}
}).start();
Log輸出內容如下,終於達成了我們的預期 GOOD。
32064 32188 D TestHandler: non-ui thread start, thread id: 565
32064 32188 D ES-File : {Thread-565}[TestHandler] non-ui thread end
32064 32188 D TestHandler: runnable run() be called, thread id: 565
切記: 從looper#loop的源碼中可以看出,loop被調用後,一直在執行一個死循環,所以Looper.loop()後面不要實現任何代碼邏輯,因爲永遠都不會執行到,除非執行Looper#quit
二、 HandlerThread 有何用途,和Thread有什麼區別?
首先,我們來看一下HandlerThread.java
的關鍵實現
public class HandlerThread extends Thread {
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
一目瞭然,HandlerThread的run
函數,實現了我們剛纔爲了實現在non-ui tread
中使用Handler
而多添加的所有邏輯。並且HandlerThread繼承自Thread。所以,如果我們現在非UI線程中使用Handler,最簡單的代碼實現如下:
public void initHandler(){
HandlerThread handlerThread = new HandlerThread("auto-back-up");
handlerThread.setPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
}
其餘正常使用Handler 即可,OK,完成,有疑問或者有表述不清楚的地方,歡迎評論。