哈喽哈喽~ 笔者在过去一个月都在准备考试,所以没有更新啦,现在又和大家见面了,这是笔者的第三篇Handler文章,主要是为了给出笔者在使用和学习过程中,对于handler的一些思考,以及补充在过去两篇文章中未提到的,但是仍然重要的知识点。
上期问题解答
当然在解答之前,需要说明的是: 以下所有的问题答案都是笔者个人意见,如果觉得不对可以和笔者进行讨论噢~ 所以一定要有自己的理解,不要只记笔者给的答案,因为也不一定正确哈~
Looper是怎么做到与线程绑定的?(为什么是一对一的关系)
还记得笔者在上期中提到的ThreadLocal吗?不记得的同学可以自己去看看,这也是Java多线程中比较重要的一个类。回到正题,ThreadLocal的作用是可以在不同线程中存储相同的类型的不同副本值。这样说可能有点抽象,那么笔者讲完Looper与ThreadLocal以及Thread的关系后,相信同学们也是能够理解的。
我们知道,在使用Handler机制时,我们需要进行一个会利用prepare()动作,在这个prepare动作中,我们会利用Looper类中的ThreadLocal对象作为key,我们的looper对象作为value存储到ThreadLocal.ThreadLocalMap中。而ThreadLocalMap中的set方法其实是封装了所在线程的threadLocalMap的set方法(Thread类中维护了一个自己的ThreadLocal.ThreadLocalMap类型的Map),最终是将该键对值存储在了Thread的threadLocalMap中,那么我们获取looper其实也就是从Thread的Map中获取,并且由于Looper中维护的ThreadLocal对象在全局唯一,所以不存在在一个线程中存储两个looper,也是为什么我们不能调用两次prepare()的原因。但是在不同线程中,由于使用的不是同一个ThreadLocalMap,所以获取的looper也会不同。不知道表达的是否清楚呢?哈哈~
为什么消息队列会使用单链表作为底层数据结构,而不是真正的使用队列作为底层数据结构呢?
要明白这个问题,我们需要从消息队列对于底层数据结构应该具有的特性来看,首先在Android中handler是十分常用的,不管是在UI渲染,还是要执行什么动作的时候我们都需要借助Handelr机制来告知主线程,既然常用那么消息的添加和删除肯定是频繁的,所以它的开销就一定要小,那么类似于数组这种对于删除开销比较大的数据结构,自然就排除了。那你肯定会想既然都说是消息队列了,那肯定是先来后到的,直接使用队列不是很方便吗?如果你这样觉得,那你一定没好好看我之前的文章,对于Handler机制不太明白。实际上在消息队列中,它并不是根据你先发的消息还是后发的消息来排队的,而是看你距离执行的时间来进行排队的,并且如果我们想要让消息插队,那么我们也是可以调用相应的方法进行插队的。而队列是实现不了这一点的。所以队列也就排除了。那么我们可以从链表的特性来看,链表有什么特性呢?第一,它可以离散分布,那这个特性在这个场景在有什么作用呢?好像没有,哈哈~ 真的没有吗?其实在笔者看来是有的,链表可以离散分布的特点决定了在一个节点我们可以改变它指向后续节点指针的值就能改变它后续的节点,从而实现插队,而不需要像连续存储的数据结构一样只能让后面的覆盖前面的,来实现删除。总的来说,就是因为链表的删除和添加更方便。
Handler机制的内存泄露问题
这是在特定情况下会出现的问题,比如说我们创建Handler的方式使用的是非静态的内部类,那么这里就会存在一个问题,我们知道,当我们使用非静态的内部类时,创建的对象是会持有外部引用的,而我们利用这个Handler发送的消息是会持有该Handler的,也就是Message的target属性,如果巧一点,我们发送的还是延迟的消息,那么就是说这个消息会一直在消息队列中持有Handler的强引用直到被取出消费掉,而Handler则持有外部类引用,我们假设这个外部类是Acticity,那么再巧一点,当我们发送完该延迟消息,我们就退出该activity了,而消息还没被执行,那么该activity就无法被垃圾回收器回收。那么就造成了内存泄露问题。所以在使用Handler时尽量别使用内部类的方式,或者使用静态内部类。
好了对于上期留下的问题,就解决的差不多了,我们再增加一点关于Message的内容
Message
Message是整个Handler机制中不可或缺的一部分,它是承载消息的载体。那么跟着笔者来认识认识它。
首先我们截取部分的代码给大家看看,以了解它的大概结构。
public final class Message implements Parcelable {
public int what; //消息类别
public int arg1; //参数
public int arg2; //参数
public Object obj; //消息内容
public Messenger replyTo;
public int sendingUid = -1;
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/** If set message is asynchronous */
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when; //执行时间
/*package*/ Bundle data;
/*package*/ Handler target; 消息响应方
/*package*/ Runnable callback; //回调方法
// sometimes we store linked lists of these things
/*package*/ Message next; //链表下一个节点
/** @hide */
public static final Object sPoolSync = new Object();
private static Message sPool; //消息池
private static int sPoolSize = 0; //消息池大小
private static final int MAX_POOL_SIZE = 50; //消息池最大容量
private static boolean gCheckRecycle = true;
...
}
总结起来:
大概能概括为上面的内容。我们创建一个消息,也就是去填充上面的这些值。
官方对于我们创建消息是推荐复用消息池中的消息的,因为这样可以提高效率。另外提一嘴,消息池也是一个链表结构。
那么笔者在这也就是重点讲讲消息池的复用和回收。分别对应的方法是 obtain方法和recycle方法
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();
}
其实在Message内部重载了该方法,提供了多种参数,但都是依赖以上方法进行的,其它的就不列出了。
recycle()
这个是在我们执行完消息的动作后,系统会调用该方法对消息进行回收。不知道大家有没有印象,该方法在哪被调用了呢? 如果想不到了回去看看第二篇。
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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++;
}
}
}
recycle方法是对recycleUnchecked方法进行了简单的封装,检查了该消息是否仍被使用中。而在recycleUnchecked()中则是将消息的内容进行了清除,然后如果消息池未满,则使用头插法将消息加入到链表(消息池)。
结尾
Handler部分的知识点,笔者就暂时写到这里了,本来想跟着大家一起追寻到native层去,但是时间有限(其实是因为笔者是个菜鸡,还在忙着找工作,时间太紧张了)后续再和大家分享咯。