Android多线程:HandlerThread的原理及使用

前言

在Andorid实现多线程的方式中, HandlerThread 的使用并不常见,最近开始扎实Android基础,我们都知道,若是在子线程中创建Handler实例并调用 sendMessage() 方法时,子线程由于并不会创建 LopperMessageQueue 对象,等同于消息没有入队(MessageQuue),消息也无法实现出队循环(Looper),故在子线程发送的消息任务无法执行,这时候需要调用方法 Looper.prepare()和Looper.loop() 实现消息的入队、出队、循环分发给指定的Handler。

为了解决这个问题,Android封装了自己的HandlerThread,在内部调用方法 Looper.prepare()和Looper.loop() ,方便了开发人员的使用。

下面我们来揭开HandlerThread的神秘面纱:

1.是什么

HandlerThread是Android封装好的异步消息处理类,其原理即是继承自Thread并封装了Handler

2.为什么

  • 保证多线程并发需要更新UI线程时的线程安全
  • 不需要使用任务线程(继承自Thread)+ Handler的复杂组合,方便了开发人员使用创建新线程与其他线程通信的过程。

3.怎么做

原理:继承的Thread类 + 封装的Handler类

  • 继承的Thread类:快速创建一个带有Looper的工作线程
  • 封装的Handler类:快速创建Handler与其他线程通信

4.使用步骤

1)创建ThreadHandler实例对象mThreadHandler
2)开启线程mThreadHandler.start()
3)创建工作线程的Handler实例,workHandler,并实现handleMessage方法
4)使用工作线程workHandler向工作线程的消息队列发送消息
workHandler.sendMessage(msg);
5)停止线程
mThreadHandler.quit()
mThreadHandler.quitSafely()

  • demo 示例完整代码
    模拟窗口卖票,每个窗口有6张票,工作线程workHandler发送一条消息则开启一个窗口开始买票
public class HandlerThreadActivity extends AppCompatActivity {

    private int j = 1;
    private String note;
    private TextView note_text;
    private Handler workHandler = new Handler();
    private HandlerThread handlerThread;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        initView();
        initHandlerThread();
    }

    private void initHandlerThread() {

        //step1: 创建HandlerThread实例,传入的参数为线程名称
        handlerThread = new HandlerThread("based on yourself");

        //step2: 手动调用start()开启线程
        handlerThread.start();

        //step3:
        //创建Handler,关联HandlerThread的Looper
        //复写handleMessage根据消息更新UI布局,处理消息的线程即是创建的线程handlerThread
        workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(final Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        break;
                    case 2:
                        for (int i = 1; i < 7; i++) {
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            note = msg.obj + "卖出" + i + "张票";
                            note_text.setText(note);
                            Log.d("workHandler----", note);
                        }
                        break;
                }
            }
        };
    }

    private void initView() {
        note_text = findViewById(R.id.note_text);
        Button startThread = findViewById(R.id.start_thread);
        startThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //step4: 点击一次Button,工作线程workHandler向工作线程队列发送消息
                Message msg = Message.obtain(); //不用new一个Message,采用obtain
                msg.what = 2;
                msg.obj = "窗口" + j;
                workHandler.sendMessage(msg);
                j++;
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //step5: 停止handlerThread
        handlerThread.quit(); //结束线程,效率高
        handlerThread.quitSafely(); //结束线程
    }
}
  • 效果展示

    img

Log日志

img

  • 结论
    从demo展示中和打印的Log中看到,第一次点击Button窗口1开始卖票,窗口1卖第3张票时,再次点击Button,HandlerThread不会停止当前窗口卖票,而是等待当前卖完6张票之后后再开启窗口2执行卖票任务。

5.应用场景

使用HandlerThread处理本地的 I/O操作(数据库,文件,SharePreferences),推荐使用postAtFrontOfQueue(),快速将读取操作加入队列的前端执行,必要时更新主线程UI。示例场景,从数据库读取数据显示在ListView中。

6.总结

  • HandlerThread 继承自 Thread,在复写的 run() 方法中,调用了Looper.prepare()和Looper.loop(),方便在创建的子线程中使用Handler时需要自己手动调用Looper.prepare()和Looper.loop()。
  • HandlerThread 内部处理消息队列时是按顺序 串行执行 ,即在处理完一条消息任务后再处理下一条消息任务,不适合处理 网络请求 需要并行处理的耗时任务,更适合处理本地的需要按序执行的 I/O操作
  • 缺点:若其中一条任务处理时间过长,仍然需要等待此条任务处理完成之后才处理下一条任务,会导致后续任务延时执行,造成 线程阻塞 的现象。
  • 一个线程对应一个Looper
  • Looper.prepare() 自动创建一个Looper对象,Looper创建时,自动创建MessageQueue对象,并赋值mThread为当前Thread,ThreadLocal中存入创建的Looper对象
  • Looper.loop() 开启消息循环

思考

  • 什么是线程阻塞,造成原因有哪些?
  • HandlerThread需要手动调用quit()/quitSafely(),为什么新建的子线程不用手动调用释放Looper和MessageQueue呢?

链接:https://www.jianshu.com/p/ea66c76b66f5

原文地址:https://www.cnblogs.com/lixuejian/p/15163026.html