Handler与HandlerThread

来源:互联网 发布:php输出等腰三角形 编辑:程序博客网 时间:2024/04/30 14:30

概述:

        在线程之间通信时,通常会使用到Handler。与之相关联的类有:Message,Looper,MessageQueue。简单讲它们的作用分别为:

        Handler:发送消息并且处理消息的对象。

        Message:Handler接收和处理的对象。

        Looper:每一个线程只能拥有一个Looper对象。它的loop()方法会从MessageQueue中读取消息,并且将读到的消息发送给该消息的Handler进行处理。

        MessageQueue:消息队列,它采用先进先出的方式管理消息(Message)。在Looper对象初始化时会创建MessageQueue的实例。Looper的构造方法如下:

    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

从这里可以看出,在Looper初始化时,会创建一个与之相关联的MessageQueue对象。

ThreadLocal

        在子线程中使用Handler时,需要先调用Looper.prepare(),主要是为当前线程绑定一个Looper对象。主要涉及set()与get()方法。如下:

    public void set(T value) {        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);//获取当前线程的localValues属性。        if (values == null) {            values = initializeValues(currentThread);//return current.localValues = new Values();        }        //经上面两步之后,当前线程的localValues肯定不为null,并且values也为当前线程的localValues        values.put(this, value);//将set的参数设置到localValues属性中。    }

        从中可以看出,一次prepare()只是为当前线程绑定了一个Looper对象。而一个Looper对象又关联的有一个MessageQueue,所以每一个MessageQueue只属性一个线程。所以每一次在新线程使用Looper时必须先调用prepare()。

Looper

        在Looper中,有两个方法最重要:prepare()用来创建Looper对象,并与线程关联的;loop()用于从与Looper对象关联的MessageQueue中取出消息(Message)。

        创建Looper对象,调用它的prepare()即可。代码如下:

    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }

        首先说一下sThreadLocal,它是一个ThreadLocal<Looper>对象。而ThreadLocal是一个很特殊的全局变量,它的全局性只局限于当前的线程,外界所有的线程(包括处于同一个进程中的)都无法访问到它。

        从代码中可以看出,首先从sThreadLocal中取值,如果有值说明当前线程已经被关联过Looper对象,此时就直接抛异常;如果没有,那就新new一个Looper对象,并且设置到sThreadLocal中。通过此种方法就可以保证一个线程只能拥有一个Looper对象prepare()也只做了一件事:在允许的情况下,为当前线程关联一个Looper对象

        又由于一个线程只能关联一个Looper对象,所以一个线程prepare()只能被调用一次。

        loop()方法使用一个死循环来不断取出存储在MessageQueue中的消息,并将消息交给该消息对应的handler进行处理。大体代码如下:

/** * Run the message queue in this thread. Be sure to call {@link #quit()} to * end the loop. */public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}//取出Looper关联的MessageQueue对象final MessageQueue queue = me.mQueue;…for (;;) {//从MessageQueue中取出下一条消息(Message),该方法是阻塞的Message msg = queue.next(); if (msg == null) {// No message indicates that the message queue is quitting.return;}…//将取到的消息进行分发msg.target.dispatchMessage(msg);…msg.recycle();//回收该消息}}
        从中可以看出,loop()用for(;;)进行了一个无限循环,也就是说:这个loop()方法会进行不断地读取消息,直到取到或者MessageQueue被放弃。

        当Looper取到消息时,会调用msg.target.dispatchMessage()(就是发送msg的handler中的dispatchMessage())。由于Looper.loop()是在Looper对象所处的线程中执行的,所以handler.dispatchMessage()的运行线程与Looper对象所属的线程是同一个,而不一定与handler所处的是同一个线程

        如果想获取当前线程中的Looper对象,可以调用Looper.myLooper(),它会直接返回sThreadLocal.get();。

        处理完msg后,会调用msg.recycle()将Message对象进行回收处理。

        因此,Looper#loop()方法中一个Message会完成自己的整个生命周期的后半部分:获取,处理以及回收。

prepare()与prepareMainLooper()

        prepare()为当前线程添加Looper对象,而后者是是为UI线程准备Looper对象的。如下:

    public static void prepare() {        prepare(true);    }    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }    public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }

        从上可以看出,两者调用了相同的方法,只是一个参数为false,一个参数为true。这个参数最终会赋值到MessageQueue#mQuitAllowed中,而它代表着这个MessageQueue能否被打断,简单点就是能否调用Looper#quit()方法。

        从上面代码看出,UI线程中的是无法打断Message循环的,而自己创建的线程却可以。并且prepareMainLooper()是在ActivityThread#main()中调用的,是先于我们所有的线程调用的,调用完毕之后sMainLooper有值,再次调用时就会报错,这也防止了自己在线程中调用prepareMainLooper。

Message

对于一个Message实例来说,包含好几个实例变量。 

        1,what 用户定义的int型消息代码,用于区别不同的消息

        2,obj 随消息传递的对象,用于传递数据

        3,target处理消息的handler。在上面的loop()代码中,就是调用msg.target.dispatchMessage()将消息传递回handler中。

        4,next。记录链表中下一个元素。在使用Message时,系统建议使用obtain(),而不是直接new Message()。这是因为在系统中维护了一个使用过Message的链表结构,每一次obtain时都会从这个链表中获取,防止了重复new Message造成各种内存开销。

        5,sPool 链表结构的表头。

        6,flags当前消息的状态。

        对于Message实例的获取,通常通过handler.obtainMessage()及重载方法完成。在obtainMessage()的源码中可以发现,它里面只是调用了Message.obtain(this),这里的this指的就是调用obtainMessage()的Handler对象。下面看一下obtain()的源码:

    public static Message obtain(Handler h) {        Message m = obtain();        m.target = h;        return m;    }

        这里面直接将Message的target属性设置成了当前的handler。这样在Looper.loop()进行消息分发时就可以找到对象的Handler对象了。再看obtain()方法:

    public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }
        取的时候先从链表的表头开始(sPool代表的就是表头),然后将表头移到第二个元素上。然后将取得元素的next置为null,并将sPoolSize数量减1。当链表中没有缓存的Message对象时,会new一个Message对象。

        在上面的Looper.loop()中,调用Message#recycle()方法进行回收,它经过一个判断之后会调用recycleUnchecked()方法。代码如下:

    void recycleUnchecked() {        // Mark the message as in use while it remains in the recycled object pool.        // Clear out all other details.        flags = FLAG_IN_USE;        what = 0;        arg1 = 0;        arg2 = 0;        obj = null;        replyTo = null;        sendingUid = -1;        when = 0;        target = null;        callback = null;        data = null;        synchronized (sPoolSync) {            if (sPoolSize < MAX_POOL_SIZE) {                next = sPool;                sPool = this;                sPoolSize++;            }        }    }
        该方法中首先将一个Message中的属性都初始化。然后将该Message对象设置到链表的表头。

        一个recycle()一个obtain(),两者方法结合,就完成了Message的复用。可以简单理解为Message中维护了一个Message对象池(使用链表缓存),而这个池的最大容量是MAX_POOL_SIZE(50)。使用obtain()方法获取Message对象时,首先会从链表中取Message对象,如果链表中为空,就new一个新建Message并返回。而Message对象在使用之后会添加到链表的,供下次通过obtain()获取时复用

        其实在Message这里使用的是享元模式,对于享元模式最大问题在于线程安全,所以在obtain()中加了同步。这保存了一个Message只会出现在一个线程中,从而避免了线程问题。

Handler

        它的作用只有两个:发送Message和处理Message。程序使用Handler发送消息时,被发送的消息必须被送到指定的MessageQueue中。也就是说:如果希望Handler能够正常工作,必须在当前线程中有一个MessageQueue,否则消息就没地方存储,而MessageQueue是由Looper在构造时创建的。因此,为保证handler正常工作,它所在的线程必须有一个Looper对象。这里可以分成两种情况:

        第一种:当在UI线程中使用Handler。由于主线程已经创建了Looper,所以可以直接使用。

        第二种:当在非UI线程中时,必须自己创建一个Looper对象,并且将该Looper绑定到当前线程中(调用Looper.prepare()),并启动它(Looper.loop())。

dispatchMessage()

        通过上面Message与Looper的分析,发现Message最终会传递到Handler的dispatchMessage()中,它的代码如下:

    public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }
        从这一段代码可以看出,我们一般只需要重写Handler.handleMessage()就会收到在别处发的消息

构造方法

        下面再看一下Handler构造方法,在构造方法中有两个是最重要的:获取当前Handler关联的Looper以及存储handler发送消息的MessageQueue。虽然handler的构造方法有多个,但是最终会执行下面两个中的一个:

    //不传入Looper对象时    public Handler(Callback callback, boolean async){    …    mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }        mQueue = mLooper.mQueue;        …    }    //传入Looper对象时    public Handler(Looper looper, Callback callback, boolean async) {        mLooper = looper;        mQueue = looper.mQueue;        mCallback = callback;        mAsynchronous = async;    }

        当不传入Looper对象时,与handler关联的Looper便是当前线程中存储的Looper实例。因为,如果当前线程没有Looper实例时,便会抛出异常。在UI线程中,系统已经为该线程关联了一个Looper对象,故可以直接使用new Handler();但在非UI线程中,我们需要在实例化handler之前调用Looper.prepare()为当前线程关联一个Looper对象

        当传入了Looper对象时,Handler关联的便是传入的Looper对象。此时handler发送的消息便会存储到该Looper对象的MessageQueue中。然后Looper通过loop()不断地从自己的MessageQueue中取出消息,再进行分发。

        记传入的Looper对象所在的线程为TA,Handler实例所处的线程为TB。当TA与TB不一样时,handler一样可以将消息发往looper的MessageQueue中。由于Looper.loop()运行在TA线程中,因此loop()方法中调用的msg.target.dispatchMessage()一样是运行在TA线程中,而不是运行在TB中。再结合dispatchMessage()的源码可知,此时Handler.handleMessage()也是在TA中的。

        looper取到的msg始终是与自己关联的MessageQueue中的,所以MessageQueue处于哪个线程中,就意味着它里面的msg会被哪个线程中的looper.loop()取到,进而决定了Hanlder.handleMessage()运行在哪个线程中。但是由于MessageQueue经常由Looper.myQueue()获取,所以Looper对象也就决定了Handler.handleMessage()运行的线程.

        上面TB中hanlder发送的消息会存储到TA中的MessageQueue中,TA中looper会不断从TA中MessageQueuek 取消息,某一刻会取到TB中handler发送的msg,从而会在TA中调用TB中Handler.handleMessage()。

        因此,可以总结一句:与handler关联的Looper对象决定了Handler.handleMessage()执行时所处的线程,或者更准确地说,handler发送的消息存储到的MessageQueue决定了Handler.handleMessage()执行时所处的线程。当Looper实例处于UI线程中,不管handler位于哪个线程,都可以在Handler.handleMessage()中更新界面,因为这个方法是在UI线程中执行的。示例如下:

tv = (TextView) findViewById(R.id.tv);new Thread() {public void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Looper.prepare();System.out.println("线程:" + Thread.currentThread().getName());//new handler时传入的Looper是UI线程中的Handler subHandler = new Handler(getMainLooper()) {public void handleMessage(Message msg) {System.out.println("线程:"+ Thread.currentThread().getName());tv.setText("我是在run中运行的");};};subHandler.sendEmptyMessage(0);Looper.loop();};}.start();
        在上述代码中,subHandler是处于子线程中的,但是handleMessage()是执行在UI线程中的,所以代码不会报错,而且也会更新成功。但是,如果我们在Handler的实例化时不传入getMainLooper(),则会崩掉,异常就是不能在子线程中更改UI。至于为什么要在前面加上Thread.sleep(2000),参看碎雨(四)中的"非UI线程更新UI"。

发送消息

        在Handler中常用sendEmptyMessage和sendMessage发送消息,两者最终会走到sendMessageAtTime(Message, long)中。sendMessageAtTime()的代码为:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;//首先获取MessageQueue        if (queue == null) {            RuntimeException e = new RuntimeException(                    this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);            return false;        }        return enqueueMessage(queue, msg, uptimeMillis);    }
        这里的mQueue都是通过Looper.mQueue属性获得的。 也就是说,handler发送的消息最终会存储到与之相关联的Looper对象的MessageQueue中,而Looper.loop()也是从与自己相关联的MessageQueue中获取的Message对象。从而保证了handler发送的消息会被正确的取出来。

        其中enqueueMessage()的代码为:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        msg.target = this;//在这里将Message.target的属性给设置上了        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }

        在发送消息时,除了send*系列方法,还有一个post()。源码如下:

public final boolean post(Runnable r){   return  sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {    Message m = Message.obtain();    m.callback = r;    return m;}
        从中可以看出,post()也是将参数Runnable封装成Message然后发送出去。此时该Message的callback变量就是Runnable对象。

        再结合Handler.dispatchMessage()可以看出,收到该消息后只会运行Message.callback,并不会执行Handler类中的handleMessage()等。

    private static void handleCallback(Message message) {        message.callback.run();    }
        在该方法中,终于执行了post()中传入的Runnable对象的run()。

        因此,当通过Handler.post()运行时,Message对象的callback属性不为空,就会执行传入post中Runnable.run()方法。如果使用Handler#sendMessage(),Message对象的callback为空,就会执行到Handler#handleMessage()方法。

MessageQueue

        在上面的handler.sendMessage()代码中,最终会调用到MessageQueue.enqueueMessage(),大体代码如下:

boolean enqueueMessage(Message msg, long when) {        ……        synchronized (this) {            ……            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                ……// Inserted within the middle of the queue.                  Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }            ……        }        return true;    }
        从中可以发现,它是通过Message.next将整个Message串成一连Message链,每一个Message都通过next属性记住它的下一个Message。这就是MessageQueue存储Message的过程。

总结

        handler把消息发送到与之关联的looper中的MessageQueue中。Looper.loop()会不断地从MessageQueue中取新消息,并将取到的消息传递到Handler.dispatchMessage()中,从而将消息又传回handler中。

        message能正确地返回到发送它的handler对象中,是因为message.target变量记录下了handler对象。

        由于looper对象决定了handleMessage()调用的线程,所以Handler可以用于线程之间的通信。比如在子线程中访问网络,然后通过handler将结果返回到UI线程,进而更新UI。

        一个线程最多只能关联一个Looper对象,一个Looper对象对应一个MessageQueue对象,而MessageQueue中可以存储多条不同的Message,每一个Message最多只能指定一个Handler进行处理。因此,线程与Handler是一对多的关系。

HandlerThread

        当在子线程中使用Handler时,需要为该线程关联一个Looper对象。此时可以使用HandlerThread类,它本身就是一个Thread的子类,它的run()方法如下:

    @Override    public void run() {        mTid = Process.myTid();        Looper.prepare();//为该线程关联一个Looper对象        synchronized (this) {            mLooper = Looper.myLooper();            notifyAll();        }        Process.setThreadPriority(mPriority);        onLooperPrepared();        Looper.loop();//启动关联的looper对象        mTid = -1;    }

        从中可以看出HandlerThread已经调用过了Looper.prepare()和Looper.loop()。因此,在调用start()之后可以直接使用Handler。

        在调用Looper.loop()之前,调用了onLooperPrepared(),这是一个空方法,一般会在该方法中添加执行一些设置。比如,初始化Handler之类的。

        由于onLooperPrepare()是在run()方法中调用的,所以onLooperPrepared()运行在子线程中,因此可以在里面进行一些访问网络之类的操作。



2 0
原创粉丝点击