最近在找实习,笔试的时候有一道关于android消息机制的题目。以前也看过一些这方面的东西,但是没有可以得总结过,今天看了任玉刚的《android开发艺术探索》,趁热打铁,总结一下android消息机制。
Android消息机制的主要作用是任务切换,将某个任务切换到指定的线程中执行。从应用的角度来说,主要是为了解决UI线程单线程机制的限制。系统角度(接触不多,感觉有用于AMS调度Activity的作用)。
Q:UI线程为什么设计为单线程?
A:UI控件的操作不是线程安全的,对于多线程并发访问的时候,如果使用加锁机制会导致:
1、UI控件的操作变得很复杂。
2、加锁的操作必定会导致效率下降。
所以android系统在UI操作上使用单线程机制。
切入正题
Android消息机制主要包括:message、handler、Looper。
其中message比较简单,仅仅作为消息的载体,但是它其中的一些属性不容忽略。例如target、callback。还有一些其他属性。例如:arg1、arg2、when、what、data、obj。arg1、arg2、what为int型;when为long型;data为bundle;obj为Object。
Handler为消息操作者,主要用于发送和处理的message。发送有两种方式:
1、通过直接发送message,即sendMessage();
2、通过发送Runnable接口,即Post(Runnable)这种方式最终会调用sendMessageDelayed
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
而在sendMessageDelayed()中,第一个参数getPostMessage(r),我们来看看这里发生了什么
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在getPostMessage(r)方法中,首先生成了一个message对象,同时为message的callback属性设置为post(Runnable r)传入的runnable接口。
通过不管哪种发送方式最终要将调用到handler的sendMessageAtTime方法。
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);
}
再来看看 enqueueMessage(queue, msg, uptimeMillis)这个方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在这个方法中,为message的target设置了值,值为发送此消息的handler(这里为下面消息处理做好了一些准备工作,同时确保了消息由哪个handler发送,之后处理的时候也是那个handler处理)。再往下看queue.enqueueMessage(msg, uptimeMillis),顾名思义,就是将message发送到Looper维护的MessageQueue队列中(不是真实的队列实现,通过单链表实现)。
处理消息的过程在下面具体说明。
Looper是消息机制中必不可少的成分,它内部维护着一个消息队列MessageQueue,在实例化Looper的时候就会创建消息队列,一个线程中只能有一个Looper和一个消息队列的问题了(当然得知道一个线程中只能创建一个Looper,回答这个问题需要看看Looper.prepare()这个方法,同时了解ThreadLocal这个类)
看一下源代码
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.prepare()中调用内部私有的静态方法prepare(),其中sThreadLocal的类型是ThreadLocal。ThreadLocal类型主要应用于当某些数据是以线程为作用域并且不同线程中有不同的数据副本的场合。这里的Looper恰巧适用于这种场合,通过prepare()确保了Looper在某个线程中只能实例化一个对象,并且保存在sThreadLocal中。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
而实例化Looper时,实例化了MessageQueue,从这里解释了上面一个线程中只能有一个Looper和一个消息队列的问题。
在消息机制中Looper担当着消息的储存与转发的工作。通过handler发送的消息最终都存储在Looper的队列中。
Q:handler是怎么和MessageQueue建立的联系呢?
A:handler的构造函数有很多,如下:
但是最终会调用的是以下两个:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
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: " +
klass.getCanonicalName());
}
}
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;
}
不管调用哪一个,总而言之,主要的任务就是初始化值。为handler中的mLooper、mQueue 、mCallback 等赋初值。
通过实例化handler就将handler和MessageQueue、Looper建立了联系。
友情提醒:
1、如果在没有Looper的线程中创建Handler,就会报”Can’t create handler inside thread that has not called Looper.prepare()”的错误,解决方法就是在创建handler之前先调用Looper.prepare()方法。
2、创建handler的时候我们总是使用handler的派生类,其实向handler中传入callBack接口也可以实现处理消息的功能。
public interface Callback {
public boolean handleMessage(Message msg);
}
上面说了一下handler通过sendMessageAtTime方法将消息插入到,Looper中维护的MessageQueue队列中,接下来主要看看Looper是如何取出消息,并将消息转交给handler处理。
Looper中一个重要的方法就是loop(),只有调用了Loop.loop()后,消息循环系统才会真正的运转起来。
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;
// 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);
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();
}
}
我们挑重要的看,首先looper方法中开启了一个死循环,不断的从MessageQueue 中获取消息
Message msg = queue.next(); // might block
queue.next()这个方法是阻塞操作。
获取到了message之后loop方法中调用 msg.target.dispatchMessage(msg),进行了转发工作。从上文的
enqueueMessage方法中看出这里的 msg.target就是当初发送这个 msg的handler。接下来看看handler是怎么通过dispatchMessage处理消息的。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里有两个callback需要解释下
msg.callback:这个callback是handler通过post(Runnable r)方法发送消息时,间接通过getPostMessage(r)将r这个Runnable接口赋值给message的callback。
mCallback:这个callback从类型上不同于上一个,它是一个接口,具体如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
是实例化handler时,可以选择性传入的参数值。
再看看handleCallback内部发生了什么
private static void handleCallback(Message message) {
message.callback.run();
}
代码很简单,就是执行Runnable的run方法。
经过分析结合handler的dispatchMessage方法,不难发现handler处理消息的流程如下:
至此消息机制介绍的差不多了。
最后用一张图来说明Message、Handler、Looper之间的关系。