Android是基于事件驱动,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,理解弄懂消息机制可以让我们在开发的过程中更加得心应手。
Android的消息机制也是Handler机制,主要的作用是用来在不同线程之间的通信,通常使用在子线程执行完成一些耗时操作,需要回到主线程更新界面UI时,通过Handler将有关UI的操作切换到主线程。
先来大致梳理下整个流程:
Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程 。所谓Looper线程就是执行循环工作的线程
在应用程序的入口ActivityThread.main()方法中系统已经帮我们创建好Looper对象
//主线程中不需要自己创建Looper
public static void main(String[] args) {
......
Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
......
Looper.loop();//开启消息轮询
......
}
子线程中的Looper是需要我们自己手动创建的
public class LooperThread extends Thread {
@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();
// ...其他处理,如实例化handler
// 开始循环处理消息队列
Looper.loop();
}
}
这样你的线程就可以成为Looper线程
在Looper的构造方法中还创建了MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper.prepare()方法的作用
1.将当前线程变成Looper线程,在其内部维护一个消息队列MQ
2.创建Looper对象并将Looper对象定义为ThreadLocal
调用Looper.loop()后,Looper线程就开始真正的工作了。该方法是一个阻塞性的死循环,它不断轮询自己的MQ,并从中取出队头的消息执行。
public static void loop() {
final Looper me = myLooper();//得到当前线程Looper
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 从MQ中取出消息
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 将真正的处理工作交给message的target(handler)处理分发消息
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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.myLooper()得到当前线程looper对象:
public static @Nullable Looper myLooper() {
// 在任意线程调用Looper.myLooper()返回的都是那个线程绑定的looper
return sThreadLocal.get();
}
handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,如果当前线程还没有初始化Looper,或者说当前线程还不是looper线程,会报RuntimeException,不过这也是可以set的。
public class handler {
final MessageQueue mQueue; // 关联的MQ
final Looper mLooper; // 关联的looper
final Callback mCallback;
// 其他属性
public Handler() {
// 没看懂,直接略过,,,
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());
}
}
// 默认将关联当前线程的looper
mLooper = Looper.myLooper();
// looper不能为空,即该默认的构造方法只能在looper线程中使用
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
mQueue = mLooper.mQueue;
mCallback = null;
}
// 其他方法
}
Handler发出的message有如下特点
1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码
msg.target.dispatchMessage(msg);
2.post发出的message,其callback为Runnable对象
// 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
public final boolean post(Runnable r)
{
// 注意getPostMessage(r)将runnable封装成message
return sendMessageDelayed(getPostMessage(r), 0);
}
Handler处理消息
// 处理消息,该方法由looper调用
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果message设置了callback,即runnable消息,处理callback!
handleCallback(msg);
} else {
// 如果handler本身设置了callback,则执行callback
if (mCallback != null) {
/* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。 */
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message没有callback,则调用handler的钩子方法handleMessage
handleMessage(msg);
}
}
// 处理runnable消息
private final void handleCallback(Message message) {
message.callback.run(); //直接调用run方法!
}
// 由子类实现的钩子方法
public void handleMessage(Message msg) {
}
重点
1.Handler 可以在任意线程中发送消息,这些消息会被添加到关联的MQ上
2.Handler是在它关联的Looper线程(Looper绑定的线程)中处理消息的
总结
1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。
2.handler是在它关联的looper线程中处理消息的。(handlerMessage()方法运行所在的线程是根据handler创建的时候绑定的Looper线程,和绑定的Looper所在的线程一致)
3.Android的主线程也是一个looper线程,我们在其中创建的handler默认将关联主线程MQ
message又叫task,封装了任务携带的信息和处理该任务的handler
注意事项:
1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3.擅用message.what来标识信息,以便用不同方式处理message。
为了能够更好的理解掌握消息机制,以下有几道面试题
因为线程对应的Looper是在ThreadLocal里面存储,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。ThreadLocal它的作用是可以在不同的线程之中互不干扰地存储并提供数据(就相当于一个Map集合,键位当前的Thead线程,值为Looper对象)。另外,在looper创建的方法looper.prepare()中,会有一个判断如果当前线程存在Looper对象,就会报RunTimeException,所以一个线程只有一个Looper,而MQ作为Looper的成员变量自然也就只有一个。
任何线程都可以实例化Handler,handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,如果当前线程还没有初始化Looper,或者说当前线程还不是looper线程,会报RuntimeException。
在应用程序的入口ActivityThread里面的main方法中会创建一个主线程的looper对象和一个大Handler,(这也是为什么直接在主线程拿Handler就有Looper的原因,在其他线程是要自己Looper.prepare())
Android是基于事件驱动的,通过looper.looper()不断接收事件,处理事件,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,当收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。如果它停止了,应用也就停止了。也就是说我们的代码其实就是运行在这个循环里面去执行的,当然就不会阻塞。
而所谓ANR便是Looper.loop没有得到及时处理,一旦没有消息,Linux的epoll机制则会通过管道写文件描述符的方式来对主线程进行唤醒与睡眠,Android里调用了linux层的代码实现在适当时会睡眠主线程。
拓展
对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出
ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出。
主线程在没有事件需要处理的时候就是处于阻塞的状态。想让主线程活动起来一般有两种方式:
第一种 :是系统唤醒主线程,并且将点击事件传递给主线程;
第二种 :是其他线程使用主线程的Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。
总结
Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。因此loop的循环并不会对CPU性能有过多的消耗。
Handler.sendMessageDelayed()内部调用sendMessageAtTime()把传入的时间转化成绝对时间when(延时的时间加上系统当前的时间),然后调用MessageQueue的enqueueMessage(),采用线程安全的方式将Message插入到消息队列中,消息队列的插入是由when顺序排列,插入的新消息有三种可能成为消息队列的head:
(1)消息队列为空;
(2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把消息插入到队列的头部)系列的API才会满足这个条件;
(3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。
接着就是Looper.looper()启动消息循环,循环开始调用messageQueue.next()从消息队列中取出一个合理的消息。如果next()返回null,则looper()直接return,本次消息循环结束。如果消息不为空则调用msg.target.dispatchMessage(msg)处理消息(msg.target就是Handler)
.next()取下一个消息的实际执行时间取决于上一个消息什么时候处理完
在MessageQueue.next()中,如果在消息队列中顺序找到了一个消息msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于msg.when,那么会计算一个timeout,以便在到执行时间时wake up;如果当前系统时间大于或等于msg.when,那么会返回msg给Looper.loop()。所以这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理。
(1)在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。
(2)即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。
(3)在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。
所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。
参考
https://blog.csdn.net/zhanglianyu00/article/details/70842494
http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
https://www.jianshu.com/p/1c79fb5296b6