浅析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也很不错哦!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容