详解HandlerThread

作者: Chase_stars | 来源:发表于2019-05-03 15:40 被阅读16次

不达成功誓不休 — 程端礼

写在前面

“送走四月,喜迎五月”,今年的五一小长假简直舒服到了极致,牺牲两个周六换来的四天假期。是时候出去浪了, 打开微博发现不妙,吓得我赶紧躺床上又睡了一觉...

入职后经常加班,仅有的私人时间也没有心思学习。不过我还是告诉自己在忙也不能忘了充实自己。趁着今天有兴致,阅读了一下HandlerThread的源码。

用法

首先,我们来了解一下HandlerThread。

Q:什么是HandlerThread?
A:HandlerThread继承自Thread,可以说其是一个线程。并且有一个Looper对象进行消息循环。

接下来,我们看一下HandlerThread是如何执行异步任务的。

// 创建HandlerThread对象,参数为该线程的Name
HandlerThread handlerThread = new HandlerThread("work");
// 调用HandlerThread的start()函数开启线程
handlerThread.start();

// 通过调用HandlerThread的getLooper()函数得到Looper对象,并创建Handler用于执行异步任务
Handler workHandler = new Handler(handlerThread.getLooper(), new WorkHandlerCallback());

// 在主线程中创建Handler,用于更新UI(主线程中有一个Looper)
Handler uiHandler = new Handler(new UIHandlerCallback());

// 发送一个异步任务的消息
Message msg = workHandler.obtainMessage();
msg.what = 0x01;
workHandler.sendMessage(msg);

private class WorkHandlerCallback implements Handler.Callback {

       @Override
       public boolean handleMessage(Message msg) {
         switch (msg.what) {
                case 0x01:
                    // 开始执行异步任务
                    int sum = 0;
                    for (int i = 0 ; i < 100 ; i ++) {
                        sum += i;
                    }
                    // 异步任务执行成功后,告知UI线程更新
                    Message message = mUiHandler.obtainMessage();
                    message.what = 0x02;
                    message.obj = sum + "";
                    mUiHandler.sendMessage(message);
                    break;
                    default:
                        break;
            }
           return true;
       }
}

private class UIHandlerCallback implements Handler.Callback {

       @Override
       public boolean handleMessage(Message msg) {
         switch (msg.what) {
                case 0x02:
                    // 更新UI
                    textView.setText((String) msg.obj);
                    // 异步任务做完以后,不再需要HandlerThread,就调用quit()函数退出
                    handlerThread.quit();
                    break;
                    default:
                        break;
            }           return false;
       }
}

以上就是HandlerThread的基本用法,是不是很简单呢,通过简单的几步就可以在异步线程中做耗时任务,再也不用担心主线程阻塞了。

注意:

  • HandlerThread也是一个线程,想要其工作就一定要调用start()函数,而且Looper对象是在run()函数中创建的,只有run()函数执行了,才会创建Looper对象进行消息循环。
  • 通过HandlerThread的getLooper()创建的Handler只能用来执行异步任务,因为它不在主线程中,无法进行UI更新,适合做一些耗时任务。

源码

学会了使用HandlerThread,那么就不想知道它的内部是如何实现的吗?反正我是挺想的,也不知道你想不想,咱也不知道,咱也不敢问,有兴趣的童鞋就和我一起探其究竟吧。

public class HandlerThread extends Thread {
    // 线程优先级
    int mPriority;
    // 线程id
    int mTid = -1;
    // 该线程所持有的Looper对象
    Looper mLooper;
    // 线程内部的Handler,可以通过getThreadHandler()函数直接获取使用
    private @Nullable Handler mHandler;
    
    // 拥有一个参数的构造函数
    // 传入参数为线程名称,具有默认线程优先级
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    // 拥有两个参数的构造函数
    // 传入的参数name是线程名称,参数priority是线程优先级(线程优先级详见Process类)
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    // 该函数为空实现,子类可以重写该函数,在Looper进行消息循环之前调用
    protected void onLooperPrepared() {
    }

    // 调用该线程的start()函数后,run()函数会被执行
    // run()函数可以说是HandlerThread的核心,该函数内部会创建Looper进行消息循环
    @Override
    public void run() {
        // 获取该线程的id
        mTid = Process.myTid();
        // 为该线程创建Looper
        Looper.prepare();
        // 通过持有同步锁机制得到该线程的Looper对象
        // 然后调用notifyAll()函数通知getLooper()函数Looper对象已经创建完成。
        synchronized (this) {
            // 获取该线程的Looper对象
            mLooper = Looper.myLooper();
            // 唤醒在当前对象监视器上等待的所有线程
            notifyAll();
        }
        // 设置线程优先级
        Process.setThreadPriority(mPriority);
        // 调用onLooperPrepared()函数,子类可以在消息循环之前做一些准备工作
        onLooperPrepared();
        // 开始消息循环
        Looper.loop();
        mTid = -1;
    }
    
    // 获取该线程的Looper对象
    public Looper getLooper() {
        // 如果该线程不是isAlive,则直接返回null
        if (!isAlive()) {
            return null;
        }
        
        // 通过持有同步锁机制判断当前是否创建了Looper对象
        // 如果该线程没有start,即还没有创建Looper对象
        // 则调用wait()函数,使当前对象上的线程进入等待,就是等待run()函数中的notifyAll()函数执行
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    // 可以直接获取一个Handler对象
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            // 创建Handler实例,参数为该线程的Looper
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    // 退出消息循环,效率高,但不是线程安全的
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            // 调用Looper对象的quit()函数退出消息循环,
            // 内部调用的是MessageQueue的quit(boolean safe)函数,传入参数为false
            looper.quit();
            return true;
        }
        return false;
    }

    // 退出消息循环,效率低,但是线程安全的
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            // 调用Looper对象的quitSafely()函数退出消息循环
            // 内部调用的是MessageQueue的quit(boolean safe)函数,传入参数为true
            looper.quitSafely();
            return true;
        }
        return false;
    }

    // 获取当前线程id
    public int getThreadId() {
        return mTid;
    }
}

总结

分析了HandlerThread的源码后,总结为以下几点:

  • HandlerThread有两个构造函数,分别是线程名称或线程名称和线程优先级。
  • HandlerThread的本质就是一个线程,但不同于线程,它的核心就是run()函数,它在run()函数内部创建一个Looper对象进行消息循环,使通过该Looper创建的Handler实例运行在该线程中。为了避免getLooper()获取到的Looper对象为空,采用了同步锁机制,即在getLooper()函数中若mLooper为null,则让当前对象上的线程进入等待,直到run()函数中创建好Looper对象后,唤醒在当前对象监视器上等待的所有线程,即getLooper()函数继续执行。
  • HandlerThread内部提供了一个Handler,其创建方式也是通过getLooper()函数获取该线程的Looper对象进行创建,不过该Handler只有在外部调用getThreadHandler()时才会进行创建。
  • HandlerThread有两种退出方式,一种不是线程安全的,但是效率高;一种是线程安全的,但是效率低;其最终调用的都是MessageQueue的quit(boolean safe)函数。
  • HandlerThread和Thread一样,都是调用start()函数开启线程。
  • HandlerThread的构造函数需要传入线程名称,而Thread的构造函数则不需要。
  • 继承自Thread的子类需要重写其run()函数,在其中执行任务;而HandlerThread的子类可以重写 onLooperPrepared()函数,在创建Looper对象之前做准备工作。

相关文章

网友评论

    本文标题:详解HandlerThread

    本文链接:https://www.haomeiwen.com/subject/cyganqtx.html