你用过HandlerThread么?

前言

我们都用过Handler,也很熟悉怎么使用Handler,肯定也知道Handler使用过程中的注意事项,那就是内存泄漏,也知道大部分内存泄漏都是因为静态变量引用的问题。Handler是一个内部类,非static内部类或者匿名内部类都会持有外部类的引用。如果此时Activty退出了, handler持有他的引用,则这个Activity 并不会被销毁,其实还是在内存中,所以就造成了内存泄漏 (Memory Leak) 的问题。怎么解决这个问题,网上都有很成熟的文章和技术实现,这里不再累赘,这期主要讲下Handler的另一种使用方式HandlerThread。

一、HandlerThread的本质

HandlerThread 本质上就是一个普通Thread。

Handler完成两个线程通信的代码中,需要调用Looper.prepare() 为一个线程开启一个消息循环,默认情况下Android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 然后通过Looper.loop() 让Looper开始工作,从消息队列里取消息,处理消息。

所以要使用Handler完成线程之间的通信,首先需要调用Looper.prepare() 为该线程开启消息循环,然后创建Handle,然后调用 Looper.loop() 开始工作。这都是很常规的流程。

而HandlerThread 帮我们做好了这些事情,它内部建立了Looper。

二、HandlerThread 用法

public class OtherActivity extends AppCompatActivity {
private static final String TAG = "OtherActivity";
private Handler handler1;
private Handler handler2;
private HandlerThread handlerThread1;
private HandlerThread handlerThread2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);

// 创建HandlerThread
handlerThread1 = new HandlerThread("handle-thread-1");
handlerThread2 = new HandlerThread("handle-thread-2");
// 开启HandleThread
handlerThread1.start();
handlerThread2.start();

handler1 = new Handler(handlerThread1.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};

handler2 = new Handler(handlerThread2.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};

handler2.sendEmptyMessage(2);
handler1.sendEmptyMessage(5);
}

// 释放资源
@Override
protected void onDestroy() {
super.onDestroy();
handlerThread1.quit();
handlerThread2.quitSafely();
}
}

HandlerThread创建 Looper 并执行 loop() 的线程在任务结束的时候,需要手动调用 quit。否则,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread 作为 GC Root 持有超出生命周期的实例引发内存泄漏。

官方使用 quitSafely() 去终止 Looper,原因是其只会剔除执行时刻晚于 当前调用时刻 的 Message。这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。

那么主线程需要 quit 吗?其实不需要,在内存不足的时候 App 由 AMS 直接回收进程。因为主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要! 换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。

HandlerThread 的在Android中的用处

Android本身就是一个巨大的消息处理机,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,主线程实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个主线程里被执行的。

public final class ActivityThread {

//...
private static ActivityThread sCurrentActivityThread;
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
private void attach(boolean system) {
sCurrentActivityThread = this;
//...
}
public static void main(String[] args) {
//....

// 创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();

// 创建ActivityThread对象
ActivityThread thread = new ActivityThread();

// 建立Binder通道 (创建新线程)
thread.attach(false);

Looper.loop(); //消息循环运行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施即可。

总结

管它呢,用就好了,封装好的东西干嘛不用了,可以减少我们开发过程中bug,提示开发效率,多好的一件事啊,用起来就对了,哈哈

原文链接:https://juejin.cn/post/7032649435133771807?utm_source=gold_browser_extension

0 个评论

要回复文章请先登录注册