一、如何在Thread中使用Handler戏挡?
- 在UI Thread中使用Handler
通常芍瑞,開發(fā)者會(huì)在UI Thread
直接初始化Handler
,用于處理各種Message
消息褐墅,實(shí)際上是用Looper
主循環(huán)器拆檬,從MessageQueue
消息隊(duì)列中循環(huán)獲取消息。那么這個(gè)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
又是什么時(shí)候被初始化的,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();
}
}
注解中已經(jīng)講解的很清楚:
調(diào)用prepareMainLooper初始化一個(gè)Looper逝钥,作為Application的main looper屑那,prepareMainLooper會(huì)被Android FrameWork直接調(diào)用,所以不需要開發(fā)者關(guān)心。
那么持际,OK沃琅,在UI Thread
中,Android FrameWork 會(huì)幫助我們初始化main looper
蜘欲,那么我們other Thread
中如何使用Handler
益眉。
-
non-UI Thread
使用Handler
首先看如下代碼執(zhí)行結(jié)果
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...
能夠被打印,這樣就完成了我們的目標(biāo)姥份,但是Log輸出的內(nèi)容如下:
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)
找到上面的異常輸出內(nèi)容郭脂,是在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
沒有獲取到當(dāng)前線程的looper
對象,OK澈歉,看一下此方法的實(shí)現(xiàn)展鸡。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
因?yàn)?code>ThreadLocal用來提供線程局部變量,多個(gè)線程之間相互隔離闷祥,所有說sThreadLocal
中娱颊,沒有當(dāng)前線程的Looper
實(shí)例,另外錯(cuò)誤輸出中已經(jīng)提示凯砍,咱沒調(diào)用Looper.prepare()
,看一下此方法的源碼實(shí)現(xiàn)拴竹。
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的新實(shí)例悟衩。OK,我們更新一下程序:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 第一次改動(dòng)新添加一行
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();
執(zhí)行程序栓拜,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: ...
哪里去了?繼續(xù)看源碼幕与,發(fā)現(xiàn)Looper.java
中有loop()
函數(shù)挑势,關(guān)鍵代碼如下:
/**
* 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會(huì)向MessageQueue
中添加一個(gè)Message
,但是我們上面實(shí)現(xiàn)的代碼啦鸣,沒有實(shí)現(xiàn)從消息隊(duì)列中取消息去執(zhí)行的邏輯潮饱,但是Looper#loop可以實(shí)現(xiàn)。所以我們在更新一下代碼:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 第一次改動(dòng)新添加代碼
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(); // 第二次改動(dòng)新添加代碼
}
}).start();
Log輸出內(nèi)容如下诫给,終于達(dá)成了我們的預(yù)期 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被調(diào)用后中狂,一直在執(zhí)行一個(gè)死循環(huán)凫碌,所以Looper.loop()后面不要實(shí)現(xiàn)任何代碼邏輯,因?yàn)橛肋h(yuǎn)都不會(huì)執(zhí)行到胃榕,除非執(zhí)行Looper#quit
二盛险、 HandlerThread 有何用途,和Thread有什么區(qū)別?
首先苦掘,我們來看一下HandlerThread.java
的關(guān)鍵實(shí)現(xiàn)
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
函數(shù),實(shí)現(xiàn)了我們剛才為了實(shí)現(xiàn)在non-ui tread
中使用Handler
而多添加的所有邏輯鸟蜡。并且HandlerThread繼承自Thread膜赃。所以,如果我們現(xiàn)在非UI線程中使用Handler揉忘,最簡單的代碼實(shí)現(xiàn)如下:
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,完成泣矛,有疑問或者有表述不清楚的地方疲眷,歡迎評論。