本人只是 Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!
1. 概述
HandlerThread
是Android
提供用来创建含有Looper
线程的,其实在之前分析IntentService
的博文中已经看到了它的应用,再来回顾下IntentService
的启动过程:
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
// 创建包含 Looper 的线程并启动之
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 通过新线程的 Looper 创建 Handler 实例
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
复制代码
这段IntentService
的启动代码中直接使用到了HandlerThread
,但当时只是一笔带过并没有仔细分析HandlerThread
的使用方法和实现原理,本文将详细讲解如何在项目中使用HandlerThread
和其内部的实现原理。
本文假设您对
Handler,Thread,Looper,Message 和 MessageQueue
相关知识有了一定的了解,所以涉及到它们的地方,只会稍作说明不再深入分析。
2. HandlerThread 使用方法
在讲解其具体使用方法前,还是先来看下对HandlerThread
的声明:
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread { ... }
复制代码
从这段声明里可以看到:HandlerThread
能够很方便地启动一个带有looper
的线程,而这个looper
可以用来创建handler
。这句话里隐含了几点重要知识:
HandlerThread
是一个Thread
线程,具有线程的特性。Android
中默认线程没有looper
,如果想创建带有looper
的线程需要在创建的过程中主动创造looper
对象。Handler
中必须要有looper
,它是整个消息查询、分发、处理的核心,在创建Handler
的过程中可以指定任意线程的looper
对象。
现在通过一个简单的示例演示下HandlerThread
的使用方法:
public class MainActivity extends Activity {
private static final String TAG = "Android_Test";
private Button mButton;
private TextView mText;
// 新线程和与之相关联的 Handler 对象
private HandlerThread mHanderThread;
private Handler mThreadHandler;
// 和主线程相关的 Handler 对象
private Handler mUiHandler;
// 用于子线程和主线程中的消息分发
private static final int MESSAGE_CODE_GET = 1;
private static final int MESSAGE_CODE_SET = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.main_button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 主线程通过子线程 Handler 分发消息,以达到在子线程中处理耗时任务的目的。
mThreadHandler.sendEmptyMessage(MESSAGE_CODE_GET);
}
});
mText = (TextView) findViewById(R.id.main_text);
// 创建 HandlerThread 并启动新线程
mHanderThread = new HandlerThread("HandlerThread");
mHanderThread.start();
// 通过新线程中的 looper 创建相关的 Handler 对象
mThreadHandler = new Handler(mHanderThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "mThreadHandler's thread: " + Thread.currentThread().getName());
if (msg.what == MESSAGE_CODE_GET) {
try {
// 休眠 5 秒,模拟子线程处理耗时任务的过程。
Thread.sleep(5 * 1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
// 向主线程 Handler 发送处理结果
mUiHandler.sendEmptyMessage(MESSAGE_CODE_SET);
}
}
};
mUiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "mUiHandler's thread: " + Thread.currentThread().getName());
if (msg.what == MESSAGE_CODE_SET) {
// 主线程接收来自子线程的消息就行后续处理,这里是显示当前时间信息。
mText.setText(String.valueOf(SystemClock.uptimeMillis()));
}
}
};
}
}
复制代码
这个示例的主要功能是主线程中发起任务,在子线程中处理这些耗时任务,处理完成后通知主线程并更新界面,并打印出运行过程,从下面的运行结果可以看到:耗时任务确实是在子线程中执行的。
03-01 10:04:57.311 30673 30723 I Android_Test: mThreadHandler's thread: HandlerThread
03-01 10:05:02.313 30673 30673 I Android_Test: mUiHandler's thread: main
复制代码
从上面的示例可以总结得到HandlerThread
的使用方法:
- 首先创建
HandlerThread
对象并运行它,在创建过程中需要指定线程名字; - 获取
HandlerThread
对象中的looper
并通过它来构造一个子线程Handler
对象; - 主线程通过子线程
Handler
对象向子线程分发任务; - 子线程处理耗时任务并把处理结果分发到主线程,主线程进行后续的处理。
3. HandlerThread 原理分析
HandlerThread
和普通的Thread
的区别就在于其内部是包含Looper
的,所以我们分析的重点就是它是怎么创建使用Looper
以及在使用后如何退出。首先来看下它的构造函数:
public class HandlerThread extends Thread {
// 线程优先级
int mPriority;
// 线程号
int mTid = -1;
// 线程内部的 Looper 对象
Looper mLooper;
private @Nullable Handler mHandler;
// 只指定线程名字并使用默认的线程优先级来构造 HandlerThread 对象
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
// 同时指定线程名字和优先级来构造 HandlerThread 对象
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
// 省略其他内容
...
}
复制代码
由于HandlerThread
是直接继承Thread
的,所以在通过start()
启动线程后,其中的run()
就会启动,这也是线程内部的核心方法,来看下其实现:
@Override
public void run() {
mTid = Process.myTid();
// 创建一个和当前线程有关的 Looper 对象
Looper.prepare();
synchronized (this) {
// 得到当前线程的 Looper 对象后唤醒等待
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
// 调用回调方法,可以在开始消息轮询之前进行某些初始化设置,默认是空方法。
onLooperPrepared();
// 启动消息轮询,进行消息的查询分发和处理。
Looper.loop();
mTid = -1;
}
复制代码
这段代码就是HandlerThread
中创建Looper
对象并启动消息循环的核心,我们来一步步分析其重要逻辑。
3.1 创建 Looper 对象
在核心代码run()
中首先看到的是Looper.prepare()
,其作用就是创建当前线程的Looper
对象:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
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));
}
复制代码
在使用Looper.prepare()
创建Looper
对象的过程中利用ThreadLocal
把这个对象和当前线程建立了关联。
ThreadLocal
是一个可以存储线程局部变量的类,如果大家感兴趣可以自行查阅相关资料,在这里就不对其进行详细讲述了。
3.2 获取 Looper 对象
创建完Looper
对象后会在同步代码块里去唤醒等待,那这个等待会发生在什么时候呢?记得示例中是通过getLooper()
得到Looper
对象的,来看下它的内部实现:
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
// 线程没有启动或者已经死亡时返回 null
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
// 线程已经启动但是 Looper 对象还没有创建完成时等待
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
// 等待结束说明此时 Looper 对象已经创建完成,返回之。
return mLooper;
}
复制代码
在这里看到当“线程已经启动但是Looper
对象还没有创建完成”时会进行等待,当创建完成时会唤醒等待,这时getLooper()
就可以返回已经创建完成的Looper
对象了。之所以需要这个“等待-唤醒”机制,因为获取Looper
是在主线程中进行的,而创建Looper
是在子线程中进行的,必须使用这个机制来完成两者的状态同步。
3.3 开启 Looper 循环
前面已经讲了Looper
对象的创建以及如何在主线程中获取,那么如何通过Looper.loop()
开启循环呢?
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
// 获取Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取消息队列
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
// 开启一个无限循环来从消息队列中获取消息
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
// 获取到消息后,分发到 target 去处理。
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
// 回收消息对象
msg.recycleUnchecked();
}
}
复制代码
这段代码非常长,在分析的时候不需要弄懂每一行的意思,只需要了解其中关于消息的大致处理流程即可,大家如果不想去看这大段代码,只需关注添加注释的几行即可,其基本流程是:通过一个无限循环从消息队列中查询Message
消息,如果查询不到就等待,如果查询到就交给其target
来处理,最后要回收资源。
3.4 退出 Looper 循环
在使用HandlerThread
+Handler
在子线程处理耗时任务后并且不再需要时,必须要退出Looper
的消息循环,可以通过quit()
:
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
复制代码
这份方法可以退出Looper
循环同时会把当前消息队列中的所有消息都抛弃,也无法再向该消息队列中发送消息。但有时我们并不想直接清空消息队列,这时可以使用另外一种方式:
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
复制代码
这个方法可以更安全地退出,它会让消息队列中的非延迟消息继续得到处理,是更推荐的退出方式。
4. 总结
本文介绍了HandlerThread
的使用方法并分析其源码,通过分析源码,我们了解到了其内部Looper
的创建、获取、开启、退出的过程,加深了对HandlerThread
原理的理解,更有利于以后的使用。