Android HandlerThread 分析

  • Post author:
  • Post category:其他




前言

在上一篇文章中我们对Handler、Looper、MessageQueue的内部实现原理以及实现。我们大部分的时候都是将消息从子线程发送到主线程来进行处理。就是说 Handler的 handleMessage 方法是在主线程中执行。下面我将介绍的这个类则完全相反:在主线程发送消息但是在子线程里面处理消息,也就是说 handleMessage 是在子线程中执行的,这个时候我们就不能在这个方法执行 UI的更新操作了。



示例代码

public class MainActivity extends AppCompatActivity {

    private HandlerThread handlerThread;
    private MyHandler mMyHandler;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //首先我们
        handlerThread = new HandlerThread("handlerThread1");
        handlerThread.start();
        mMyHandler = new MyHandler(handlerThread.getLooper());
        findViewById(R.id.next).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击按钮的时候我们就发送一条消息出去
                Message msg = mMyHandler.obtainMessage();
                msg.what = 1;
                mMyHandler.sendMessage(msg);
            }
        });
    }
}

private class MyHandler extends Handler {

    public MyHandler(Looper looper) {
        super(looper);
    }

    //我们知道 Message的消息分发是在 Looper中的 loop方法中,最后调用的是 Handler的 handleMessage方法
    //但是现在该方法确实在 子线程中执行的,也就是现在再也不能执行 UI操作了
    public void handleMessage(@NonNull Message msg) {
    //注意:刚方法是在子线程里面执行的,所以可以执行耗时的操作,但是不能执行 UI 操作
        switch (msg.what) {
        }
    }
}

上面就是我们对 HandlerThread 基本使用,首先我们创建一个HandlerThread 对象,然后调用start方法,最后我们需要创建一个 Handler但是我们需要把 HandlerThread的Looper对象传入到Handler里面。



源代码分析

  • 构造方法
public class HandlerThread extends Thread {

    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
}

从代码中我们可以看到 HandlerThread继承Thread,在构造方法需要传入线程的名字以及优先级,默认线程优先级是0,接着调用 start 方法的话我们也可以猜测的出来就是启动一个线程。

  • 启动线程(start )
public class HandlerThread extends Thread {
    public void run() {
       //获取当前线程的标识符,保存起来
        mTid = Process.myTid();
        //创建一个 Looper对象,同时将 Looper对象保存到当前线程的 threadLocals 中
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        //设置线程的优先级
        Process.setThreadPriority(mPriority);
        //这里对外暴漏一个接口回调,该回调用于对外暴漏 Looper对象已经创建成功了,你可以使用 Looper对象了。
        onLooperPrepared();
        //注意:loop 是一个阻塞的方法,也就是说如果我们没有调用MessageQueue的销毁方法,下面的代码是不会执行的。
        Looper.loop();
        //如果我们销毁了 MessageQueue的话,那么才能走到这一步,重新设置线程的标识符为 -1 
        mTid = -1;
    }
}

上面的代码我们是不是非常似曾相识呀,我在上篇文章中对

ActivityThread的 main 方法

分析的时候里面是不是就有这么一段代码:首先创建 Looper对象,然后将对象保存到 ThreadLocal中的,本质还是保存到线程的 threadLocals 中。我们现在根据这几次的代码分析得出:**Looper 对象在哪个线程创建,Handler的 dispatchMessage或者handleMessage 就是在哪个线程中执行。

在之前的时候我们分析过Looper类中的 loop方法,其本质就是

不断的从 MessageQueue中去获取消息,当获取到消息以后就调用 Message的target(Handler类型)的dispatchMessage 来进行消息转发的,现在 loop 方法是在子线程中运行的,所以 dispatchMessage 和 handleMessage也一样是在子线程中运行的。

  • 发送消息

发送消息还是跟我们最开始使用 Handler的时候一样,直接通过调用 sendMessage来实现的,也就是把消息插入到消息队列里面去。然后等待消息队列把消息取出来然后调用 Handler的 dispatchMessage进行分发。但是

这个怎么从消息队列取消息,以及线程的通信并没有这么简单的,后面还会单独写一篇文章来深入分析 MessageQueue 的

  • HandlerThread 的销毁

注意:

我们在使用完 HandlerThread 以后需要 调用quit 方法来退出,以免内存泄漏

//这里调用 Loop的quit 方法就是调用 MessageQueue的quit(false) 方法
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}

//这里的安全退出无非就是调用 MessageQueue的 quit(true)方法
public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely();
        return true;
    }
    return false;
}

public class Looper {
    public void quitSafely() {
        mQueue.quit(true);
    }
    
    public void quit() {
        mQueue.quit(false);
    }
}

从HandlerThread退出方法我们可以看出最后还是调用 MessageQueue的 quit方法。Looper类只是对 MessageQueue的一个包装而已。

  • HandlerThread、Thread、Handler的三者区别

当我们熟练掌握了某个类以后就需要去了解其内部的实现原理,当我们已经把某个类的内部实现原理分析了以后就需要对其他类似的类进行一个更好的分析。

  1. Handler其实就是一个包装类,处理消息的方法取决于 Looper对象创建是在哪个线程里面。
  2. Thread 就是一个线程类,专门用于处于耗时、并发的任务,但是 run 方法执行完毕线程也就消失了。如果不做特殊处理的话 run 方法里面只能执行一次
  3. HandlerThread 其本质就是一个线程,不过与其他线程不同的区别就是,如果你不主动退出的话,该线程是不会自动退出的,除非出现异常或者是其他问题。我们也不在 run 方法里面处理耗时的任务,而是在 Handler的 dispatchMessage或者是 handleMessage来处理耗时的操作。
  4. HandlerThread 其实还可以用于定时执行任务功能,类似 TimerTask 功能。具体实现:**可以在 handleMessage 方法后面再重新发送一个 Message,这样子就形成了一个循环、定时的功能。**如果之前大家用过阻塞队列的话,那么我们对该类就会有非常深刻的理解。

    我们可以把 MessageQueue比作是一个阻塞队列,然后 Handler 发送消息就是往阻塞队列里面添加任务,然后Looper 就相当于 for循环不断的从阻塞队列中获取任务在 run 方法中去执行,只是 HandlerThread执行耗时任务是在Handler的 handleMessage 方法中



总结

通过上面的代码分析我们可以对Handler、Looper有了一个更深刻的理解,该类**本质就是一个线程只不过在线程里面加入了一个死循环,但是我们又不能让死循环一直执行(如果一直执行死循环的那相当于会把CPU的资源全部耗完),那么我们就想办法把当前类阻塞,如果有新的任务添加进来的话,那么就直接唤醒线程去执行任务,如果队列中没有任务的话那么线程在继续进行阻塞。只不过Looper方法中的阻塞(通过Linux的epoll 机制实现)跟Java线程阻塞(通过调用Object 累的wait方法来实现)的方式不一样的。**总体来说该类设计的还是比较简单,但是功能却非常的强大,不得不佩服 Google工程师的实力。



版权声明:本文为u012417984原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。