zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    XTU 1250 Super Fast Fourier Transform
    XTU 1249 Rolling Variance
    XTU 1243 2016
    CodeForces 710A King Moves
    CodeForces 710B Optimal Point on a Line
    HDU 4472 Count
    并查集
    高精度四件套
    Kruskal最小生成树
    [蓝桥杯]翻硬币(贪心的详解附带题目测试链接)
  • 原文地址:https://www.cnblogs.com/lixuejian/p/15163026.html
Copyright © 2011-2022 走看看