一、如何在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.(Handler.java:200)
30659 30827 E AndroidRuntime: at android.os.Handler.(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,完成,有疑问或者有表述不清楚的地方,欢迎评论。