• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

看完这篇。再也不怕被问 HandlerThread 的原理

开发技术 开发技术 2周前 (07-27) 16次浏览

HandlerThread是什么

看完这篇。再也不怕被问 HandlerThread 的原理

官网介绍

A Thread that has a Looper. The Looper can then be used to create Handlers.
Note that just like with a regular Thread, Thread.start() must still be called.

翻译:

HandlerThread,持有一个可用来构建Handlers的Looper,像一个常规的线程类,必须要调用start()才能正常工作。

HandlerThread的父类是Thread,所以HandlerThread的本质还是一个线程,但是它并非像Thread需要在run代码块内执行耗时的任务,HandlerThread是通过搭配外部的Handler分发处理消息执行任务的,可以很简单地返回和管理子线程的一个Looper对象。

HandlerThread常见的使用场景

有两个耗时任务A、B,任务B的执行需要A执行结果,即 A,B不可以并行执行,而是要串行按顺序执行任务。

下面给出模拟这种场景HandlerThread使用的实例代码:(代码可直接复制运行,有点长有点渣,见谅)

getResultA()doThingB(),模拟了A,B两个不可以并行执行的耗时任务。

taskHandlerHandler子类的实例,通过获取handlerThread开启后创建的Looper,串行发送了消息A,消息B,Looper自然也是先取出消息A,给taskHandler.handleMessage处理,再取出消息B完成了串行执行耗时任务A、B。

完成了串行执行耗时任务A、B。

public class HandlerThreadActivity extends AppCompatActivity {

    private Handler taskHandler;
    private HandlerThread handlerThread;

    private static String resultA;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handlerThread = new HandlerThread("HandlerThread-1");
        //!!关键:HandlerThread需要调用start开启线程,否则持有Looper为null
        handlerThread.start();
        //使用handlerThread线程持有的Looper构建 taskHandler实例
        taskHandler = new TaskHandler(handlerThread.getLooper());
        //发送消息A
        Message msgA = Message.obtain();
        msgA.what = 0;
        msgA.obj = "Task-A";
        taskHandler.sendMessage(msgA);
        //发送消息B
        Message msgB = Message.obtain();
        msgB.what = 1;
        msgB.obj = "Task-B";
        taskHandler.sendMessage(msgB);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //手动退出HandlerThread的Looper
        handlerThread.quitSafely();
    }

    @WorkerThread
    private static String  getResultA() {
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "DMingO";
    }

    @WorkerThread
    private static void  doThingB() {
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " :"+resultA + " 's blog");
    }

    private static class TaskHandler extends Handler{

        public TaskHandler(@NonNull Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 0:
                    //执行耗时任务 getResultA()
                    resultA = getResultA();
                    break;
                case 1:
                    if(! "".equals(resultA)){
                        //拿到任务A的返回结果才能执行任务B
                        doThingB();
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

运行结果:

可以看到TaskHandler.handleMessage是运行在HandlerThread这一个线程上,归根结底还是HandlerThread把它线程的Looper给了TaskHandler实例

I/System.out: HandlerThread-1 :DMingO 's blog

HandlerThread起的最大作用就是 很简便地提供了一个可设置命名和优先级的线程的Looper对象

HandlerThread源码分析

通过最简单的使用入手分析HandlerThread作为一个线程,提供一个子线程的Looper的背后原理:

        handlerThread = new HandlerThread("HandlerThread-1");
        handlerThread.start();
		taskHandler = new TaskHandler(handlerThread.getLooper());

看下getLooper()葫芦里什么药:

    public Looper getLooper() {
        //isAlive()判断当前线程是否已经开启
        //如果线程未开启(未调用HandlerThread.start),会返回null
        //所以必须执行了start()后,才能调用 getLooper(),否则会有空指针异常
        if (!isAlive()) {
            return null;
        }
        
        // 如果线程已开启但Looper未被创建,会进入同步代码块,阻塞-->直到Looper被创建
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    //mLooper==null-->线程进入阻塞状态
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        //确保 返回的mLooper不为null
        return mLooper;
    }

通过分析,getLooper() 方法确保可以返回一个HandlerThread线程持有的且非空的Looper对象。前提是HandlerThread线程已经开启。如果线程已开启但Looper未被创建,线程会阻塞,直到Looper被创建了。

那么在哪个方法,mLooper才被赋值,Looper对象才被创建呢?还记得 getLooper() 方法在最初如果发现线程未被开启,直接就返回null,这不就说明HandlerThread线程的开启与否与它的Looper创建,这两者息息相关嘛。

那就再看下HandlerThread的run()方法有什么名堂:

    @Override
    public void run() {
        mTid = Process.myTid();
        //创建此线程的Looper和MessageQueue
        Looper.prepare();
        synchronized (this) {
            //给 mLooper 赋值
            mLooper = Looper.myLooper();
            //此时mLooper!=null-->取消线程阻塞
            notifyAll();
        }
        //为线程设置mPriority优先级
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //开始运行 Looper
        Looper.loop();
        mTid = -1;
    }

开启HandlerThread线程后,会创建此线程的Looper和MessageQueue,设置线程优先级,开始Looper的循环取消息。

欸,HandlerThread这名字,它的Handler又去哪儿了呢?emmmm目前被隐藏了:

    private @Nullable Handler mHandler;
    
    /**
     * 返回与此线程相关联的一个Handler实例
     * @hide 目前此方法是被隐藏的,无法正常直接调用
     */
	@NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

可以看出,HandlerThreadmHandler的实例化是属于懒加载方式,只能在外界调用 getThreadHandler()的时候,才会对mHandler判空&进行实例化。实例化时传入的Looper对象自然是HandlerThread这一线程创建的Looper。因此若Looper还未被初始化,方法也会一直阻塞直到Looper创建完成,也需要线程已开启。

毫无疑问,mHandler 也自然也是只能去处理HandlerThread这一个线程的消息。

可以看出HandlerThread这个类与Looper的关系是密不可分的,自然也会有退出Looper的办法,看以下两个方法:

    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自然也为null),返回 false
  • 如果线程已经开启了,则会调用 Looper类的quit() / quitSafely()方法,并返回 true

不同的是,根据官方描述,建议使用quitSafely(),这会允许消息队列中还在排队的消息都被取出后再关闭,避免所有挂起的任务无法有序的被完成。

HandlerThread分析总结

HandlerThread 本质是一个Thread,却和普通的 Thread很不同的是:普通的 Thread 主要被用在 run 方法中执行耗时任务,而 HandlerThread 在线程开启后(run方法中)创建了该线程的Looper和消息队列,外界Handler可以很方便获取到这个Looper,搭配执行耗时任务,适合串行执行耗时任务等场景。


喜欢 (0)