1 前言
众所周知,在我们android中有主线程和子线程之分,我们对它们做一下区分:
1.在主线程中刷新UI,但不能做耗时操作,否则很可能报ANR异常
2.在子线程中不能刷新UI,但可以做耗时操作
这是二者的区别,也是一对矛盾。有没有办法解决这种问题呢?google官方给出的方法就是:
在子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。
学习Handler机制意义:
在android中只要牵扯异步加载+子线程更新UI,它背后的原理都是用Handler机制来做的。如:AsyncTask,Volley网络框架等等。所以说掌握了handler机制,我们再去看关于“子线程更新UI”的框架源代码是不是就简单多了呢?!真是一本万利的事啊!
下面我们对handler进行一下深入的分析。
2 ThreadLocal简介
讲解之前我们先要讲解一下ThreadLocal,因为在Handler中这个东西很重要。
大家先不要深究ThreadLocal,这只需要记住下面的特点即可:
1.ThreadLocal中可以存放一个变量A,供多个线程使用。
2.ThreadLocal会给每一个线程一个变量A的副本,这样多个线程通过ThreadLocal使用这个变量时,各个线程之间互不影响。也就是说A线程更改了变量A的值,并不会改变B线程中的变量A的值。
在本章讲解中大家对ThreadLocal掌握到此即可,如果想进一步学习可以参考我这一篇文章《Android开发之ThreadLocal的使用》。
明白了ThreadLocal的这个特性之后,我们再去理解Looper的工作机制就会容易得多了,我们继续。
3 Handler探究
在我们的工作中Handler经常用,但是你发现没有:
我们一定要在主线程里面new一个handlder,然后在子线程中handler.sendMessage(message)来发送消息至主线程,最终消息在handleMessage(Message msg) {}得到相应的处理。
3.1 创建Handler
大家想一下,为什么一定要在主线程中new 一个handler呢,难道在子线程中不行吗?!好我们这就试一下,代码如下:
private class LooperThread extends Thread{
@Override
public void run() {
super.run();
Handler handler=new Handler();
}
}
运行一看,报错了!!!
Can’t create handler inside thread that has not called Looper.prepare().
太好了,报错了!如果不报错,我们还学个毛啊,是吧。下面我们进入Handler的构造方法,看看它为什么报错了。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur");
}
}
mLooper = Looper.myLooper();
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;
}
看17-18行,因为Looper.myLooper()获取一个Looper对象mLooper 为null,所以报错了。我们再看一下20行的错误代码。这不正式我们报的错嘛!错误的意思是:
不能在thread中创建handler,因为没有调用Looper.prepare()方法。
既然错误明确了让我们调用Looper.prepare()方法,那我们就调用一下试试,代码如下:
private class LooperThread extends Thread{
@Override
public void run() {
super.run();
Looper.prepare();
Handler handler=new Handler();
System.out.println("add code : Looper.prepare()");
//doing something
}
}
再运行,果然不报错了!!
小结:
这里我们先做一个小结,说的太多怕大家消化不了:
1.new一个handler之前首先要调用 Looper.prepare();否则会报错。
那么你可能有疑问,骗人的吧,为什么我用handler从来就没有调用过呢?!在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。在ActivityThread中的main()方法中,代码如下:
public static void main(String[] args) {
...
上面很多代码省略
...
Looper.prepareMainLooper();
...
下面很多代码省略
...
Looper.loop();
}
在prepareMainLooper()方法中又去调用了Looper.prepare()方法。代码如下:
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}
因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。
3.2 Looper讲解
好到此为止,我们解释了为什么在子线程中直接new Handler()会报错。但是还有点小尾巴要处理一下:
Looper.prepare(); 里面是什么东西我还没看呢?!
Looper.myLooper();为什么在线程中取出的值是null?我还不知道呢!
Looper到底是个什么玩意,还没告诉我呢!
其实这二个方法正是一对,我们看一下它们的源代码:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
由代码可以看出:
1.myLooper()方法是从ThreadLocal取出一个Looper对象,因为在线程中第一次取,肯定是null。
2.prepare()是向ThreadLocal中添加一下Looper对象,它会先判断是否有一个Looper了,如果没有就设置,否则会报错。如果你调用2次Looper.prepare()就会报错误。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象
好,到现在为止,我们的ThreadLocal开始露出庐山真面目了,ThreadLocal中存放一个Looper对象,这个Looper对象供很多子线程使用,但是子线程之间相互不影响。这个后面会通过源码再讲解,大家在这里先记着,有个印象。
下面我们来说一下Looper是个什么东西:
我们先来看一下Looper的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看出,Looper中有一个消息队列MessageQueue和一个当前的线程。 在构造方法中初始化了它们大家注意,对线程的初始化,其实就是把当前的线程赋给它。所以,Looper,MessageQueue(消息队列)和线程是一一对应的,也就是说:“一个线程对应一个Looper和一个MessageQueue”或者“一个Looper对应一个线程和一个MessageQueue”,或者“一个消息队列对应一个Looper和一个线程“。
总结:
1.一个线程对应一个Looper
2.一个Looper对应一个消息队列
3.一个线程对应一个消息队列
4.线程,Looper,消息队列三者一一对应
5.当收到消息Message后系统会将其存入消息队列中等候处理。Looper在Android的消息机制中担负着消息轮询的职责,它会不间断地查看MessageQueue中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。
3.3 Message的发送讲解
在讨论完Looper、线程、消息队列这三者的关系之后我们再来瞅瞅Android消息机制中对于Message的发送和处理。
看看我们最常用的handler使用方式是不是这样的:
handler.sendMessage(message)——>发送消息
handleMessage(Message msg){}——>处理消息
另外我们还可以通过post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法发送消息。不管那种方法,除了postAtFrontOfQueue()之外这几个方法均会执行到sendMessageAtTime(Message msg, long uptimeMillis)方法,我们来看看它的源码:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
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);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
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, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我们重点看enqueueMessage方法,这个方法分2个步骤:
1.第一步:
给msg设置了target,请参见代码第24行,这个target赋值,大家要记得,后面的loop()方法中要用到。
此处的this就是当前Handler对象本身。在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。不难发现,哪个Handler发出了消息就由哪个Handler负责处理。
2.第二步:
将消息放入消息队列中,请参见代码第28行
在enqueueMessage(msg,uptimeMillis)中将消息Message存放进消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。
上面我们说过:
1.一个线程对应一个Looper
2.一个Looper对应一个消息队列
3.一个线程对应一个消息队列
4.线程,Looper,消息队列三者一一对应
那么:
24行:msg.target = this;中的this就是当前handler本身。
28行:queue.enqueueMessage(msg, uptimeMillis);中的queue就是该线程所对应的消息队列.
为什么这么说呢?我们回头看看handler的构造方法
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;
mCallback = callback;
mAsynchronous = async;
}
(1) 获取Looper,请参见代码第4行
(2) 利用Looper的消息队列为mQueue赋值,请参见代码第9行
(3) 为mCallback赋值,,请参见代码第10行
(4) 为mAsynchronous赋值,,请参见代码第11行
看到了吧,这个mQueue就是从Looper中取出来的。在之前我们也详细地分析了Looper、线程、消息队列这三者的一一对应关系,所以此处的mQueue正是线程所对应的消息队列。
总结:
发送消息时:
第一:给msg标记上要处理它的handler(每个msg对应不同的handdler)
第二:将消息放入消息队列queue中,这个消息队列是我们在handler构造方法中利用Looper早就建立好了。
再次请大家记住:Looper、线程、消息队列是一一对应的。
在28行消息队列调用enqueueMessage(msg, uptimeMillis)方法,我们再来看看,消息队列中是如何对消息进行排序的,代码如下:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。下面我们来是讲解一下出队列。
3.4 Message的处理讲解
消息是如何被处理的呢?其实Looper的消息队列不断的被轮询。在Looper中有这样一个方法:loop();它是一个死循环,专门为了从消息队列中取消息,如果消息队列中没有消息了,它就堵塞。我们看一下它的代码:
public static void loop() {
final Looper me = myLooper();
...省略很多代码....
final MessageQueue queue = me.mQueue;
//重点在这里,for死循环,不雄姿英发的从消息队列中取消息
for (;;) {
Message msg = queue.next(); // 取出一条消息
if (msg == null) {//判断消息是否为空
return;
}
...
省略很多代码
...
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
省略很多代码
...
msg.recycleUnchecked();
}
}
为了节省篇幅,我省略了一些代码。
第4行,我们从Looper中取得消息队列。
第7行,这是一个for循环,而且是一个死循环,目的是不断的轮询消息队列。
第8行,Message msg = queue.next() ,这行代码是从消息队列中取出一条消息。
第16行,msg.target.dispatchMessage(msg);我们在上面发送消息的enqueueMessage方法中讲过,msg.target是该mes对应的handler。然后handler调用dispatchMessage方法来分发消息,然后进入handler的handlemessage()方法来处理。好,下面我们看看dispatchMessage()方法中都写了啥?
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
哈哈,看到这里是不是晴朗多了,这个方法之所以写了这么多if语句,是因为我们创建handler的方式不一样,它要兼容一下:
第一个判断:
if (msg.callback != null) {
handleCallback(msg);
}
这个if判断是处理Message的回调callback
比如调用handler.post(Runnable runnable)时,该runnable就会被系统封装为Message的callback。
关于这点在源码中也有非常直观的体现:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
第二个判断:
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
这是处理Handler的回调callback,比如执行Handler handler=Handler(Callback callback)时就会将callback赋值给mCallback
第三个判断:
调用handleMessage()处理消息Message,比如:
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
3.5 梳理Handler机制
Android异步消息机制中主要涉及到:Thread、Handler、MessageQueue、Looper,在整个机制中它们扮演着不同的角色也承担着各自的不同责任。
1.Thread
负责业务逻辑的实施。
线程中的操作是由各自的业务逻辑所决定的,视具体情况进行。
2.Handler
负责发送消息和处理消息。
通常的做法是在主线程中建立Handler并利用它在子线程中向主线程发送消息,在主线程接收到消息后会对其进行处理
3.MessageQueue
负责保存消息。
Handler发出的消息均会被保存到消息队列MessageQueue中,系统会根据Message距离触发时间的长短决定该消息在队列中位置。在队列中的消息会依次出队得到相应的处理。
4.Looper
负责轮询消息队列。
Looper使用其loop()方法一直轮询消息队列,并在消息出队时将其派发至对应的Handler.
我做了一张图帮助大家理解:
3.6 Handler正确的使用方式:
通过上面的讲解,我们来做一个总结,看看正确的handler如何使用:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
大家可能心生疑问,为什么我使用handler从来没用过Looper.prepare(); 和 Looper.loop(); 这二句,这是如果你在主线程中创建handler,主线程已经给你调用了,不需要我们自己去调用,但是如果你在子线程中创建,那么必须要调用这二句。
我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。
3.7 Handler的一点延伸
Handler不但可以发送消息,而且还可以在线程中进行UI操作,使用方式如上:
1.getActivity().runOnUiThread(Runnable)
2.View.post(Runnable)
3.View.postDelayed(Runnable, long)
4.new Handler(Looper.getMainLooper()).post(Runnable);
6.handler.post(Runnable);
如:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap =
loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
其实之所以可以在线程中进行UI操作,是因为handler都是在主线程中创建的。既然是在主线程中创建的,那么一定不要在其中做耗时操作!
4 结尾
好了就讲到这里吧,希望对大家有所帮助,在技术上我依旧是一个小渣渣,加油勉励自己!