浅析HandlerThread

浅析HandlerThread

背景

首先呢?HandlerThread面试的时候有的会问,但是面试官不直接问你是否知道HandlerThread以及用途和实现?面试官会问你:面试必问的一个题目:
handler的消息机制等一系列问题,如果你说的还算可以,那么问题来了?
接下来会问你假如在一个子线程(工作线程)中怎么使用handler?嘻嘻 ,如果你知道handler的消息机制,那么这个问题很好回答了,代码如下:

  private final class WorkThread extends Thread {

        private Handler mHandler;

        public Handler getHandler() {
            return mHandler;
        }
          public void quit() {
            mHandler.getLooper().quit();
        }
        @Override
        public void run() {
            super.run();
            //创建该线程对应的Looper,
            // 内部实现
            // 1。new Looper()
            // 2。将1步中的lopper 放在ThreadLocal里,ThreadLocal是保存数据的,主要应用场景是:线程间数据互不影响的情况
            // 3。在1步中的Looper的构造函数中new MessageQueue();
            //其实就是创建了该线程对用的Looper,Looper里创建MessageQueue来实现消息机制
            //对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
                }
            };
            //开启消息的死循环处理即:dispatchMessage
            Looper.loop();
            //注意这3个的顺序不能颠倒
            Log.d("WorkThread", "end");
        }
    }

这里我们可以测试下在另外一个子线程里通过WorkThread的getHandler给它发消息看下结果:

 public void send(View view) {
        new SendThread(mWorkThread.getHandler()).start();
    }

    private final class SendThread extends Thread {
        private Handler mHandler;

        SendThread(Handler handler) {
            this.mHandler = handler;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 3; i++) {
                mHandler.sendEmptyMessage(0x1);
                SystemClock.sleep(1000);
            }
        }
    }

log:可以看到收到了消息并打印了是在非主线程

02-15 10:45:19.290 22660-22738/com.gxz.study D/WorkThread: false,1
02-15 10:45:20.290 22660-22738/com.gxz.study D/WorkThread: false,1
02-15 10:45:21.291 22660-22738/com.gxz.study D/WorkThread: false,1

这里有同学问 小伙子,你这里不对, Log.d(“WorkThread”, “end”);没打印。是的,Looper.loop();为阻塞函数,只有当调用mHandler.getLooper().quit()或者quitSafely()方法后,loop才会中止.那2个方法有毛区别呢?

  1. 当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

  2. 当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

  3. 无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

  4. 需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。

HandlerThread的用法

前面扯远了,但是你必须知道的东西。那就有同学问了好麻烦啊上面的,假入我以后在工作中用的话我还得注意 Looper.prepare(); new mHandler(); Looper.loop();的顺序。呵呵。。。
哈哈,同学,这个HandlerThread就是解决这个的。google工程师早知道你会这么问。
我们先看怎么用,我们把上面的测试例子用HandlerThread实现然后在分析内部原理。

//1.初始化,参数为名字,也就是线程的名字,后面我们会结合源码来看
 mHandlerThread = new HandlerThread("WorkThread");
 //必须调用start方法,因为HandlerThread继承自Thread来启动线程
 mHandlerThread.start();
 //初始化Handler,只是传递了一个mHandlerThread内部的一个looper
 mHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
            }
        };
//2.使用
  public void send(View view) {
        new SendThread(mHandler).start();
    }

    private final class SendThread extends Thread {
        private Handler mHandler;

        SendThread(Handler handler) {
            this.mHandler = handler;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 3; i++) {
                mHandler.sendEmptyMessage(0x1);
                SystemClock.sleep(1000);
            }
        }
    }
 //3.别忘记释放,停止Looper消息循环,内部还是调用了looper的quit,及时释放防止内存泄漏哦!!
   mHandlerThread.quit();
结果(一毛一样哦):
02-15 11:11:24.738 13536-13787/com.gxz.study D/WorkThread: false,1
02-15 11:11:25.739 13536-13787/com.gxz.study D/WorkThread: false,1
02-15 11:11:26.740 13536-13787/com.gxz.study D/WorkThread: false,1

因此

  1. 如果我们有耗时的任务处理可以通过HandlerThread获取looper,looper进而构造Handler,然后通过Handler的post(Runnable r)在handleMessage里进行处理耗时处理。
  2. 很方便进行线程间的通信。

HandlerThread的源码分析

  • 原理:HandlerThread其实是extends Thread,内部的run方法调用了Looper.prepare()方法和 Looper.loop();哈哈,和我们之前写的第一种方法一样。
public class HandlerThread extends Thread {
    int mPriority;//线程优先级
    int mTid = -1;//线程id
    Looper mLooper;//Looper对象

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
  • 对于线程,我们还是主要看run方法,源码很短,直接全部贴了。我们清楚的看到了Looper.prepare和Looper.loop的创建,这里和最开始说的一样。
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        //后面会分析
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

但是这多了一个onLooperPrepared()方法,这里可以根据名字看出来,当Looper.prepare()创建完后,我们可以做一些初始化的东西,这当然是在子线程里,我们看下这个方法的实现。空实现,如果你要做些初始化的准备工作可以extends HandlerThread重写onLooperPrepared方法即可。

 /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }
  • 退出/结束方法:quit()方法或quitSafely()方法。实际上是调用了looper.quit方法和looper.quitSafely方法,上文中的区别也说到了。
 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;
    }
  • 最后还有一点,就是在上面的例子中我们构造Handler的时候传入了Looper,通过HandlerThread的getLooper(),源码如下。这里如果线程没有start活着一些其它原因,该线程没有处于一种存活的状态会返回null,
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

该函数有可能阻塞,目的是为了防止获取的looper对象为空,使用了wait方法,并且使用了局部synchronized,锁住了当前的对象,那什么时候唤醒等待呢?当然是初始化完该线程关联looper对象的地方,也就是run方法。也就是你构造完HandlerThread必须调用start方法。对于synchronized和notifyAll和wait一些线程同步处理的操作,可以参考一些资料。

 synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }

结束

这里我们学习了HandlerThread类的简单用法以及内部源码以及实现原理。
同时也介绍了一些面试必问handler的消息机制。so,当再有面试官问你文章最开始的在一个子线程(工作线程)中怎么使用handler的问题,你可以说下怎么使用,同时最后说一个android提供的类-HandlerThread也很不错哦!!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值