Android Handler消息机制完全解析(四)-IdleHandler和epoll机制
Android Handler消息机制(五)-HandlerThread完全解析
关于Handler的相关文章我们已经学习了四篇了,这一篇我们来学习下另一个Handler相关的知识HandlerThread,从名字来看它有Handler和Thread应该跟这两者有关,同样我们还是带着问题来学习今天的内容
- 为什么要有HandlerThread
- HandlerThread解决了什么问题
首先Handler是可以跨线程进行通讯的,大家想象一个场景:有两个Thread,ThreadA和ThreadB,如何在ThreadB中声明一个Handler对象且ThreadB中的Handler发送消息会发送到ThreadA中处理?通过前面的学习我们知道在Handler有一个传递Looper对象的构造方法
java
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
因此我们可以在ThreadB中获取到ThreadA中的looper对象,然后声明Handler的时候将此looper传递给Handler对象,这样就可以达到在ThreadB中声明的Handler发送的消息会发送到ThreadA中处理。来看一段代码
java
Thread threadA = new Thread(new Runnable() {
Looper looper;
@Override
public void run() {
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
public Looper getLooper() {
return looper;
}
});
threadA.start();
Handler workHandler = new Handler(threadA.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// dosomething
}
};
如上所示第16行在主线程声明一个Handler的时候传递了threadA的Looper,这样通过workHandler发送的消息就能在threadA中收到,但是这样有一个问题就是上述threadA.getLooper()这个方法获取不到,因为在实例化handler的时候的Runnable是一个匿名内部类,而外部类不能直接访问匿名内部类的属性和方法,那么怎么办呢?我们可以将将Thread封装一下
java
public class MyHandlerThread extends Thread {
Looper looper;
MyHandlerThread(String name) {
super(name);
}
@Override
public void run() {
super.run();
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
public Looper getLooper() {
return looper;
}
}
于是我们调用就可以像下面这样调用
java
MyHandlerThread threadA = new MyHandlerThread("threadTest");
threadA.start();
Handler workHandler = new Handler(threadA.getLooper());
这样封装之后似乎在线程中声明一个Handler简洁了很多,因为我们把Looper的一些操作封装在run方法里。但是这样有并发的问题,在第二行调用threadA.start之后会运行MyHandlerThread的run方法并对looper进行赋值(这个过程是在子线程中执行的),而主线程的代码运行到第三行时会调用threadA.getLooper方法,此时有可能子线程还未对其进行赋值,也就是说此时threadA.getLooper可能为空。怎么解决这种问题呢?鉴于上述一系列问题所以谷歌给我们封装了一个HandlerThread,我们来看看它是怎么解决这个并发问题的呢?也就是说它是怎么确保执行threadA.getLooper时looper一定是有值的。要想彻底弄明白这个问题,需要对多线程并发有一定的了解,首先要理解synchronized关键字,我在这里写两段伪代码
java
public class Test {
public void test1() {
synchronized (this) {
code1
}
}
public void test2() {
synchronized (this) {
code2
}
}
}
问题来了,test1中的code1和test2中的code2是互斥访问的吗?答案是的,因为者两者持有的是同一把锁所以同一时刻只能执行一个。如果是下面这样就不会互斥
java
public class Test {
public void test1() {
synchronized (obj1) {
code1
}
}
public void test2() {
synchronized (obj2) {
code2
}
}
}
了解synchronized还不够,还需要对多线程的wait和notify有所了解,这里我直接上一个实例,这个实例就是利用多线程的wait和notify方法,来达到等待、唤醒的一个操作
java
public class ThreadA extends Thread {
private Object obj;
public ThreadA(Object obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("wait之前的时间:" + TimeUtils.getDate(System.currentTimeMillis()));
obj.wait();
System.out.println("wait之后的时间:" + TimeUtils.getDate(System.currentTimeMillis()));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
首先定义一个ThreadA它继承自Thread在构造方法中接收一个Object对象用来加锁,并重写run方法,在run方法中调用wait
java
public class ThreadB extends Thread {
private Object obj;
public ThreadB(Object obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj) {
System.out.println("notify之前的时间:" + TimeUtils.getDate(System.currentTimeMillis()));
obj.notify();
System.out.println("notify之后的时间:" + TimeUtils.getDate(System.currentTimeMillis()));
}
}
}
然后定义一个ThreadB与ThreadA类似,只不过它在run方法里调用notify方法,然后再main函数调用
java
Object object = new Object();
ThreadA threadA = new ThreadA(object);
threadA.start();
Thread.sleep(3000);
ThreadB threadB = new ThreadB(object);
threadB.start();
大家先想一想如何打印,打印结果
java
wait之前的时间:2024-10-29 17:16:09
notify之前的时间:2024-10-29 17:16:12
notify之后的时间:2024-10-29 17:16:12
wait之后的时间:2024-10-29 17:16:12
发现没有在threadA执行到wait方法后就释放了锁并进入了等待状态,后面的代码暂时不执行,等threadB拿到锁并调用notify方法并且将代码块执行完后将锁释放,此时ThreadA中的wait收到了notify的通知,继续后面的代码执行。这种场景在并发编程中也是经常用到,大家要记住这种场景的处理方式即加锁+wait+notify的方式。
回想一下我们刚才遇到的问题即如何确保threadA.getLooper()方法一定有不为null的返回值。这里是不是提供了一个思路:可以对getLooper方法加锁并进行判断如果looper为空则调用wait等待,run方法也加锁当完成了对mLooper的赋值之后notify一下,此时getLooper方法被唤醒并且looper已经完成了赋值。
说了这么多好像HandlerThread还没正式登场,上面我们讲的内容其实就是HandlerThread的主要原理,HandlerThread的源码并不多总共还不到200行,我精简一下
java
public class HandlerThread extends Thread {
...
Looper mLooper;
private @Nullable Handler mHandler;
/**
* 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;
}
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;
}
...
}
可以看到首先HandlerThread是一个线程类它继承自Thread,并且对其进行了封装,在run方法里调用了Looper.prepare()和Looper.loop()方法,第17行和33行的锁是不是就跟我们上面讲的实例是一样,在调用getLooper方法时会判断mLooper是否为null,如果mLooper为null说明run方法里还未对mLooper赋值,此时调用wait方法进入等待状态且getLooper方法会将锁释放,run方法拿到锁并对mLooper赋值之后调用notifyAll方法此时getLooper收到这个通知,此时会再次判断mLooper是否为空,由于此时在run方法里已经对mLooper进行了赋值因此此时mLooper是不为空的,所以会执行return mLooper,这样getLooper就确保了mLooper一定有值。
关于HandlerThread的源码以及封装的过程就讲到这里,这也是关于Handler的第五篇文章了,相信这五篇文章的内容足以解决你工作和面试中的问题。
如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。