今天对Android中的Handler Message Looper这三个类从源码角度深入理解其用法。handler类是Android更新ui状态经常用到的异步方式,也是android进程之间通信的一种方式,是一种异步消息处理线程,实现异步线程需要解决的具体问题如下:
1,每个异步线程内部包含一个消息队列(MessageQueue),队列的消息一般采用排队机制,就是先进先出(FIFO)。
2,线程的执行体重使用while(true)进行无限循环,循环体重从消息队列中取出消息,并根据Message的来源进行相应的回调函数处理(CallBack接口)。
3,其他外部线程可以向本线程的消息队列发送消息,消息队列内部读/写操作必须进行加锁,就是不能同时读/写操作。
为了更好理解Handler的工作原理先简单介绍下Handler、Loop、MessageQueue这三个的关系
1、MessageQueue:消息队列,采用FIFO的方式管理Message;
2、Loop:管理MessageQueue,不断从队列里面取出消息,根据Message的信息找到发送该消息的Handler的回调函数进行相应处理
3、Handler:发送Message到MessageQueue,并处理Message信息
这样一来,关于Handler来处理异步消息的过程我们可以这样总结:首先创建一个MessageQueue队列,然后Handler将Message发送到MessageQueue中,然后Loop来从MessageQueue中取出Message,根据消息的target信息去回调相应Handler的函数。接下来我们从源码去看这个过程是怎么一步一步实现的。
首先来看个简单的例子便于我们讲解:
`public class MainAcitivty extends Acitivity
{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button)findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
Message msg=Message.obtain();
msg.what=0x123;
handler.sendMessage(msg);
}});
}
final Handler handler=new Handler()
{
public void handleMessage(Message msg)
{
if(msg.what==0x123)
btn.setBackgroundColor(Color.BLUE);
}
};
}
/*这段代码的主要实现点击一下按钮,按钮就会变蓝色,
这是通过handler来实现的,当然实际项目中肯定不是这么写,
这里只是为了讲解handler的使用才特别写的例子。*/
首先来看下Handler的构造函数:
public Handler() {
this(null, false); //继续调用重载的构造函数
}
public Handler(Callback callback, boolean async) {
。。。
//省略了部分代码,着重看下面的代码
mLooper = Looper.myLooper();` 1
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; 2
mCallback = callback;
mAsynchronous = async;
}
/* 这段代码中看到标记1那行代码,非常重点!!mLooper是Looper类型
这个代码就是给mLooper获取当前线程的Looper对象,那Looper怎么产生的呢又是什么时候产生的呢,
我们去看Looper这个类的源码的myLooper方法*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/*这个地方我们看到有个sThreadLocal变量,这个是什么东西呢,
原来这个是Looper的线程局部变量,ThreadLocal,简单介绍下这个类型,
这是个线程私有变量,可以将线程内部的变量和当前线程匹配起来,
只有当前线程能够访问,这里可以简单的理解为sThreadLocal存了Looper对象,那么什么时候存的Looper对象呢,通过查找找到这么个方法*/
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
/*这句是说如果调用prepare这个方法的时候,
sThreadLocal里面已经有Looper对象了那么就会抛出异常,
这就是说在每个线程中只会有一个Looper对象,这也好理解,因为Looper对象是管理队列的,而每个线程要保证只有一个队列,不然就会造成队列管理混乱呀,所以线程中只会有一个Looper对象和一个队列。所以不要重复去调用prepare()这个方法,否则要抛出异常的*/
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
/*看到这句没,就是这里new了一个Looper对象,但是这个方法是private啊,外部是没法方位的,只有内部方法才可以访问啊,
别急再找找 ,果然找到了,先来看看Lopper的构造函数*/
}
/*这个构造函数就是说了每个Looper对象生成的时候就会创建一个队列,
所以队列的创建不是在别的地方就是在Looper对象的构造函数里面的,
这也就解释了Looper管理队列的原理,线程的队列是存在Looper对象的mQueue变量里面的*/
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();//存了当前的线程
}
/*下面这个方法就调用了上面 prepare(boolean quitAllowed)的方法,
继而new 了一个Loooper对象,所以在很多参考书上会在new Handler对象之前一定要有这么一句Looper.prepare(),现在懂了吧。*/
public static void prepare() {
prepare(true);
}
好了这些工作做完后我们再回到刚才标记1的地方,就是mLooper = Looper.myLooper();`这句,经过前面的Looper的方法调用,我们已经生成了Looper对象和MessageQueue队列了,接下来继续看下面的代码,下面的判断里面说if(mLooper==null)就抛出异常 “Can’t create handler inside thread that has not called Looper.prepare()”这个也说了需要调用Looper.prepare(),这也验证了刚才我们走的源码流程。标记2那个地方就是将mQueue队列添加到当前这个Handler对象里面,而CallBack这里我们是没有设置那么就是null,以后写代码的时候可以自己实现这个回调函数哦。好了,这样在new一个Handler之后我们就完成了这么几个工作,在当前线程中创建了一个Looper对象和MessageQueue队列,并且将队列和Looper对象都存到当前的Handler对象里面了,这样就可以让Handler将消息发送到队列里面了。
接下来我们来看看Handler是怎么将消息发送到队列里面,Looper是怎么管理队列取消息然后再回调相应的函数呢。
我们前面说过Message是通过Handler来发送的,那么必然我们就要去看看Handler这个类里面关于发送的一些函数了
我们就以sendMessage(Message msg)这个为例子好了
public final boolean sendMessage(Message msg)
{
/*继续调用重载函数,根据函数名字就可以判断这是一个延迟发送的函数,只是我们没设置延迟的时间,默认就为0了*/
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//继续看下面的函数
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
/*这句很重点,这个msg的target是什么东东呢,
所以这里就是我们前面说的关于Message的源头信息,
就是说我得知道这个Message是谁发的呀,
毕竟有时候Handler对象有很多对吧,
这里就是设置Message的Handler对象。
*/
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
/*继续看下面的重载函数,
其实所有的Handler向队列发送消息最后都会调用下面的队列函数,
queue是个MessageQueue对象,去看看这个源码*/
return queue.enqueueMessage(msg, uptimeMillis);
}
/*下面的代码就是在MessageQueue这个类里面的,就是queue.enqueueMessage(msg, uptimeMillis)这个方法,首先是获得自身的同步锁synchronized (this),接着这个msg跟MessageQueue实例的头结点Message进行触发时间先后的比较,
如果触发时间比现有的头结点Message前,则这个新的Message作为整个MessageQueue的头结点,如果阻塞着,则立即唤醒线程处理
如果触发时间比头结点晚,则按照触发时间先后,在消息队列中间插入这个结点
接着如果需要唤醒,则调用nativeWake函数,这个函数会经过一系列的调用最终会唤醒Looper对象的Loop方法里面取message消息*/
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
到这里Handler的发送消息完成了,那么在哪里实现了从队列取数据呢,前面我们说过Looper对象是管理队列的,那么很显然,肯定是Looper对象里面来实现取数据的。看下的代码
public 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;//获得Looper管理的队列,其实就是当前线程的队列
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
/*这个地方开始循环去队列取消息了*/
for (;;) {
Message msg = queue.next(); /*might block,这个点是说队列有可能是阻塞状态,所以前面那个地方才有个唤醒队列的本地函数。
但这里我么重点关注的是消息取出后的处理,暂时不做研究了*/
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
/*这个地方获取到消息后就去调用target的dispatchMessage的方法,
就是发送该消息的Handler,
这里就是去执行Handler里面的方法了。
我们着重看handler里面的就行了*/
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
/*看下面的Handler的dispatchMessage这个方法 ,message的callback是个CallBack是个Runnable类型,就是说如果你设置了一个Runnable类型的对象,将它添加到Message里面了,那么Handler处理的时候就会回调改Runnable对象的run方法,举个例子说 msg=Message.obtain(Handler handler,Runnable r);这样就会执行r.run方法里面的操作了
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); 1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { 2
return;
}
}
handleMessage(msg); 3
}
}
我们先来看看标记1的代码,就是去执行了Runnable对象的run方法了
private static void handleCallback(Message message) {
message.callback.run();
}
我们再来看看标记2的代码,此处的CallBack类型不是Runnable类型了,是个Handler的内部接口,看下这个内部接口的定义吧,这里有个函数,是handleMessage方法,显然如果设置了Handler的这个接口,就需要实现这个方法,什么时候设置这个CallBack接口呢,就是Handler的构造函数的时候我们那会说CallBack=null了,所以如果你要将处理消息的代码设置到CallBack这个接口里的话,就需要将这个接口在Handler的构造函数中传入就行了。
public interface Callback {
public boolean handleMessage(Message msg);
}
显然我们这里是没有设置CallBack接口的,所以我么你直接看标记3这个代码handleMessage(msg);是不是很眼熟,就是我们在MainActivity里面new Handler的时候重写的handleMessage(msg)这个方法,所以到这里就会调用这个方法,而源码里面的这个方法是什么都没做,所以需要我们去具体实现相应的逻辑处理代码
public void handleMessage(Message msg) {
}
至此,关于Handler的整个发送消息然后消息入队列,然后从队列里面取出消息,最终在Handler里面处理消息,这个过程都已经说完了,那么前面说了在new一个Handler对象之前必须要Looper.prepare()生成当前线程的Looper对象和队列,那么我们在MainActivity里面好像没有执行这个啊,因为在ui线程启动的时候,AcitiviyThread已经帮我们生成了Looper对象和队列,也已经在Looper.loop()了,不断在读取队列的消息了,只是刚开始没消息,队列是处于阻塞状态下,所以在子线程中如果需要new一个Handler的话,必须要先Looper.prepare(),生成Looper和相应的队列,而且只能一次不能重复,然后再开启loop()这个方法不断去队列读取消息,具体例子的话我不举例了,因为是第一次写博客,所以很多语言组织上存在很多瑕疵,如果有什么建议或者错误的地方恳请大家帮忙指正,我们在后面继续更新我的博客,写一些关于Java和Android方面的知识,也是自己最近学习的心得吧,和大家一起交流交流