注:本来只是想整理成Handler部分的面试题及答案拿来背诵的,哪知一看源码就没停下来,变成了大篇文章...本文根据源码对Handler进行分析,但由于能力有限,仍然和市面上的大多Handler消息机制的文章相似——即,仅探讨表层逻辑,对于其中的Binder、native、epoll之类的并不涉及。所以如果你已经看多了表层解析,就可以右上角啦~。
而且有个大问题:我把自己当作一个旁观者再读了一遍这篇文章发现自己讲的并不清楚,所以各位还是自己去看一遍源码比较好。至少我自己感觉在看了这一遍源码之后,印象就很深刻了,估计能记很久。
Android的消息机制主要是指Handler的运行机制。
1. 什么是Handler
Handler是Android消息机制的上层接口。我们可以使用Handler将一个任务切换到某个指定的线程中去执行。在Android 中,Handler主要是为了解决在子线程中无法访问UI的矛盾。
2. 为什么子线程无法访问UI
因为Android的UI是线程不安全的,UI负责与用户的交互,如果多线程中并发访问UI会导致UI处于不可控的状态。其次也不能选择使用加锁处理,首先因为加锁会阻塞某些线程的执行,降低UI访问的效率,其次加锁会让UI访问的逻辑变得复杂。
//该方法用于检查当前是否是UI线程
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}}
3. 为什么使用Handler
UI线程直接与用户交互,如果在UI线程处理耗时操作,用户将无法继续执行其他UI操作,用户体验很差。其次Android规定,如果任意一个Acitivity没有响应5秒钟以上就会弹出ANR窗口。因此我们可以使用Handler将耗时操作放到子线程中去执行以避免上述情况。
4. 怎么使用Handler
首先
private Handler handler = new Handler(){
@Override public void handleMessage(Message msg) {
}};
然后再子线程中处理完耗时操作后构建一个Message对象,然后使用handler.post(Runnable r)或者handler.sendMessage(Message msg)等方法传入Message即可。post()方法最后也是使用sendMessage()方法,因为Runnable对象会被转化成一个Message。
这里有一点需要注意的是,由于我们是使用Message来进行消息传递,所以如果每次都new 一个Message就很可能会产生大量用了一次就不再用的Message对象,消耗有限的内存资源。针对这种情况,Handler的obtainMessage()方法内部采用了享元模式。我们应该优先使用Handler的obtainMessage()方法构建Message对象。
5. Handler中的享元模式
享元模式适用于可能存在大量重复对象的场景。具体的定义忘了。一般经典的享元模式会使用Map作为对象容器。不过在Message中,是通过一个单链表来实现的,具体的说就是sPool,而sPool就是一个Message。
那为啥sPool这个Message对象会是一个单链表呢?见如下代码:
//Message.java中
Message next; //这不就是自定义单链表的中next指向下一个节点嘛
调用obtainMessage()最终会调用Message的obtain()方法,obtain()方法先会判断sPool如果为空,就返回一个新的Message对象,不为空就返回sPool,然后sPool指向其next。
6. Handler的内存泄露问题及其处理方法
成因:
Handler的内存泄露问题大致是这样形成的:就是主线程中创建的Handler会和主线程Looper的消息队列MessageQueue相关联,于是MessageQueue中的每个Message都会持有一个Handler的引用。而由于非静态内部类和匿名类都会隐式的持有它们所属外部类的引用,所以就导致了Message持有Handler引用,Handler持有其外部类的引用的引用链。在Message被处理前,这条引用链会阻止垃圾回收器的回收,于是发生内存泄露。
验证:
可以使用 handler.postDelayed()方法进行验证。
解决:
解决方法:1. 使用内部静态类构造Handlr,因为内部静态类不会持有外部类的引用。但是这样的话就无法操控外部Activity的对象,于是还需要增加一个队Activity的弱引用。2. 在Activity退出的时候调用Looper的quit和quitSafely方法,以及使用对应的handler.removeCallback方法,这些方法会最后会执行 msg.target = null 操作,让msg不再持有handler引用。
7. 讲解一下Handler的运行机制
private Handler myHandler = null;
new Thread("handler线程"){
@Override
public void run() {
Looper.prepare();
myHandler = new MyHandler();
Looper.loop();
}
}.start();
private class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
Handler运行机制的主要参与者是Handler、MessageQueue、Looper以及Message和ThreadLocal 这5个角色。
(感觉有些复杂)
这5个角色关系有点复杂,
-----------------------------ThreadLocal-------------------------------------
先讲相对简单的ThreadLocal。ThreadLocal是在Looper中使用的,ThreadLocal的作用是我们可以通过它把数据存储到指定的线程,并且只有在指定的线程才能获取数据。
而其中把数据储存到指定的线程这个操作在Looper.prepare()中进行,方法调用的最后会调用ThreadLocal的set方法传入一个Looper对象(Looper对象包含了MessageQueue和当前线程)。
//Looper.java中
//全局唯一一份的ThreadLocal,将会在每个ThreadLocalMap中作为key
static final ThreadLocal sThreadLocal = new ThreadLocal<~>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get()!= null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//这个sThreadLocal是static final 的.
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
然后我们可以看到ThreadLocal中的set(T value)中的createMap()方法中传入的是Thread和new Looper(),但并不是用Thread作为key呦,如下。
//ThreadLocal.java中
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else{
createMap(t, value);
}
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//Thread.java中
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap(this , firstValue)中,this就是那个全局唯一份的sThrealLocal。所以我们很清楚了,在每个线程中Looper.prapare()的时候都会给当前线程传入一个ThreadLocalMap,这个Map的key其实不重要,因为所有线程中的ThreadLocal都指向同一个对象,就是Looper.java中的static final ThreadLocal sThreadLocal…因为key不变,所以如果多次调用Looper.prepare()的话,是会覆盖的。
上面讲了如何通过ThreadLocal把数据存储到指定线程,至于后部分的只有在指定的线程才能获取数据也就一目了然,通过当前线程t.threadLocals获取线程中的变量副本,然后取出值即可。在源码中的体现如下:
//Looper.java中
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//注意这个myLooper()。返回了一个Looper对象。
}
//ThreadLocal.java中
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//下面这个value。。。之前存进去的那个Looper对象了
T result = (T)e.value;
return result;
}
}
return setInitialValue();//这个就是在map中没有的时候返回个null
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
简单的讲,每个线程都有个存储变量的副本,互相隔离。
那么这个特性对我们的Handler机制有什么影响呢?从上面可以了解到每个Thread其实都对应了一个Looper,而每个Looper都对有一个MessageQueue。
具体有什么影响需要先看一看Handler的构造方法才能看出来。
-----------------------------Handler--------------------------------
先来看一个错误示例:
new Thread(){
public void run(){
Handler handler =new Handler();
};
}.start();//正确的方法是handler前面用Looper.prepare(),后面用Looper.loop()
上述代码会抛出“Can’t create handler inside thread that has not called Looper.prepare()”异常,这是因为在Handler构造方法中所有规定,如下:
//Hnadler中
public Handler(Callback callback, boolean async) {
…省略代码
mLooper = Looper.myLooper(); //最终调用sThreadLocal.get()
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//Looper中
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//如果没有获取到Map,则返回null
}
在ThreadLocal一节的分析中可以看到sThreadLocal.get()方法在没有获取到ThreadLocalMap的情况下,会返回一个null。
因为其下一步 mQueue = mLooper.mQueue;需要将Looper中的MessageQueue拿出来进行操作,所以必然之前就需要有一个Looper对象,所以就必然要先Looper.prepare()啦。
至于在主线程中我们为啥可以直接new一个Handler呢?因为系统已经帮我们做好了这一步,代码如下:
//ActivityThread中
public static void main(String[] args) {
…代码省略
Looper.prepareMainLooper();
…代码省略
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
…代码省略
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
扯远了咳咳。
从前面的Handler的构造方法中就可以看到,Handler其实使用的是Looper对象中的MessageQueue,而Looper对象又是和Thread对应的。
那么我们就知道了,Handler的各种发消息的操作send() , post() 等,最后都会把数据Message传递到“那个线程(假定先命名为Thread_1)”中。
知道这个我们就可以知道为啥handler可以在不同的线程中给自己的线程的线程发消息啦。
对于使用者来说,使用Handler的步骤并不复杂,定义一个Handler,然后调用handler.sendMessage()发送消息,然后在handleMessage()中对消息进行处理即可。
那么我们就从发送消息开始一步步了解Handler的消息机制。
Handler的发消息有很多种发送消息的接口,如下
postAtTime(Runnable r, Object token, long uptimeMillis)
postAtTime(Runnable r, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long delayMillis)
sendMessageAtFrontOfQueue(Message msg)
sendMessageAtTime(Message msg, long uptimeMillis)
sendMessageDelayed(Message msg, long delayMillis)
sendEmptyMessageDelayed(int what, long delayMillis)
sendMessage(Message msg)
sendEmptyMessage(int what)
postAtFrontOfQueue(Runnable r)
……等方法
但是这么多方法,最后都是调用了enqueueMessage()方法
//Handler.java中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //这个地方要注意,在这里msg持有了handler引用
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最后的queue还有印象么,就是之前说的,从Looper对象中存储的那个MessageQueue。
所以最后调用了MessageQueue.java中的enqueueMessage(Message msg, long when)方法,如下:
//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
…省略代码
synchronized (this) {
if (mQuitting) {
…省略代码
msg.recycle();
return false;
}
…省略代码
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
…省略代码
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
…省略代码
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
…省略代码
}
return true;
}
上述的代码不扣细节的话,就一件事,把消息插入mMessages中。
sMessages明明是Message为何能插入呢,因为在1.5的享元模式中就介绍了Message可以构成单链表。所以虽然喊MessageQueue.java是消息队列,但是其实它自身并不是什么链表队列结构,它的作用是对Message这个单链表进行各种操作。
所以Handler发送消息的最终结果,就是把消息插入到了Message的单链表中。而且这个Message持有Handler的引用。
那么现在消息插入到消息链表中了,怎么把msg拿出来呢?这就到了我们的Looper和MessageQueue环节了。
还记得之前的Looper.prapare() – new Handler() – Looper.loop() 三部曲吗。拿消息的操作就在Looper.loop()中,当然也在MessageQueue中,具体的流程如下。
-------------------Looper和MessageQueue和Message----------------------
话不多说,上代码:
//Looper.java中
publi static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on
this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
…省略代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
…省略代码
try {
msg.target.dispatchMessage(msg);
} finally {
…省略代码
}
…省略代码
final long newIdent = Binder.clearCallingIdentity();
…省略代码
}
}
我留下了几行Binder的代码,我看不懂,但是觉得这个和Binder有关的代码肯定有很大的深意o(╯□╰ )o ,去除掉了其他我不懂的代码之后,剩下的代码的逻辑很清晰:先是获取到当前线程的MessageQueue queue = me.mQueue,然后用一个死循环不断使用queue.next()方法得到msg。如果有msg,就调用handler.disptachMessage(msg)进行消息分发。要问Handler在哪儿?就是msg.target哇。
这里先不看disptachMessage(msg)的过程,我先看看for(;;)这个死循环里,只有一处可以返回。就是当queue.next()返回null的时候,并且所有需要处理的msg都是从queue.next()拿来的。所以看样子,我们的Looper.loop()中最主要的工作是由queue.next()来完成的。
让我们深入queue.next()看看:
Message next() {
…省略代码
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);//这里甚至有个native方法!!!肯定有深意
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
return msg;
}
if (mQuitting) {
dispose();
return null;
}
}
}
这里面我省略了一堆我看不懂的代码,剩下的代码逻辑也不难。这里又有一个for(;;)死循环,这个死循环是不断的mMessages中拿出msg,还记得我们之前见过的mMessages吗,之前看到mMessages是插入节点,这次见到就是要从中取出节点了,取出的操作就是各种.next。
这里重点关注两个return的地方,记住只有两个return。第一个是返回msg,第二是mQuitting == true 的时候返回null。
那么返回null会怎么样呢?还记得Looper.loop()中当msg == null的时候会发生什么吗,当msg = null的时候,Looper.loop()退出for(;;)死循环。既然退出死循环了,loop()方法也就执行完毕,也就意味着这个Looper不再获取新消息,handler再发什么消息都没有用了。
甚至我们可以看到当mQuittiong == true的时候,handler试图enqueueMessage() 发送的消息直接就被回收了:
//MessageQueue.java中
boolean enqueueMessage(Message msg, long when) {
…省略代码
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
…省略代码
}
上面的msg.recycle()和享元模式有关,大概就是这个msg直接给你抹除所有信息然后插入到sPool中。当然sPool这个单链表也不是给你无限插入的,最大数量是MAX_POOL_SIZE = 50。可以自己看下源码哈。
从上面看出mQuitting的威力巨大,那么哪儿设置这个mQuitting的值的呢,见源码:
//MessageQueue.java中
void quit(boolean safe) {
…省略代码
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
…省略代码
}
}
而这个quit( boolean safe )则是在Looper中被调用,如下:
//Looper.java中
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
怎么样,是不是感觉很简单。这两者的区别在于如何处理那些延迟发送的msg,看最后调用的方法名就清楚了:removeAllMessagesLocked()和removeAllFutureMessagesLocked()。
两个方法里都用到了msg.recycleUnchecked(),也就是说这些msg对象最后会被回收进sPool里。所以理解为啥官方建议使用handler.obtainMessage()方法了吧——因为很多地方的msg都会被回收进sPool。而且直接获取现有msg肯定也比每次新new一个message资源消耗少,效率更高。
上面讲了MessageQueue.next()返回null的特殊情况,那么如果正常返回了一个msg呢?正常的话则会调用Looper.loop() 中的msg.target.dispatchMessage(msg)方法,这个方法调用的是Handler中的dispatchMessage()方法,如下
//Handler.java中
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
(假装看不到handleCallback(msg)^_^因为看着有点复杂啊)最后都是调用了handleMessage(msg),就是我们最最最最最最最最开始的——
private Handler handler = new Handler(){
//重写handleMessage方法
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
——这里面的handleMessage(msg)了,然后就可以拿msg去玩耍啦。
还记得这handleMessage()最初哪儿调用的吗?是msg.target.dispatchMessage(msg),而这个msg最终的源头又是存在于t.sThreadLocal这个ThreadLocalMap里的,所以在handleMessage(msg){}方法也就是执行在t这个线程中啦。
于是就达成了——handler这个在外行走可以在不同的线程发送消息,最后消息都是传送到hanler最初创建的Thread里进行处理——这个功能。
1.8 总结
总结几个要点:
1. ThreadLocal是个好东西,它的存在让Thread、Handler、MessageQueue绑定了起来,于是让handler可以做到不管在哪个线程发消息,最终消息都会传送到原来的Thread里。ThreadLocalMap也是功不可没。
2. Looper和MessageQueue共同协作来让整个消息队列动起来,不断的取出新消息
3. Message更多是用来构造成单链表,不管是MessageQueue中的sMessages还是自己的享元模式中的sPool。所以最好使用handler.obtainMessage()来获取Message对象。
4. 如果上述文章哪儿有问题或者哪儿看不懂请轻轻的喷,最好是留个言我可以改善改善O(∩_∩)O
于是,一圈分析结束~ ~ ~ ~ ~ ~ ~啦啦啦啦啦。
…
…
…
秋豆麻袋,那这个msg.callback的运行机制呢?还有那些看着名字奇奇怪怪的方法呢?还有为什么UI线程死循环竟然不阻塞呢?还有….你不是说那个内存泄露有问题吗?你的最终结论呢?
O(╯□╰)o
Msg.callback实在不想看了,内存泄露问题等我再问问大神,那些奇奇怪怪名字的方法如果书上木有我也研究不出啥。
但是至于为什么UI线程死循环不阻塞这个问题,问的好!
看我再秀(Baidu)一波!
其实我在源码中看到了一堆Binder、native、epoll字眼,所以我一直怀疑不阻塞这个东西应该和Binder机制甚至和Linux的底层机制管道epoll有关。
...
算了直接放链接点我你可以发现新大陆
epoll模型
当没有消息的时候会epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。
所有的ui操作都通过handler来发消息操作。
比如屏幕刷新16ms一个消息,你的各种点击事件,所以就会有句柄写操作,唤醒上文的wait操作,所以不会被卡死了。
这部分留待以后如果有机会研究Linux再详细看吧。和Binder机制一样,如果要研究的话,就太深入了。
本文参考《Android开发艺术探索》 + 《Android源码设计模式解析与实战》+ AOSP以及感谢1Offer童鞋。
网友评论