在android开发过程中相信屌丝程序员们都用过Handler来处理一些逻辑任务,比如发送延迟消息处理业务等逻辑,我们常用的图片加载缓存库ImageLoader和Picasso等内部也是通过Handler来最终有后台加载线程切换到主线程(UI线程)来更新页面的,今天就趁着离职有点儿时间就抽空的分析了下它的一点源码,在此总结出来。闲言少叙,书归正传!
先来谈谈Looper:
Looper从源码上来看就是一个普通的Java类,它在消息机制中,顾名思义,就是一个消息循环的角色。有时候读源码,我习惯性的会从它的构造器开始读,当然有时候会从一个方法切入,根据情况不同采取不同的方式。下面让我们看看Looper的构造器都做了什么:
//(每个Looper对象的)消息队列,也就是说每个Looper对象都持有自己的消息队列
final MessageQueue mQueue;
//(每个Looper线程关联的)当前线程
final Thread mThread;
private Looper(boolean quitAllowed) {
//初始化当前Looper对象的消息队列
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();//获取当前线程
}
从上面的代码中我们可以得出如下简单的结论:
a. 每个Looper对象都有自己的消息队列MessageQueue!
b. 每个Looper对象都和当前线程或者说创建Looper的线程相关联。
那么问题来了,当前线程是如何跟Looper对象想关联的呢?如果你读过Looper源码,从代码注释中你可以看到下面一个代码:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//注意是在run方法中调用了prepare
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
}
};
Looper.loop();
}
}
可以发现里面调用了prepare()这个静态方法,所以直接看看prepare()这个方法做了什么了不起的事儿!
//注意为静态final变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//一个Thread只能关联一个Looper对象
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
从代码上不难看出prepare做了两个工作:
a.在当前线程中创建一个Looper对象,放入ThreadLocal中;ThreadLocal作用简单来说就是在每个线程中存储数据,每个线程只能获取到自己存储在ThreadLocal的数据,其他的线程是获取不到自己线程存储在ThreadLocal的数据的。当然既然用ThreadLocal保存一个Looper那么我们肯定可以通过ThreadLocal得到这个Looper对象,方法如下:
//获取当前线程关联的Looper对象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
b.如果当前的线程已经有一个Looper对象相关联,就会抛出异常,也就是说一个Thread只能关联一个Looper对象
总之一句话prepare()方法就是让线程关联Looper对象用的!
写到此处不难发现Looper已经完成了如下工作:
这样我们线程也绑定Looper了,消息队列也由Looper对象创建好了,所以该是消息队列工作的时候了!!!那怎么才能让一个Looper对象持有的消息队列工作呢?其实笼统的说一句无非就是不停的从消息队列中查看是否有新的消息,有则处理,无则阻塞!这个处理消息的方法其实在上面的例子中也现身过,那就是loop()这个静态方法!
/**该方法必须在prepare方法之后调用**/
public static void loop() {
//获取当前线程锁关联的Looper对象
final Looper me = myLooper();
if (me == null) {//在调用loop()之前必须调用looper.prepare()方法
throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread.");
}
//获取当前对象的消息队列
final MessageQueue queue = me.mQueue;
..........省略两行代码......
for (;;) {//是一个无限循环,来遍历消息队列
//获取一条消息Message对象,可能会造成阻塞,
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
//该消息的target是一个Handler,来分发消息,具体怎么分发稍后讨论
msg.target.dispatchMessage(msg);
.....省略部分代码..
msg.recycleUnchecked();
}
}
可以发现loop()方法其实执行了如下两步工作:
1)获取当前线程关联的Looper对象
2)获取Looper对象的消息队列然后开启无限循环获取消息和处理消息
重点就是第二步了,在无限循环中有且只有一个跳出的入口:那就是消息队列的next方法返回了null!另外需要注意的是next方法是一个阻塞方法,这也意味着当MessQueue没有消息的时候,next方法会阻塞进而使得loop方法也一直阻塞。当然next方法有新的消息的时候就调用 msg.target.dispatchMessage(msg);发送并处理消息!需要注意的消息队列中的Mssage都有自己的target对象来处理,target对象不唯一!。简单的概况下可以用如下流程图简单表示:
通过上面的流程图也可以清晰的知道在获取到一个Messge对象之后,通过Message.target.dispatchMessage进行消息的分发和处理。那么这个target到底值什么鬼呢?还记得上文的例子代码LooperThread么?里面有个handler是什么意思呢会不会就这这个target呢?其实回答这个问题之前或许我们应该考虑:“我们是什么时候向Looper中的消息队列添加一条条消息来供loop循环读取呢?“
这就不得不说本篇博文的另一个主角Handler了,顺便说一句,Message的target你应该能想到其实也是一个Handler对象,不信?后面又说明!
Handler简析
先看看Handler的一个构造器,为了说明问题捡了其中的一个构造函数来说明:
//当前线程关联Looper对象的消息队列
final MessageQueue mQueue;
//当前线程关联的Looper对象
final Looper mLooper;
public Handler(Callback callback, boolean async) {
...省略部分代码..
//获取当前线程中关联的的Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {/不能在Thread里面创建Hanlder对象,如果没有调用prepare的话)
throw new RuntimeException(
"Can‘t create handler inside thread that has not called Looper.prepare()");
}
//这个就是初始化关联对象的地方
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
从上面的代码中可以看出一个Handler对象有如下信息:
1)Handler对象持有一个Looper对象的引用mLooper 。
2)Handler对象持有一个消息队列对象的引用mQueue ,并且该引用在构造器中得到了初始化,初始化也很简单就是把looper对象创建的消息队列MessageQueue赋值给mQueue对象。
也就是说Handler关联了Looper对象及Looper对象创建的消息队列!
3)在一个子线程里面是如果没有调用Looper.prepare,不能创建Handler对象!
万事俱备,是时候回答“什么时候向消息队列添加消息”这个问题了!
在使用Handler的时候我们是通过sendMessage方法发送消息的,看看这个方法都做了什么,查看其源码,它的调用脉络是:
sendMessage(Message)–>sendMessageDelayed(Message, long)–>sendMessageAtTime(Message,long);所以直接看sendMessageAtTime这个方法即可:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
****省略了部分代码**
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//初始化了target从这里可以看出来target就是一个Handler
msg.target = this;
//调用messageQueue的enqueueMessage插入消息
return queue.enqueueMessage(msg, uptimeMillis);
}
最终sendMessageAtTime回调用enqueueMessage这个方法,这个方法可以得到如下结论:
a,上图中loop()循环取的message对象后调用message.target,这个target就是一个Handler!
b.Handler调用sendMessage方法发送消息的过程其实就是向MessageQueue这个消息队列插入一条消息的过程,另外我们到这里可以做出以下断言:在UI线程中(主线程)中创建的Handler对象通过sendMessage发送的Message实际上是添加到了UI线程的消息队列中!Looper的loop()方法通过循环消息队列,通过其next方法获取消息,然后处理之。所以下面就该讨论处理流程了。
很简单就从上图中的dispatchMessage方法说起:
public void dispatchMessage(Message msg) {
//如果创建的msg,设置了callback这个Runnable
if (msg.callback != null) {
//执行callback方法,其实是执行run方法
handleCallback(msg);
} else {//如果创建Handler的时候创建了Callback对象
if (mCallback != null) {
//执行callback的handleMessage方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//让handler自己来处理msg
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
//只是简单的调用了Runnable的run方法
message.callback.run();
}
//简单的接口,提供了一个handleMessage方法来处理消息
public interface Callback {
//返回一个boolean值
public boolean handleMessage(Message msg);
}
所以Handler处理消息的过程其实也很简单:
a.检测Message的callback!=null,注意这个callback其实是一个Runnable,调用handleCallback方法其实就是执行这个Runnable的run方法而已。
b.如果创建Handler的时候,初始化了mCallack(这个callback并不是一个Ruannable,其实一个接口,该接口也很简单,就提供了一个handleMessage方法,由客户端决定怎么处理这条Message。
c.最后一步就是调用Handler的handleMessage方法来处理消息了。
通常我自己在使用Handler的时候就是用的定义一个Handler的子类,重写handleMessage方法来处理消息,倒是没有为Handler创建callback!
同时如果你设置了Handler的Callback,并且Callback的handleMessage方法如果返回true,那么Handler的handleMessage方法将不会执行;否则Handler的handleMessage方法也会得到执行!
所以通过上面的讲解,综合起来能得到下面的流程图:
到此位置android的消息机制可以说是解说完毕,不过还有些问题值得思考:我们说了这么多,那么android主线程(UI线程)的工作机制又是怎么样的呢?
UI线程的消息处理
在任何关于activity启动流程解析的资料中我们都会进入ActivityThread的main方法,这里当然不会再分析Activity的启动流程,拿来主义有时候还是不错的(需要注意一点ActivityThread不是一个Thread,只是一个普通的java对象:
public static void main(String[] args) {
//创建主线程的Looper对象,prepareMainLooper内部实际上调用了
//Looper.prepare(false)方法,并把UI线程交给Looper的静态变量 //Looper sMainLooper;持有
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
thread.attach(false);
//创建一个Handler,这个Handler就是关联了UI消息队列的Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//开启了消息循环
Looper.loop();
}
}
结合前面关于Looper的分析,其实ActivityThread的main方法也很简单,其流程如下:
1)调用Looper.prepareMainLooper()使得UI线程也就是主线程与一个Looper对象想关联。prepareMainLooper方法如下:
//提供一个静态变量来持有UI线程的Looper对象
private static Looper sMainLooper;
public static void prepareMainLooper() {
//为UI线程创建一个Looper对象
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//静态变量来引用这个关联了UI线程的Looper对象
sMainLooper = myLooper();
}
}
既然Looper用一个静态变量来保存关联了UI线程的Looper对象,那么我们可以调用Looper的如下两个方法检测是否是主线程:
//Picasso提供的检测是否是UI线程的方法
static boolean isMain() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
而ImageLoader检测是否是UI线程的方法则是如下:
Looper.myLooper() == Looper.getMainLooper()
2)创建一个Handler对象,根据前面的讲解这个Handler对象同样关联了UI线程的looper对象以及该looper对象的消息队列
3)调用Looper.loop()循环获取和处理消息。
需要注意的是:根据Looper源码的注释,android官方并不希望我们自己主动调用prepareMainLooper()方法。
最后的总结
这样UI线程的处理流程也简单的梳理完毕,那么还有一个最后一个问题:我们知道UI组件的更新是是在UI线程中进行的,也即是如果你在非线程中处理了某个任务后需要更新UI组件,那么在非UI线程线程工作完成后都需要交给UI线程来处理,怎么通知UI线程呢?你应该会知道答案:通过Handler,在UI线程中创建Handler,在非UI线程工作完毕后调用UI线程创建的Handler发送消息到UI消息队列,然后按照上面的流程图处理即可。
前面我既然分析了ImageLoader的源码(详见ImageLoader博客),那么就根据ImageLoader的工作原理把非UI线程和UI线程的工作流程也做个总结吧,如下图所示:
到此为止android消息处理机制就简单的讲解完毕,如有不当的地方欢迎批评指正,共同学习!
其实多写博客还是有所帮助的,比如我在写这篇博客的时候,边分析Looper和Handler的源码,有的时候还需要ImageLoader的工作原理来配合加深理解和体会。如果我之前没有写过ImageLoader博客的话,理解或者体会或许就不那么深了,这也算是坚持写博客对自己的回报吧!