zoukankan      html  css  js  c++  java
  • 看完这篇。再也不怕被问 HandlerThread 的原理

    HandlerThread是什么

    image-20200728000754030

    官网介绍

    A Thread that has a Looper. The Looper can then be used to create Handlers.
    Note that just like with a regular Thread, Thread.start() must still be called.
    

    翻译:

    HandlerThread,持有一个可用来构建Handlers的Looper,像一个常规的线程类,必须要调用start()才能正常工作。

    HandlerThread的父类是Thread,所以HandlerThread的本质还是一个线程,但是它并非像Thread需要在run代码块内执行耗时的任务,HandlerThread是通过搭配外部的Handler分发处理消息执行任务的,可以很简单地返回和管理子线程的一个Looper对象。

    HandlerThread常见的使用场景

    有两个耗时任务A、B,任务B的执行需要A执行结果,即 A,B不可以并行执行,而是要串行按顺序执行任务。

    下面给出模拟这种场景HandlerThread使用的实例代码:(代码可直接复制运行,有点长有点渣,见谅)

    getResultA()doThingB(),模拟了A,B两个不可以并行执行的耗时任务。

    taskHandlerHandler子类的实例,通过获取handlerThread开启后创建的Looper,串行发送了消息A,消息B,Looper自然也是先取出消息A,给taskHandler.handleMessage处理,再取出消息B完成了串行执行耗时任务A、B。

    完成了串行执行耗时任务A、B。

    public class HandlerThreadActivity extends AppCompatActivity {
    
        private Handler taskHandler;
        private HandlerThread handlerThread;
    
        private static String resultA;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            handlerThread = new HandlerThread("HandlerThread-1");
            //!!关键:HandlerThread需要调用start开启线程,否则持有Looper为null
            handlerThread.start();
            //使用handlerThread线程持有的Looper构建 taskHandler实例
            taskHandler = new TaskHandler(handlerThread.getLooper());
            //发送消息A
            Message msgA = Message.obtain();
            msgA.what = 0;
            msgA.obj = "Task-A";
            taskHandler.sendMessage(msgA);
            //发送消息B
            Message msgB = Message.obtain();
            msgB.what = 1;
            msgB.obj = "Task-B";
            taskHandler.sendMessage(msgB);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //手动退出HandlerThread的Looper
            handlerThread.quitSafely();
        }
    
        @WorkerThread
        private static String  getResultA() {
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "DMingO";
        }
    
        @WorkerThread
        private static void  doThingB() {
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " :"+resultA + " 's blog");
        }
    
        private static class TaskHandler extends Handler{
    
            public TaskHandler(@NonNull Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 0:
                        //执行耗时任务 getResultA()
                        resultA = getResultA();
                        break;
                    case 1:
                        if(! "".equals(resultA)){
                            //拿到任务A的返回结果才能执行任务B
                            doThingB();
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }
    

    运行结果:

    可以看到TaskHandler.handleMessage是运行在HandlerThread这一个线程上,归根结底还是HandlerThread把它线程的Looper给了TaskHandler实例

    I/System.out: HandlerThread-1 :DMingO 's blog
    

    HandlerThread起的最大作用就是 很简便地提供了一个可设置命名和优先级的线程的Looper对象

    HandlerThread源码分析

    通过最简单的使用入手分析HandlerThread作为一个线程,提供一个子线程的Looper的背后原理:

            handlerThread = new HandlerThread("HandlerThread-1");
            handlerThread.start();
    		taskHandler = new TaskHandler(handlerThread.getLooper());
    

    看下getLooper()葫芦里什么药:

        public Looper getLooper() {
            //isAlive()判断当前线程是否已经开启
            //如果线程未开启(未调用HandlerThread.start),会返回null
            //所以必须执行了start()后,才能调用 getLooper(),否则会有空指针异常
            if (!isAlive()) {
                return null;
            }
            
            // 如果线程已开启但Looper未被创建,会进入同步代码块,阻塞-->直到Looper被创建
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        //mLooper==null-->线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            //确保 返回的mLooper不为null
            return mLooper;
        }
    

    通过分析,getLooper() 方法确保可以返回一个HandlerThread线程持有的且非空的Looper对象。前提是HandlerThread线程已经开启。如果线程已开启但Looper未被创建,线程会阻塞,直到Looper被创建了。

    那么在哪个方法,mLooper才被赋值,Looper对象才被创建呢?还记得 getLooper() 方法在最初如果发现线程未被开启,直接就返回null,这不就说明HandlerThread线程的开启与否与它的Looper创建,这两者息息相关嘛。

    那就再看下HandlerThread的run()方法有什么名堂:

        @Override
        public void run() {
            mTid = Process.myTid();
            //创建此线程的Looper和MessageQueue
            Looper.prepare();
            synchronized (this) {
                //给 mLooper 赋值
                mLooper = Looper.myLooper();
                //此时mLooper!=null-->取消线程阻塞
                notifyAll();
            }
            //为线程设置mPriority优先级
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            //开始运行 Looper
            Looper.loop();
            mTid = -1;
        }
    

    开启HandlerThread线程后,会创建此线程的Looper和MessageQueue,设置线程优先级,开始Looper的循环取消息。

    欸,HandlerThread这名字,它的Handler又去哪儿了呢?emmmm目前被隐藏了:

        private @Nullable Handler mHandler;
        
        /**
         * 返回与此线程相关联的一个Handler实例
         * @hide 目前此方法是被隐藏的,无法正常直接调用
         */
    	@NonNull
        public Handler getThreadHandler() {
            if (mHandler == null) {
                mHandler = new Handler(getLooper());
            }
            return mHandler;
        }
    

    可以看出,HandlerThreadmHandler的实例化是属于懒加载方式,只能在外界调用 getThreadHandler()的时候,才会对mHandler判空&进行实例化。实例化时传入的Looper对象自然是HandlerThread这一线程创建的Looper。因此若Looper还未被初始化,方法也会一直阻塞直到Looper创建完成,也需要线程已开启。

    毫无疑问,mHandler 也自然也是只能去处理HandlerThread这一个线程的消息。

    可以看出HandlerThread这个类与Looper的关系是密不可分的,自然也会有退出Looper的办法,看以下两个方法:

        public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
        }
        
        public boolean quitSafely() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quitSafely();
                return true;
            }
            return false;
        }
    

    是不是觉得高度相似,而这两个方法相同的地方是:

    • 如果线程未开启时(looper自然也为null),返回 false
    • 如果线程已经开启了,则会调用 Looper类的quit() / quitSafely()方法,并返回 true

    不同的是,根据官方描述,建议使用quitSafely(),这会允许消息队列中还在排队的消息都被取出后再关闭,避免所有挂起的任务无法有序的被完成。

    HandlerThread分析总结

    HandlerThread 本质是一个Thread,却和普通的 Thread很不同的是:普通的 Thread 主要被用在 run 方法中执行耗时任务,而 HandlerThread 在线程开启后(run方法中)创建了该线程的Looper和消息队列,外界Handler可以很方便获取到这个Looper,搭配执行耗时任务,适合串行执行耗时任务等场景。

  • 相关阅读:
    九宫格代码
    数组相关
    动画设置模式大全
    extjs 学习小窍门
    linux mysql root密码重置
    (ExtJs 3.4)Ext.Ajax.request的同步请求实现
    Ext中renderer用法及参数
    Extjs 3.4 复选框的,默认选中 ,禁用,(纯属于自己代码中需要,总结!)
    linux部署安装nginx
    报表报500,tomcat日志显示报错:Can't connect to X11 window server using 'localhost:10.0' as the value of the DISPLAY variable
  • 原文地址:https://www.cnblogs.com/DMingO/p/13388696.html
Copyright © 2011-2022 走看看