面试总结(1):HandlerThread

前言#

面试总是让人既紧张又兴奋,尤其是百度这样大公司,总是想证明自己的能力,害怕但是又期望能问出自己的不足,真是一次难得面试体验,所以回来赶紧把没回答出来的知识点学习一下。

Handler和Thread之间的关系和用法,我们都很熟悉,那么你了解HandlerThread吗?

正文#

当我听到这个问题的时候,是有一点蒙的,因为我确实没用过,可能是平时看到或者听到过。那什么是HandlerThread呢?

顾名思义,就是一个含有Handler的Thread,他是一个线程。子线程就可以用用来处理异步任务,耗时操作。

用法:

HandlerThread teacherThread = new HandlerThread("teacher");
teacherThread.start();
teacherHandler = new Handler(teacherThread.getLooper());

很简单,创建一个HandlerThread对象,然后把这个线程的Looper绑定到Handler里面去,那么Handler里的任务就会在这个Thread中执行。

<h2>好处</h2>

经过我仔细的思考和试验,主要有以下几点:

1、模块划分清晰,把耗时任务细分,便于维护管理。
2、复用性很好,避免创建临时性的线程,消耗系统资源。
3、有利于多线程通信。
4、让UIhandler更专注于更新界面,优化界面的流畅度。

<h2>Demo</h2>

多说无用,赶紧写点东西实际感受一下。

我们来模拟一个考场的情景:

/**
* 线程池
 * */
private ExecutorService threadPool = Executors.newSingleThreadExecutor();

/**
* 更新UI的Handler
* */
private Handler mainHandler = new Handler(Looper.getMainLooper()){
   @Override
   public void handleMessage(Message msg) {
      switch (msg.what){
         case 0:
             textView.setText("考试开始,老师开始发放试卷...");
             break;
         case 1:
             textView.setText("发放试卷完毕,学生开始答题...");
             break;
         case 2:
             textView.setText("考试时间到,老师收试卷...");
             break;
         case 3:
             textView.setText("考试结束...");
             break;
         }
   }
};

/**
* 线程池开启任务
*/
threadPool.execute(new Runnable() {
    @Override
    public void run() {
       mainHandler.sendEmptyMessage(0);
       // 老师发卷
       ...
       mainHandler.sendEmptyMessage(1);
       // 学生答题
       ...
       mainHandler.sendEmptyMessage(2);
       // 答题结束
       ...
       mainHandler.sendEmptyMessage(3);
    }
});

大概就是这样一个流程,如果是一般写法,首先创建一个线程池开启线程,线程里面开始处理各种各样的任务,到一定的阶段,就更新UI的显示状态。

但是如果这个线程任务变得越来越复杂,代码越来越长,维护性也会变差,所以就得重新设计一下这个任务。

从面向对象的编程思想来看,首先我们可以把这个任务划分为两个角色:

老师:发送试卷,回收试卷。
学生:答题。

老师发送完试卷,通知学生答题,学生答题结束,通知老师回收试卷。

ok,分析结束,就先实现老师和学生的Handler:

teacherHandler = new Handler(teacherThread.getLooper()){
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what){
              // 接收到开始指令,老师开始发放试卷
              case 0:
                 // 更新UI
                 mainHandler.sendEmptyMessage(0);
                 sleep();
                 // 发送试卷结束,学生开始答题
                 studentHandler.sendEmptyMessage(0);
                 break;

                    // 学生答题结束,老师开始收试卷
              case 1:
                 // 开始收卷
                 mainHandler.sendEmptyMessage(2);
                 sleep();
                 // 考试结束
                 mainHandler.sendEmptyMessage(3);
                 break;
         }
    }
};

studentHandler = new Handler(studentThread.getLooper()){
   @Override
   public void handleMessage(Message msg) {
       // 接收到开始指令,老师开始发放试卷
       mainHandler.sendEmptyMessage(1);
       sleep();
       // 发送试卷结束,学生开始答题
       teacherHandler.sendEmptyMessage(1);
    }
};

// mainHandler 代码不变
...

老师和学生通过handleMessage()来接受操作指令,内部实现具体的功能逻辑。

搞定了两个角色类,剩下的就是onCreate中去初始化线程了:

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    /**
     * 处理老师和学生操作的线程
     * */
    private HandlerThread teacherThread, studentThread;

    private Handler teacherHandler, studentHandler;
    
    /**
     * 更新UI的Handler
     * */
    private Handler mainHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    textView.setText("考试开始,老师开始发放试卷...");
                    break;
                case 1:
                    textView.setText("发放试卷完毕,考试开始答题...");
                    break;
                case 2:
                    textView.setText("考试时间到,老师收试卷...");
                    break;
                case 3:
                    textView.setText("考试结束...");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);

        initHandlerThread();

        teacherHandler.sendEmptyMessage(0);
    }

    /**
     * 初始化HandlerThread
     * */
    private void initHandlerThread() {
        Log.e("lzp", "mainLooper:" + getMainLooper().toString());

        teacherThread = new HandlerThread("teacher");
        teacherThread.start();
        Log.e("lzp", "teacherLooper:" + teacherThread.getLooper().toString());
        teacherHandler = new Handler(teacherThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    // 接收到开始指令,老师开始发放试卷
                    case 0:
                        // 更新UI
                        mainHandler.sendEmptyMessage(0);
                        sleep();
                        // 发送试卷结束,学生开始答题
                        studentHandler.sendEmptyMessage(0);
                        break;

                    // 学生答题结束,老师开始收试卷
                    case 1:
                        // 开始收卷
                        mainHandler.sendEmptyMessage(2);
                        sleep();
                        // 考试结束
                        mainHandler.sendEmptyMessage(3);
                        break;
                }


            }
        };

        studentThread = new HandlerThread("student");
        studentThread.start();
        Log.e("lzp", "studentLooper:" + studentThread.getLooper().toString());
        studentHandler = new Handler(studentThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                // 接收到开始指令,老师开始发放试卷
                mainHandler.sendEmptyMessage(1);
                sleep();
                // 发送试卷结束,学生开始答题
                teacherHandler.sendEmptyMessage(1);
            }
        };
    }

    private void sleep(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

为了便于显示,我没有把teacherHandler和studentHandler 两个类独立出来,如果独立出来,MainActivity的代码将会变得更加简洁易看。

我还打印了UI线程的Looper,和两个HandlerThread的Looper:

《面试总结(1):HandlerThread》 这里写图片描述

三个线程的Looper是不一样的,的确我们的teacherHandler和studentHandler是运行在新线程中的。而且老师和学生之间的通信变得很简单,直接通过Message把需要的数据携带进去就OK了。

使用结束记得退出HandlerThread:

public void release(){
   getLooper().quit();
}

总结#

HandlerThread的使用方法就是这个样子,他不需要我们去专注Thread的管理,把更多的精力放在实现handler的功能上,并且handler最大的优势就是便于线程之间的通信,上面的例子我觉得如果按照实际开发可能并不是很恰当,相信在以后真真正正的使用到了HandlerThread,我对他的理解会更加清晰。

我对demo重新修改了一下,尽可能的让代码维护性提高,耦合性降低,需要的朋友可以下载。

Demo下载链接

    原文作者:珠穆朗玛小王子
    原文地址: https://www.jianshu.com/p/366f3348147e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞