1. 简述
Android中耗时操作不能放在主线程,执行耗时操作都需要开启子线程来执行,执行完线程以后线程都会自动销毁。如果经常要开启线程,接着又销毁线程,这是很消耗性能的。可以选择的方案有:
a. 使用线程池 (线程池的相关介绍可以参考之前的文章:Java线程池)
b. 直接创建子线程(创建线程的方式可以参考之前的文章:Java创建线程的三种方式)
c. 使用 HandlerThread
2. HandlerThread的使用
a. 创建HandlerThread的实例对象;
b. 启动创建的HandlerThread线程;
c. 创建Handler对象,将HandlerThread的Lopper作为参数,完成 Handler对象与HandlerThread的Looper对象的绑定。
3. 实例
在布局中定义一个按钮来开启异步操作,一个TextView来显示执行异步操作后的消息变化。
布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/mTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="Hello World!" />
<Button
android:id="@+id/mBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/mTv"
android:layout_marginTop="80dp"
android:text="点击按钮" />
</RelativeLayout>
Activity以及对应的逻辑:
package cn.zzw.messenger.handlerthreaddemo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class MainActivity extends AppCompatActivity {
TextView mTv;
Button mBtn;
Handler mWorkHandler;
Handler mUiHandler;
int count = 0;
/**
* 创建主线程的Handler
*/
private static class UiHandler extends Handler {
WeakReference<MainActivity> reference;
public UiHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
if (null != reference) {
MainActivity activity = (MainActivity) reference.get();
if (null != activity) {
//主线程接收到消息后,更新UI
activity.mTv.setText("" + activity.count);
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUiHandler = new UiHandler(this);
//创建 HandlerThread 的实例对象
HandlerThread mHandlerThread = new HandlerThread("zuowei.zhang");
//启动创建的 HandlerThread 线程
mHandlerThread.start();
//创建工作线程的Handler对象,将HandlerThread的Lopper作为参数,完成 Handler对象与HandlerThread的Looper对象的绑定
mWorkHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
try {
//在工作线程的Handler的handleMessage中执行耗时操作
Thread.sleep(5000);
count += 1000;
//耗时操作完毕后,通过主线程的Handler,将消息发送到主线程。
mUiHandler.sendEmptyMessage(10011);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
mTv = findViewById(R.id.mTv);
mBtn = findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//通过工作线程的Handler发送消息到其绑定的消息队列
mWorkHandler.sendEmptyMessage(10086);
}
});
}
}
效果:
连续点击4次,发现每5秒,count的值增加1000,并显示在TextView上。
从上面的动画中可以发现:连续点击4下时,并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来。这是因为使用HandlerThread时只是开了一个工作线程,当点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作。
4. 源码分析
先上下HandlerThread的源码,加上自带的注释,总共不到200行的代码:
package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
/**
* 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 {
int mPriority;
int mTid = -1;
Looper mLooper;
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() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* 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;
}
/**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
/**
* 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>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* 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;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
当我们使用的时候调用如下代码:
HandlerThread mHandlerThread = new HandlerThread("zuowei.zhang");
//启动创建的 HandlerThread 线程
mHandlerThread.start();
先看下它的构造方法:
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;
}
有两个构造方法,name代表当前线程的名称,priority为线程的优先级别。
当创建 HandlerThread 对象后,调用 start() 方法开启线程,执行的是线程的 run() 方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
在此方法中,首先调用 Looper.prepare() 去初始化 Looper对象,并持有锁机制来获得当前线程的Looper对象。并且调用了 Looper.loop() ,开启循环。对于Looper 的相关介绍可以参考之前的文章 Android Handler 消息机制 。
在执行 Looper.loop() 方法之前,执行了 onLooperPrepared() :
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
此方法的实现是一个空的,用户可以在子类中实现该方法。该方法的作用是在线程loop之前做一些初始化工作。也可以不实现该方法,取决于具体的需求。
当创建 Handler 对象后,会调用 HandlerThread 的 getLooper() 方法,并将其作为参数放入 Handler 中。
/**
* 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;
}
首先判断此工作线程是否存活,如果没有存活直接返回 null 。如果当前线程存活的,接着判断线程中的 mLooper 是否为null,如果为null,说明当前线程已经创建成功。如果当前线程存活的,接着判断线程中的 mLooper 是否为null,如果为null,说明当前线程已经创建成功,只是还没创建Looper对象,就会调用wait方法去等待 mLooper 的创建。当 run() 方法执行后,并且初始化 mLooper 对象后,就会调用 notifyAll() 方法来通知 wait() 方法等待结束,跳出循环,获得mLooper对象的值。当 mLooper 对象获得后,接下来执行的流程可以参考之前的文章:Android Handler 消息机制。
接下来继续看剩下的方法:
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;
}
从这两个方法的方法名可以看出,当前线程退出循环的方法,一种是安全的,一中是不安全的。两者的区别在于方法内部一个指向的是 looper.quit() 和 looper.quitSafely() 。
在 Looper.java 类中:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
最终它们调用的都是 MessageQueue.java类中的方法: quit(boolean safe),区别在于传入的参数值。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
而这个布尔值 safe 的作用在于调用不同的方法,当 safe 为 true 的时候调用的是 removeAllFutureMessagesLocked()方法 ;当 safe值为 false 的时候调用了 removeAllMessagesLocked() 方法。
方法 removeAllMessagesLocked():
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
这个方法其实就是遍历Message链表,移除所有信息的回调,并重置为null。
方法 removeAllFutureMessagesLocked():
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
这个方法,它会根据Message.when这个属性,判断当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,等待该消息处理处理完毕再退出该循环。
因此说 quitSafe() 是安全的,而 quit() 方法是不安全的,因为quit方法不管是否正在处理消息,直接移除所有回调。
5. 总结
a. HandlerThread适用于构建循环线程。
b. 在创建Handler作为HandlerThread线程消息执行者的时候必须调用start方法之后,因为创建Handler需要的Looper参数是从HandlerThread类中获得,而Looper对象的赋值又是在HandlerThread的run方法中创建。