一、概述
本篇文章是对Android HandlerThread源码分析并应用,谈一下自己对HandlerThread的认知理解,并通过Demo来讲解它的使用方法。
二、HandlerThread是什么
2.1 概念
从字面上来讲,它是 Handler + Thread 的组合,首先它是一个Thread线程,它是用来做耗时任务用的,其次在HandlerThread中已经实现好了Looper消息循环处理,我们在应用的时候只需要通过Handler发送具体的消息给HandlerThread执行对应的任务即可。
首先明确一点:Handler 是Android系统 同进程 不同线程间 通信用的。
为了理解上面这句话,我们引入进程和线程的概念
进程:进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发。
线程:线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发,每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
两者的区别
1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
2. 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。
比如在某个Activity界面,默认有一个UI主线程,我们称为A线程,在里面创建一个Handler, 然后在创建一个线程B,在里面做耗时任务,待任务完成,在B中通过Handler发送消息,在A中更新UI显示,这样就相当于在把线程B中的消息分发到线程A中处理,从而完成了线程间通信。
为什么呢?因为线程A 和 线程B 可以共享数据段(Handler对象)。
2.2 特点
1. HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
2. 优点是不会有堵塞,减少对性能的消耗,缺点是不能同时进行多任务的处理,需要排队,处理效率较低。
3. 与线程池注重并发不同,HandlerThread是一个窜行队列,因为HandlerThread背后只有一个线程。
三、源码解析
1. 两个构造方法
//构造方法,设置线程名称,默认线程优先级0
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
//构造方法,设置线程名称和自定义线程优先级
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
2. onLooperPrepared,此方法是 Looper消息循环前的准备工作,可以重写。
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
* Looper消息循环前的准备工作,可以重写
*/
protected void onLooperPrepared() {
}
3. run方法:当线程调用start()方法时,回调此方法
@Override
public void run() {
mTid = Process.myTid(); //线程id
Looper.prepare(); // 创建Looper对象和消息队列MessageQueue
synchronized (this) { //通过同步锁机制获取当前线程的Looper对象
mLooper = Looper.myLooper();
notifyAll(); //通知getLooper方法looper已经创建成功,对应getLooper方法中的wait()
}
Process.setThreadPriority(mPriority); //设置线程优先级
onLooperPrepared(); //重写此方法,作用是在消息循环之前进行一些准备工作
Looper.loop(); //开启消息循环
mTid = -1;
}
4. getLpoper()方法,与run()方法中有个同步锁机制,目的是为了准确且必定要获取当前线程的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.
此方法返回与此线程关联的Looper。如果此线程尚未启动或不处于活跃状态,
则此方法将返回null。如果此线程已启动,则此方法将阻塞,直到循环器已初始化。
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
boolean wasInterrupted = false;
// If the thread has been started, wait until the looper has been created.
// 使用对象同步锁机制:如果线程已开启, 进入等待状态直到looper成功初始化.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait(); //等待Looper初始化完成,对应run方法中的notifyAll
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
/*
* We may need to restore the thread's interrupted flag, because it may
* have been cleared above since we eat InterruptedExceptions
*/
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return mLooper;
}
为了理解上面synchronized同步锁机制,我们通过一个Demo来讲解
/**
* author : me
* date : 22-11-14下午2:49
* desc : 线程测试类
* version: 1.0
*/
public class TestMain {
public static void main(String[] args){
Object object = new Object();
MyThreadRunnable myThread1 = new MyThreadRunnable(object,"thread11111");
Thread test1 = new Thread(myThread1);//通过public Thread(Runnable runnable)方法创建线程test1
test1.start();//开始启动
try {
Thread.sleep(6000);
System.out.println("6秒后调用notifiyAll方法唤醒线程,让线程继续执行任务");
synchronized (object) {
object.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* author : me
* date : 22-11-14下午2:49
* desc :
* version: 1.0
*/
public class MyThreadRunnable implements Runnable {
private Object object;
private String threadName;
public MyThreadRunnable(Object object, String threadName) {
this.object = object;
this.threadName = threadName;
}
@Override
public void run() {
try {
for (int i=1; i<6; i++) {
if (i == 3) {
synchronized (this.object) {
System.out.println("当i==3时,线程调用wait方法 等待");
this.object.wait();
}
}
System.out.println(this.threadName + " i的值: " + i );
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打印如下:
thread11111 i的值: 1
thread11111 i的值: 2
当i==3时,线程调用wait方法 等待
6秒后调用notifiyAll方法唤醒线程,让线程继续执行任务
thread11111 i的值: 3
thread11111 i的值: 4
thread11111 i的值: 5
通过此例子,再来理解上面3和4中的代码,就可以明白了。
5. getThreadHandler()方法
/**
* @return a shared {@link Handler} associated with this thread
* @hide
// 返回与该线程关联的handler对象
//注意这是一个隐藏的方法,无法直接通过 HandlerThread对象调用 getThreadHandler() 获取Handler
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
此方法可以忽略,是一个hide方法,无法直接调用获取Handler, 一般还是得通过
public Handler(@NonNull Looper looper)方法来 获取handler对象, 比如
Handler mThreadHandler = new Handler(myHandlerThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
//消息处理
}
}
6. quit()和quitSafely()方法
// 直接退出线程的looper消息循环,强制退出循环
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
//待消息队列中剩余消息全部处理完毕后,退出线程的looper消息循环,安全退出消息循环
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
四、HandlerThread使用步骤
// 步骤1:创建HandlerThread实例对象
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
//如果不调用start方法,会报错:
11-15 17:30:32.347 8763 8763 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
11-15 17:30:32.347 8763 8763 E AndroidRuntime: at android.os.Handler.<init>(Handler.java:257)
11-15 17:30:32.347 8763 8763 E AndroidRuntime: at android.os.Handler.<init>(Handler.java:162)
//通过报错的堆栈log信息找到源码:Handler.java:257
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue; //这行代码报错,空对象异常
mCallback = callback;
mAsynchronous = async;
}
//looper为空对象,说明looper对象没有创建成功。 这也间接地表明 源码中通过同步锁机制必须保证要成功创建Looper对象的缘由。
// 步骤3:创建工作线程Handler 并 复写handleMessage()
//关联HandlerThread的Looper对象、实现消息处理操作与其他线程进行通信
Handler workHandler = new Handler( mHandlerThread.getLooper() ) {
@Override
//消息处理
public boolean handleMessage(Message msg) {
.......
//当然这里可以做耗时任务,因为是处于子线程中,不会堵塞UI线程,不会引起ANR
return true;
}
});
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
Message msg = Message.obtain();
msg.what = 1;
workHandler.sendMessage(msg);
// 步骤5:一般在Activity的OnDestory()方法中,结束线程,即停止线程的Looper消息循环
mHandlerThread.quit();
五、实例演示
/**
* author : me
* date : 22-11-15 下午3:46
* desc : 主界面
* version: 1.0
*/
public class MainActivity extends AppCompatActivity {
private Button mButton1;
private Button mButton2;
private MyHandlerThread myHandlerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1 = findViewById(R.id.btn_one);
mButton2 = findViewById(R.id.btn_two);
//1. 创建HandlerThread实例对象
myHandlerThread = new MyHandlerThread("handlerThread test");
//2. 启动线程
myHandlerThread.start();
//3. 创建工作线程Handler, 并复写handleMessage
Handler mThreadHandler = new Handler(myHandlerThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
try {
Log.e("test", "用线程睡眠6秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR");
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("test", "====接收主线程中发来的消息 处理消息的线程名称为:" + Thread.currentThread().getName());
} else if (msg.what == 2) {
try {
Log.e("test", "用线程睡眠12秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR");
Thread.sleep(12000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("test", "====接收自定义线程中发来的消息 处理消息的线程名称为:" + Thread.currentThread().getName());
}
}
};
//点击此按钮: UI主线程 与 HandlerThread线程 进行通信
mButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = Message.obtain();
msg.what = 1;
mThreadHandler.sendMessage(msg);
Log.e("test", "调用线程的名称:" + Thread.currentThread().getName());
}
});
//点击此按钮: 自定义线程 与 HandlerThread线程 进行通信
mButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 2;
mThreadHandler.sendMessage(msg);
Log.e("test", "调用线程的名称:" + Thread.currentThread().getName());
}
});
thread.start();
}
});
@Override
protected void onDestroy() {
super.onDestroy();
//退出app时,子线程中的looper要退出,终止消息循环
myHandlerThread.quitSafely();
}
}
/**
* author : me
* date : 22-11-14下午6:09
* desc :
* version: 1.0
*/
public class MyHandlerThread extends HandlerThread {
public MyHandlerThread(String name) {
super(name);
}
@Override
protected void onLooperPrepared() {
//如果需要执行某些Looper循环之前的设置,可以在该方法中处理
super.onLooperPrepared();
Log.e("test", "=====onLooperPrepared======");
}
@Override
public boolean quitSafely() {
Log.e("test", "=====quitSafely======");
return super.quitSafely();
}
}
当进入主界面,点击button1,然后退出app,打印log如下:
11-16 17:18:49.977 23277 23384 E test : =====onLooperPrepared======
11-16 17:18:53.121 23277 23277 E test : 调用线程的名称:main
11-16 17:18:53.121 23277 23384 E test : 用线程睡眠6秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR
11-16 17:18:59.122 23277 23384 E test : ====接收主线程中发来的消息 处理消息的线程名称为:handlerThread test
11-16 17:21:33.309 23277 23277 E test : =====quitSafely======
当进入主界面,点击button2,然后退出app,打印log如下:
11-16 17:22:43.080 23277 23620 E test : =====onLooperPrepared======
11-16 17:22:44.758 23277 23622 E test : 调用线程的名称:Thread-2
11-16 17:22:44.758 23277 23620 E test : 用线程睡眠12秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR
11-16 17:22:56.759 23277 23620 E test : ====接收自定义线程中发来的消息 处理消息的线程名称为:handlerThread test
11-16 17:23:06.764 23277 23277 E test : =====quitSafely======
好了,通过此Demo可以知道Handler可以在不同进程间通信,并且了解HandlerThread的使用方法啦。
总结
一. 关于Handler对象创建,取决于与哪个线程的Looper挂钩.
1. Handler uiHandler = new Handler() 或 new Handler(getMainLooper()) 或 new Handler(Looper.myLooper()); 这3个方法都是创建主线程的Handler对象.
2. Handler workHandler = new Handler(new HandlerThread("workthread").getLooper()); 它是创建子线程中的Handler对象.
通过workHandler 这个来发送消息,然后在handleMessage方法中处理任务,因为是任务在子线程中处理,所以不会引起ANR现象.
二. 使用注意
HandlerThread在线程内部创建Looper和消息队列,并且在线程启动之后开启消息循环。我们可以使用该Looper构建Handler对象从而绑定工作线程,然后通过Handler向消息队列发送消息,这样HandlerThread就可以取出消息并根据消息类型执行具体的后台任务,这里执行任务是在子线程中,它不会阻塞UI线程,所以不会引起ANR现象。
由于HandlerThread的run方法是一个无限循环,因此当我们不再使用HandlerThread的时候应该调用它的quit或quitSafely方法来终止线程。