前言
之所以要写Handler,是因为最近面试Handler被虐,所以回来看了一下,并且发现知识链很多都是断裂的,所以打算从今天开始,有时间就整理博客。这应该算是我干Android这几年的第一篇博客,可能有不对的地方,望小伙伴们能指点一二!
Handler是什么
Handler就是线程间通讯的一个工具,可以让你在不同线程间更加简单的传递数据。
Handler是怎么工作的
它主要涉及到了四个类:Handler,Message,Looper,MessageQueue
分别都是做什么的呢
Message作为消息主体,由Handler把这个消息Message传送到用于存放消息的消息队列MessageQueue中,然后再由Looper这个循环器不断的轮训查看消息队列MessageQueue中是否有新的消息,如果有在发送回Handler,由Handler分发这个消息Message。大概的工作流程就是这样的。
Handler通讯机制的简单使用
发消息
/**
* 延时执行该Runnable
* 此处Runnable是在主线程中
*/
Runnable run = new Runnable() {
@Override
public void run() {
int a = 1 + 2 + 3;
Log.v("myHandler"," a = "+a);
}
};
handler.postDelayed(run,2000);//延迟两秒,注意:该方法即使Activity销毁仍然执行
handler.postAtTime(run,2000);//时间参数计算方法是从手机开机开始计算,之后的毫秒值执行接口。比如从开机到现在是10000毫秒,你想在3000毫秒后执行run,就填入10000 + 3000。当前开机时间获得方法是SystemClock.uptimeMillis()。
/**
* 消息组装
*/
Message msg = Message.obtain();
msg.what = 12;
msg.arg1 = 13;
msg.arg2 = 14;
handler.dispatchMessage(msg);//是在当前线程发送消息(如果当前是在子线程中,Handler接收消息不要更新ui否则异常,因为Android不能再子线程中更新UI)
handler.sendMessage(msg);//进入UI线程发送消息
handler.sendEmptyMessage(0);//进入UI线程发送标记
/**
* 定时任务
* 其实sendMessageDelayed调用的也是sendMessageAtTime
*/
handler.sendMessageAtTime(msg, SystemClock.uptimeMillis()+3000);//计时,SystemClock.uptimeMillis()获取当前时间
handler.sendMessageDelayed(msg,2000);//上面的是绝对时间,下面的是相对时间,即消息送走时间
收消息(主线程)
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 12:
Log.v("myHandler"," msg.arg1 = "+msg.arg1);
Log.v("myHandler"," msg.arg2 = "+msg.arg2);
break;
default:
}
}
};
收消息(子线程)
new Thread() {
@Override
public void run() {
Looper.prepare();
handler = new Handler(Looper.myLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
Log.v("myHandler", "isMainThread = " + isMainThread());
Looper.myLooper().quitSafely();//退出looper
return true;
}
});
Looper.loop();//死循环,此处之后的代码在Looper退出之前不会执行(并非永远不执行)
Log.v("myHandler", "isMainThread = " + isMainThread());//这一行代码只有当第9行执行完之后才会执行
}
}.start();
这里需要做一个说明:在子线程中创建Handler时,必须使用带有Looper参数的Handler构造器,Handler有三个带有Looper构造器的重载方法,子线程默认Looper是没有初始化的,所以需要调用Looper.prepare();初始化,并且还要调用Looper.loop()开始循环,否则这里的Handler是无法接收到消息的!而在接收消息后又调用了Looper.myLooper().quitSafely();退出循环,这个时候第14行的日志才会被打印,因为在循环结束之前子线程是被阻塞的,所以没有继续往下执行,因此上方代码片段中,在没有接收到消息之前不会打印任何日志,接收到消息之后第8行日志被打印,接着loop循环结束,然后子线程继续向下执行,打印第14行日志,然后子线程生命结束
----------------------------------------------------------------------------------------------------------------
下面开始进入正题,分析Handler!既然Handler消息通讯主要牵扯到四个类,那接下来就一个一个来说一下!
1:Handler
首先说一下Handler的构造器,一共是有七个重载的构造方法
final Looper mLooper;
final MessageQueue mQueue;
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通过源码可以看到,前5个构造方法最终调用的是第6个和第7个构造方法,那也就是说只有第6个和第7个在实际构造Handler, 给成员变量mLooper和mQueue做了显式初始化初始化,也就是说一个Handler只对应一个Looper,looper中有消息队列MessageQueue,这里从Looper中获取了消息队列MessageQueue赋值到本类的成员变量上,此处是Handler与Looper和MessageQueue产生关系的地方。
前面说Handler是怎么工作的地方说过,Handler把消息Message传送到用于存放消息的消息队列MessageQueue中,上方源码中的构造器就让Handler与MessageQueue产生关系,然后能对MessageQueue做一些操作,而这个操作就是单纯的往其内部放一些消息Message。
前面说过的使用例子中我们能看到Handler有好多方法能发送消息,例子中也并不是全部的发送消息的方法,有兴趣的可以翻看下源码或api。Handler发送消息无论你调用的是哪个发送消息的方法,最终都是由enqueueMessage一个方法来处理的,我们来看一下这个方法是怎么处理的
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
//给msg做了一个标记,标记这个消息的收取方
msg.target = this;
//告诉msg我们是在哪个线程上工作的
msg.workSourceUid = ThreadLocalWorkSource.getUid();
//设置消息是否异步,让msg不受制于同步屏障所影响
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//开始往消息队列中塞入消息
return queue.enqueueMessage(msg, uptimeMillis);
}
上方源码中每一行都加了注释,那我们来说一下这个方法的参数,第一个参数就是在本类Handler的构造时所获取到的消息队列,第二个参数是一个时间,这个时间是给MessageQueue使用的,如果我们发送消息时调用的方法是有时间参数的,那这里就是那个时间,如果调用的是没有时间参数的方法发送消息,这里会默认一个0,有什么用呢,主要就是消息队列排序用的,为什么要排序呢?在Handler的头注释中有这么一段话
There are two main uses for a Handler:
to schedule messages and runnables to be executed at some point in the future;
to enqueue an action to be performed on a different thread than your own.
说Handler有两个主要用途:
- 计划在未来某个时间点执行消息和可运行文件;
- 将要在不同线程上执行的操作排队
也就是说Handler可以让我们在不同线程中更加方便(简单)的即时或延时的发送消息,后边的消息队列中会讲到这个时间的具体使用方法
上方是Handler发送消息,那是怎么接受消息的呢?我们来看下源码
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*/
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
注释中有写道,我们可以实现接口Callback或者重写Handler的handleMessage方法来接收消息,那这两个地方的消息又是哪里来的呢?是由下方源码中的方法来分发的,如果这是一个系统消息,那调用的就是下方的第3行,否则就进入else中,上方使用例子中在主线程中接收消息就是下方源码中第10行的handleMessage方法的重写,使用例子中在子线程中构建Handler,我们实现了Callback接口,那里实现的方法handleMessage就是下方第6行代码执行的回调
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上面就是Handler发送消息和接收消息的讲解,既然都是围绕着消息运转的,那消息很重要,接下来我们来看一下是什么样的消息
2:Message
Message:消息的载体
我们先来看一下Message的头注释说了什么东西
/**
*
* Defines a message containing a description and arbitrary data object that can be
* sent to a {@link Handler}. This object contains two extra int fields and an
* extra object field that allow you to not do allocations in many cases.
*
* <p class="note">While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.</p>
*/
public final class Message implements Parcelable {
大致的意思是:Message是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message中包含了两个额外的 int字段和一个object字段,这样在大部分情况下,使用者就不需要再做内存分配工作了。虽然Message的构造函数是public的,但是最好是使用Message.obtain( )或Handler.obtainMessage( )函数来获取Message对象在message.get()方法。关键的部分来了,它最后提到了一个池的概念!什么意思呢?主要是说Message它可以回收再利用,在他的成员变量上有这样两个字段
//池的当前大小
private static int sPoolSize = 0;
//池的最大容量
private static final int MAX_POOL_SIZE = 50;
这两个字段是对池的一个最大容量限定所用,也就是说这个Message不光是一个消息,我们也可以把它当做一个消息池来看待,但是它的最大容量是50个,但其实我们在正常使用时并不是很容易就达到这个最大容量,分析源码的时候我们就能大致明白为什么不那么容易达到了! 这个Message还实现了一个Parcelable的接口,为的是进程间通信,Handler消息机制不是说好的线程间通信么?在后边我们讲消息队列的时候会在来说一下这里提到的问题。下面我们继续来分析Message
首先来看一下这个消息的载体他能够承载哪些消息
//用于区别消息的类型
public int what;
//携带数据,空消息所携带的数据
public int arg1;
public int arg2;
//携带数据(进程间通信时只能携带系统定义的parcelable对象)
public Object obj;
//Messenger进行进程间通讯时,用于实现双向通讯
public Messenger replyTo;
//可存放多条的携带数据
Bundle data;
//消息所携带的代码语句数据
Runnable callback;
//消息的处理目标对象,用于处理消息
Handler target;
//标记该Message是否在被使用
int flags;
//时间,在MessageQueue中实现排序
long when;
//用于实现单链表,以链表实现Message池
Message next;
//链表头指针
private static Message sPool;
上方是能够承载的消息,在每一个上面都加了注释,有兴趣的朋友可以瞄一下,接下来我们看构造器:
/**
* Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
从源码上来看,构造器啥也没干,Java中如果不写构造方法的时候,不是会有一个空的默认构造方法么?但是那个方法没有注释啊,这里主要是为了咱们来看这个注释:“想要获取一个Message的首选方法应该是Message.obtain()”,还记得头注释么,也这么说过,那我们来看一下这个方法干了什么
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
从这个源码上看,这个方法首先使用synchronized关键字开启了一个锁,加锁就是为了防止脏数据的产生所产生的一个产物,Handler通信机制中,我们程序员在使用时,都是子线程对UI线程的消息传递,怎么可能会出现脏数据呢?有兴趣的朋友可以在源码上搜索一个下这个方法都在哪里调用过,就知道了,并不是单单只有Handler在使用Message这个对象。然后在这个方法里我们看到如果sPool是空的它会给你new一个Message对象,否则的话它把是否在被使用的标记m.flags改成了0,m.next对象给设置成了null,然后把sPool赋值的m的这个Message对象给你用了,在这里采用了一个回收再利用的方式,避免有能用的而你在去创建一个所导致的内存浪费。
消息销毁回收的源码在下面,只是不需要我们主动调用,但为了了解Message我们还是来看一下源码
void recycleUnchecked() {
// 当消息保留在回收的对象池中时,将其标记为正在使用.
// 清除所有其他细节.
flags = FLAG_IN_USE;//标记该Message是否在被使用
what = 0; //用于区别消息的类型
arg1 = 0;//携带数据,空消息所携带的数据
arg2 = 0;//携带数据,空消息所携带的数据
obj = null;//进程间通信时只能携带系统定义的parcelable对象
replyTo = null;//Messenger进行进程间通讯时,用于实现双向通讯
sendingUid = UID_NONE;//发送消息用的uid
workSourceUid = UID_NONE;//此消息排队的uid
when = 0;//存放时间,用于在MessageQueue中实现排序
target = null;//消息的处理目标对象,用于处理消息
callback = null;//消息所携带的代码语句数据
data = null;//携带数据,可存放多条
synchronized (sPoolSync) {
//sPoolSize:池的当前大小
//MAX_POOL_SIZE:池的最大容量
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
我就把上方源码所有的字段全都加上了注释以便理解。recycleUnchecked方法回收之前先重置Message的状态,包括设置为未使用状态和清空所写携带的数据。可以看到,在将Message放回对象池的时候会首先判对象池的容量是否已经满了,只有未满的时候才会回收进对象池,对这个当前池的大小在这个方法中做了一次+1的操作,否则将会丢弃等待GC的回收。那-1的呢,还记得上面的那个Message.obtain()方法吗?那里的源码第8行做了池的-1操作,
因此,虽然Message的构造方法是public的,但是系统建议我们使用obtain方法来获取对象,因为这样可以从对象池中获取Message,避免了多次分配对象。
message类中有一个方法要特意讲一下,这个方法在Handler中和在MessageQueue中有用到
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
如果是异步消息,这里则会返回true。因为在消息的入列和出列的时候都有使用这个方法,所以这里说明一下
3:Looper
Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。
还是先看构造器
final MessageQueue mQueue;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这里可以看到,构造中就干了俩事,一个是给成员变量mQueue做了一个显式初始化,而真假值的参数是来控制消息队列是否可退出的,true就是可退出的。另一个就是获取了一个当前工作线程的引用,因为Looper对外提供了几个方法中使用到了线程,所以这里获取了一下当前工作线程的引用,但是问题来了,这个构造器是私有的,我们外部不能使用,那来看一下他的头注释是咋说的,其中有这么一段话
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call
{@link #prepare} in the thread that is to run the loop, and then
{@link #loop} to have it process messages until the loop is stopped.
说你想使用Looper必须要调用prepare和loop两个方法才行,这两个方法是干什么的呢,prepare就是初始化Looper用的,loop方法就是真正的开启一个循环的方法,之所以Looper是循环器,就是因为loop这个方法,在Looper中最重要的方法有如下三个
prepare:初始化
loop:开始循环
quit(不安全-API1)、quitSafely(安全-推荐使用-API18):结束循环
我们先来说下第一个prepare
上源码
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
这个静态的初始化方法prepare也是一个重载,只不过方法的访问权限不同而已。源码中我们可以看到,成员变量sThreadLocal中只让你有一个Looper,在第6行判断的,你给多了它就给你抛异常,但你要是没有Looper,它就给你创建一个。其实在Looper的类中还有另外一处可以初始化的地方在头注释中没有提到
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
源码中我们可以看到,这里第8行调用了一次prepare(false);方法,但是这里给的是一个false,意味着消息队列不能退出,源码中我们能看到,Google并不建议我们使用该方法,这个方法虽然是public的,但是你在应用开发过程中要是使用了,人家就抛一个异常给你,因为主线程的Looper已经初始化过了!
loop循环
这个方法有点长,所以我就挑点咱们平时发消息使用的那点主要的讲一下哈
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
...
for (;;) {
Message msg = queue.next(); // 可能会阻塞
if (msg == null) {
// 没有消息表明消息队列正在退出.
return;
}
...
msg.recycleUnchecked();
}
...
首先来看注释,“在此线程中运行消息队列,但是别忘了调用quit()结束loop循环。”接下来我们看源码,在源码的第6行是使用了方法myLooper()来获取一个当前对象,为什么不用this呢?从抛出的异常中可以看出来,是为了防止你没调用Looper.prepare()方法去初始化looper就直接调用了loop()方法。在然后是获取成员变量中的消息队列mQueue赋值到局部变量中queue上,局部变量和成员变量的不同呢?看这里!在然后就是进入无限循环中了,为什么这里使用for (;;)而不是while(true)呢?因为for (;;)指令少,不占用寄存器(整理中,现在跳转的是百度?),而且没有判断跳转,所以同为死循环,但是for (;;) 更优一点。我记忆当中早期Android这里使用的就是while(true)。for循环中就是处理消息队列中的内容Message了。最后消息使用完毕,销毁!但是否真的销毁了这个对象,前面说Message时有讲过
但是这里是一个死循环,如果使用空参数Handler那就是默认使用主线程(UI线程)的Looper,那这里明明是死循环,为什么却不会anr异常呢?并且主线程的死循环一直运行是不是特别消耗CPU资源呢? 导致功耗很高,其实不然,上方代码片段的13行的注释中有写道,这里可能是阻塞的,要不然没有消息了msg为null在往下执行就是return返回跳出循环了,意味着你的Activity线程执行完了,但是Android的所有的UI操作都是通过Handler来发送消息的,那这里loop中的循环都结束了还怎么做UI操作呢!阻塞这个方法是调用的消息队列中的方法,下面我们讲MessageQueue的时候在来说说这个事!
阻塞这里就涉及到了Linux pipe/epoll机制,简单说就是在主线程的消息队列MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,所以不会出现前面说的问题。
quit和quitSafely退出循环
如果你是在一个子线程中开启了一个looper,并调用了loop()方法,那当前子线程相当于阻塞于此了,你不停掉loop的话,那后面的具体代码将不会被执行,举个例子
new Thread() {
@Override
public void run() {
Looper.prepare();
Looper.loop();
Log.v("tag", "msg");
}}.start();
上方代码中日志将永远不会被打印,因为Looper中的loop()方法中的循环没有被停掉,所以线程执行到第5行就被阻塞住了,如果想要日志打印那就必须调用Looper.myLooper().quit();停掉循环,线程才会继续往下面执行,打印日志。
那quit是怎么停止的loop()呢?按照惯例,上源码
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
注释中说“调用loop()方法终止,将不再处理消息队列中的消息。在请求looper退出后,向队列发送消息的任何尝试都将失败。使用此方法可能不安全,因为某些消息可能无法传递在循环终止之前,这里指的是延时消息。建议使用quitsaffe()来确保一切未了结的工作都要有条不紊地完成。"那我们来看一下quitsaffe()方法都干了什么?
public void quitSafely() {
mQueue.quit(true);
}
单从源码上看,就一个真假值不同,别的啥也没干,那里面这个真假值到底起了什么作用呢?接下来我们来分析下MessageQueue
4:MessageQueue
从前面讲过的东西,我自己挖了几个坑
- 消息队列是怎么退出的,构造器的参数的真假值有什么具体作用
- 线程的阻塞也是在消息队列中,那它是怎么阻塞的
- 消息队列为什么要排序
- 还有一个进程间通信
接下来按照前面讲解的流程来讲解,在其中夹杂刚刚的这几个问题
上构造器
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
先来看一下这个真假值得参数,前面讲过,这个真假值是控制此消息队列是否可退出的,通过代码搜索,在本类中的成员变量mQuitAllowed只有在一个地方有使用过
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}...
}
从上方的源码片段中,我们可以看到,如果是主线程在使用此消息队列的情况下是不让你退出的。还记得最前面说的重要性么?那里我就说过,一个APP的生命从生到死,都跟Handel有关,而Handler对应一个looper,一个Looper对应一个MessageQueue,所以主线程在使用Handler的时候是不让你退出的,否则你的ui线程讲无法在继续使用!
接下来说一下构造器中的第3行,nativeInit是一个Native方法,从名字来看,是一个初始化的方法
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
上方源码可以看到该方法在native层创建了一个NativeMessageQueue对象,我们可把它理解成NativeMessageQueue是Java层MessageQueue在Native层的代表。这里把这个代表对象地址返回到java层保存了起来。那NativeMessageQueue的构造函数又干了什么呢?
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
这里我们看到,NativeMessageQueue在它的构造函数中获取了一个Looper,Native的Looper是Native世界中参与消息循环的一位重要角色。虽然它的类名和Java层的Looper类一样,但两者其实并无任何关系!源码就不贴了,有兴趣的朋友看这里:http://androidxref.com/9.0.0_r3/xref/system/core/include/utils/Looper.h
private native static long nativeInit();//初始化Native层代表
private native static void nativeDestroy(long ptr);//销毁Native层代表
private native void nativePollOnce(long ptr, int timeoutMillis); //线程阻塞
private native static void nativeWake(long ptr);//唤醒线程
private native static boolean nativeIsPolling(long ptr);//工作线程是否休眠
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);//原始文件描述
主要我们讲的是Java层面的,并且在Java层的MessageQueue中还并不是只有这么一个Native方法,所以Native我们就不在继续深刨了,主要在Java层我们来聊一聊
MessageQueue消息队列,既然是消息队列,那我们先来说说主要的入列和出列这两个方法。
入列方法是在Handler的enqueueMessage方法中调用的MessageQueue中的enqueueMessage方法。
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;
// p指向队列的头部
Message p = mMessages;
boolean needWake;
// 如果p为空,表明消息队列中没有消息,那么msg将是第一个消息
// 如果when = 0或者when < p.when,说明是即时消息,把该消息放入队首,队首是最先出队的
// needWake需要根据mBlocked的情况考虑是否触发
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 判断唤醒条件,当前消息队列头部消息是屏障消息,并且当前插入的消息为异步消息,
// 并且当前消息队列处于无消息可处理的状态
needWake = mBlocked && p.target == null && msg.isAsynchronous();
// 如果p不为空,表明消息队列中还有剩余消息,需要将新的msg按照从小到大的时间顺序放入到对应的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
// 因为消息队列之前还有剩余消息,所以这里不用调用nativeWake去唤醒线程
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 只要插入的Message不是异步的,needWake的值就是mBlocked的值,而mBlocked的值会在出队方法next中
// 当线程阻塞的时候设为true。而这里当有非异步的Message入队时会调用nativeWake方法将线程唤醒来处理消息
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
该加的注释我都加了,enqueueMessage方法是按照时间从小到大的顺序将消息插入在相应的位置。因为MessageQueue中的队列是由Message实现的,也就是Message和它的属性next实现的单链表,而单链表只能按照从表头至表尾的顺序访问。那为什么要按照时间排序呢?因为我们还有延时消息。接下来我们看一下出列的方法大致就能明白一二了
出列方法是在Looper的死循环中调用的next()方法,源码如下
Message next() {
// Native层代表NativeMessageQueue的对象指针
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 线程阻塞之前要干的事
if (nextPollTimeoutMillis != 0) {// 线程不阻塞不会进来
// 通知内核程序我要休息了(阻塞)
Binder.flushPendingCommands();
}
// 调用Native方法,参数1为NativeMessageQueue的指针,参数2为0则不阻塞,也就是第一次循环不阻塞,否则线程等待,开始阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 尝试检索下一个message。如果找到则返回
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// mMessages用来存储消息,这里从其中取一个消息进行处理
Message msg = mMessages;
// 如果msg不为空但是target为空,我们的target肯定不会为null
// 还记得Handler中的enqueueMessage()方法么,那里有设置
// 所以这里的msg不是我们设置而是系统在初始化的时候设置的障碍
if (msg != null && msg.target == null) {
// 被障碍物挡住了。在队列中查找下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 正常情况下的msg
if (msg != null) {
if (now < msg.when) {
// 下一条消息尚未准备好。设置一个超时,以便在准备好时唤醒.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {//队列中没有消息
nextPollTimeoutMillis = -1;// 设置一个非0的值,准备阻塞线程
}
// 处理完所有挂起的消息后,立即处理退出消息
if (mQuitting) {
dispose();
return null;
}
// 获取空闲时处理任务的handler 用于发现线程何时阻塞等待更多消息的回调接口。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有要运行的空闲处理程序。循环等待更多。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行空闲处理程序.
// 我们只在第一次迭代时到达这个代码块
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 释放对处理程序的引用
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果不保存空闲任务,
if (!keep) {
synchronized (this) {
// 执行完成后直接删除
mIdleHandlers.remove(idler);
}
}
}
// 重置空闲的handler个数,因为不需要重复执行
pendingIdleHandlerCount = 0;
//当执行完空闲的handler的时候,新的native消息可能会进入,所以唤醒Native消息机制层.
nextPollTimeoutMillis = 0;
}
}
上方就是消息出列的方法,大部分我都加了注释,跟message相关的,就是根据时间来拿消息,然后丢出去。因为在入列的时候我们做了时间的排序,所以这里就直接根据最近的那个时间来拿消息即可。
这个入列和出列的两个方法中,直接涉及到了两个Native方法,一个是在入列方法第57行nativeWake(mPtr),另一个是在出列方法中的16行nativePollOnce(ptr, nextPollTimeoutMillis),他们俩一个是唤醒线程的,一个是阻塞线程的。
这里涉及到了一个Native的方法nativeWake唤醒线程,我们来看一下源码!
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
// 取出NativeMessageQueue对象
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
// 调用它的wake函数
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
// 调用了Looper的wake()函数
mLooper->wake();
}
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
// 向管道的写端写入一个8字节的1
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",mWakeEventFd, strerror(errno));
}
}
}
因此,管道的读端就会因为有数据可读而从等待状态中醒来
在上方消息出列的方法中出现了一个IdleHandler。看着跟我们说的消息的出列入列没啥太大关系的东西,这是啥?从注释里面看,跟它相关的好像都有写“空闲”。它是当线程正在等待更多消息时,回调的一个接口,MessageQueue对外提供addIdleHandler(@NonNull IdleHandler handler) 方法添加空闲时任务,它到底干了啥呢?通过代码搜索看到好几处都调用过这个方法,其中有一个咱么都熟悉的ActivityThread,反正我就只有这里熟悉一些,那我就只说这了,是怎么掉的这个方法呢?
final GcIdler mGcIdler = new GcIdler();
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
而上面这个方法会在什么时候被调用呢?在搜索,是在ActivityThread的内部类H中,在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中,那GvIdler是啥?也是一个内部类,不长,我贴出来
//GC任务
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
//执行后,就直接删除
return false;
}
}
// 判断是否需要执行垃圾回收。
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//获取上次GC的时间
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
就是获取上次GC的时间,判断是否需要GC操作。如果需要则进行GC操作。
剩下那几个我没细看,有兴趣呢可以自己瞅瞅,这都已经偏离主话题了。