1.HandlerThread特点及注意事项
- HandlerThread本质上是一个线程类,继承自Thread。在线程内部,代码是串行处理的。
- 其内部拥有自己的Looper对象,也就是说它可以自己进行消息的循环。通过getLooper()方法可以将这个Looper对象传递给Handler对象,这样就可以在handleMessage()方法中执行异步任务。HandlerThread将looper对象传递给子线程进行处理,目的是为了分担MainLooper的工作量,降低了主线程的压力,使界面更加流畅。
- 开启一个线程起到多个线程的作用,处理任务是串行的,按消息发送顺序进行处理,但是由于因为每个任务都会以队列的形式被执行到,如果队列中某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
- HandlerThread拥有自己的消息队列,因此它不会阻塞UI线程。
- 创建HandlerThread后需要先调用handlerThread.start()方法,Thread会先调用run()方法,创建Looper对象。
2.应用场景
适用于会长时间在后台执行,并且间隔时间内会调用的任务。
3.Handler,Thread和HandlerThread的区别
- Handler会关联一个单独的线程,Looper和消息队列,默认关联是UI线程。
- HandlerThread继承自Thread,所以它本质就是一个Thread,只不过其内部有自己的Looper对象和消息队列,可以用来循环消息队列,并将消息回调到子线程中的Handler进行处理。
4.HandlerThread原理分析
public class HandlerThread extends Thread {
int mPriority;//线程优先级
int mTid = -1;
Looper mLooper;//当前线程持有的Looper对象
private @Nullable Handler mHandler;
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.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
}
从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了**Looper.prepare()**代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法:
/**
* 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() {
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;
}
事实上可以看出外部在通过getLooper方法获取looper对象时会先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。
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;
}
从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。
5.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-11 10:04:57.311 30673 30723 I Android_Test: mThreadHandler's thread: HandlerThread
03-11 10:05:02.313 30673 30673 I Android_Test: mUiHandler's thread: main
从上面的示例可以总结得到HandlerThread的使用方法:
- 首先创建HandlerThread对象并运行它,在创建过程中需要指定线程名字;
- 获取HandlerThread对象中的looper并通过它来构造一个子线程Handler对象;
- 主线程通过子线程Handler对象向子线程分发任务;
- 子线程处理耗时任务并把处理结果分发到主线程,主线程进行后续的处理。