深入了解Handler消息机制(一)

来源:互联网 发布:索引超出数据界限 编辑:程序博客网 时间:2024/05/22 10:47

在Android开发了一段时间之后,很多读者应该知道了一个知识点:UI不能在子线程中更新。这本来是一个伪命题,因为并不是UI不可以在子线程中更新,而是UI不可以在不是它创建的线程中更新。只是绝大多数情况下UI都是从UI线程中创建的,因此在其他线程更新时会抛出异常。在这种情况下,当我们在子线程中完成了耗时操作之后,通常会通过一个Handler将结果传递给UI线程,然后在UI线程中更新相关的视图。

那么Handler,Looper的工作原理又是什么呢?它们之间是如何协作的?

在此之前我们需要了解两个概念,即MessageQueue和Message。其实Android应用是事件驱动的,每个事件都会转化为一个系统消息,及Message。消息中包含了事件相关的信息以及这个事件的处理人–Handler。每个线程中都会有一个默认的MessageQueue。这个消息队列维护了一个待处理的消息列表,有一个消息循环不断地从这个队列中取出消息,处理消息,这样就使得应用动态的运作起来。他们的运作原理就像工厂的生产线一样,待加工的产品就是Message,“传送带”就是MessageQueue,电动机就是Looper,工人们就对应于处理事件的Handler。这么一来Message就必然会产生很多的对象,因为整个应用都是由事件驱动的,即Message来驱动的,系统需要不断的产生Message,处理Message,销毁Message,难道Android没有IOS流畅就是这个原因吗?答案显然不是的。

在Message文档中有:

 /* * <p class="note">While the constructor of Message is public, the best way to get * one of these is to call {@link #obtain Message.obtain()} or one of the * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull * them from a pool of recycled objects.</p> */

意思很清楚,是建议我们在使用Message的obtain方法获取Message对象,而不是通过Message的构造函数,因为obtain方法会从被回收的对象池中获取Message对象。
下面看看obtain方法:

// sometimes we store linked lists of these things /*package*/ Message next;  private static Message sPool; 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();    }

看到上面的代码,是不是有些熟悉。对,就是链表。这个next指向的下一个Message的。每个Message对象都有一个同类型的next字段。这样一来所有的可用的Message对象就通过next串联成一个可用的Message池。在obtain方法中,首先会声明一个Message对象m,并且让m指向sPool。sPool实际上指向了m1,因此,m实际上也指向了m1,这里相当于保存了m1这个元素。下一步就是sPool指向m1的下一个元素,也就是m2。sPool完成后移就会把m.next赋值为null, 此时再把链表中的元素的个数减1,这样链表中的第一个元素就成功的脱离了原来的消息池队伍了。
但是那些Message对象什么时候被放到链表中的呢?我们在obtain方法中只看到了从链表中获取,没有看到将Message对象放进对象池的代码,那么到底线索在哪呢?

不知道注意到上面的注释的最后一句话:

which will pull them from a pool of recycled objects

相信大家也都明白了,原来在创建的时候是不会把Message对象放在池中的,在回收该对象时才会将该对象添加到链表中。其实Message类中有类似于Bitmap那样的recycle函数,具体代码如下:

 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++;            }        }    } public void recycle() { //判断该消息是否还在被使用        if (isInUse()) {            if (gCheckRecycle) {                throw new IllegalStateException("This message cannot be recycled because it "                        + "is still in use.");            }            return;        }        //清空状态并将添加到消息池中        recycleUnchecked();    }

recycle函数会将一个Message对象回收到一个全局的池中,这个池也就是上文说到的链表。recycle函数首先判断该消息是否还在使用,如果还在使用就会抛出异常,否则调用recycleUnchecked函数处理该消息。recycleUnchecked函数中先清空该消息的各个字段,并且将flags设置为FLAG_IN_USE,表明该消息已经被使用,这个flag在obtain函数中会被设置为0,这样就能够追踪该消息的状态。然后判断是否要将该消息回收到消息池中,如果池的大小小于最大SIZE,则将自身添加到表头。当我们调用obtain方法的时候,如果池中有元素的话就会从池中获取表中的第一个元素。
现在已经很明朗了,Message通过在内部构建一个链表来维护一个被回收的Message对象的对象池,当用户调用obtain函数时会优先从池中获取,如果池中没有可以复用的对象则可以创建这个新的Message对象。这个新创建的Message对象在被使用完之后会被回收到这个对象池中,当下次再调用obtain函数时它们就会被复用。

0 1
原创粉丝点击