继而调用sendMessagAtTime
方法:
public boolean sendMessageAtTime(@NonNull 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);
}
眼尖的小伙伴就会发现,等等不对,这代码中出了一个叛徒,啊不对,出了一个奇怪的东西。没错,就是刚才流程图中出现的这个MessageQueue
。你看,我没有胡说吧,这个MessageQueue
是实打实存在的,并且被作为参数一起传给了enqueueMessage
方法。其实无论你是如何使用Handler发送消息,结果都会走到enqueueMessage
方法中。
这是方法的调用链:
可以看到无论如何,最后都会走到enqueueMessage
方法中。这个enqueueMessage
方法具体做了什么事呢:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage
一共做了两件事情,一个是给Message
赋值,一个是调用传进来的这个MessageQueue
的enqueueMessage
方法。注意啊,最后这个enqueueMessage
方法是在MessageQueue
中的,已经不再是Handler
的方法了,也就是说,调用走到了这里。事件的流向已经不归Handler
管了。
Handler::enqueueMessage
方法中第一行msg.target = this;
,这个this是什么呢?这个this
在handler
方法中自然是handler
本身了,也就是说这一行代码将handler
自身赋值给了Message
对象的target
字段。我们可以看以下这个target
字段的定义:
//简化后的代码
public final class Message implements Parcelable{
@UnsupportedAppUsage
/package/ Handler target;
}
啊,这样明白了,也就是说每个发出去的Message
都持有把它发出去的Handler
的引用,对不对?
没错事实就是这样,每个发出去的Message
对象内部都会有个把它发出去的Handler
对象的引用,也可以理解Message
这么做的目的,毕竟Handler
把它发射出去了,它不得知道是谁干的,好随后找它报仇么。那么我们继续下一步,msg.setAsynchronous(true)
这一行代码是设置异步消息的,这里暂时先不管它。我们先看queue.enqueueMessage(msg, uptimeMillis)
这行代码。也就是从这行代码,Message
就可以和Handler
说拜拜了您讷。
MessageQueue
Handler
这个mQueue
就是上文我们提到过的MessageQueue
对象,在上面的介绍说也说了,这货就是个骗子,明明起名是Queue
,却是单链表。你可能误会Google工程师了,名字也确实没什么错了,从机制上看确实很像队列。队列是什么特性啊,先进先出对吧。这个先后就是按时间来划分的,时间靠前的就在前面时间靠后的就在后面。而在这个单链表中也确实是这样实现的,按照时间的先后排序。这个就先不多讲了,一会讲如何实现的消息延时发送的时候会讲到这个。
到这里你可能有疑惑了,这个MessageQueue
是什么鬼,从哪里冒出来的。你可能还记得,在上面的sendMessageAtTime
方法中有这么一行:
MessageQueue queue = mQueue;
那么这个mQueue是在哪里被赋值的呢?当然是在构造方法中啦~
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;
}
不对啊, 你TM骗我,在最开始你继承的Handler可没有这几个参数。哎呀,小伙子别心急,你看这个无参构造方法不也调用的这个方法么。
public Handler() {
this(null, false);
}
在这个有参数的构造方法中呢,可以看到有这么两行:
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;
我们在Handler
中使用的mQueue
就是在这里赋值的。这里的赋值可不简单,它拿的是人家Looper
的MessageQueue
作为自己的MessageQueue
,**而且在上面的代码中有一个很关键的点,就是调用Looper.myLooper()方法中获取这个Looper对象,如果是空的话就要抛出异常。**这一点非常关键,我们先做个记号,一会回过头来会看这一行代码。你就会明白它的作用了。
现在先不研究Looper
,我们继续看我们的MessageQueue
。上面说到,最后发送消息都调用的是MessageQueue
的queue.enqueueMessage(msg, uptimeMillis)
方法。现在我们已经拿到了queue
,进去看看这个方法它做了什么。
// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到队列头部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
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 {
//【3】消息插到队列中间
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
主要分为3个步骤(见以上代码标注)。
mMessages
是队列的第一消息,获取到它- 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
- 如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。
如何判断这个位置呢?依然是通过消息被执行的时间。
通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。
可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。
想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。
好了,MessageQueue
在Java
层的分析到这里就结束了。
等等,这就结束了?
没错,到这一步,消息已经添加到了这个名为队列实为单链表的队列中。
不对啊,我handleMessage
方法如何被调用呢?消息添加进去就完了?说好的线程切换呢?
其实到这一步真的就结束了,最起码在Java层是结束了。消息到这一步被添加到了队列中,Handler
和MessageQueue
在发送的过程中做的工作已经做完了。但是既然有队列,那么不可能说光添加不读取把。不然我添加了有什么用?
是的,接下来就是Looper
大展神威的时候到了。
Looper
在上面提到了,Handler
中的MessageQueue
对象其实就是Handler
中的Looper
它的MessageQueue
,Handler
往MessageQueue
中添加消息,其实就是往Handler
的Looper
所持有的MessageQueue
中添加对象。可能有点绕,但是需要明白的是这个MessageQueue
是Looper
的,不是Handler
的。明白了这一点,你就能很好的理解后面发生的事情。
可能有的小伙伴会说了,这个Looper
哪来的,我创建Handler
的时候从没看见过它出现啊。没错,在使用Handler
的时候它确实没出现过,但是大家还记得Handler
中两个参数的那个构造方法嘛?就是下面这个:
//Handler.java
//省略部分代码
public Handler(@Nullable Callback callback, boolean async) {
//敲黑板,划重点就是这一句!!!!
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;
}
在这一句中Handler
通过Looper.myLooper
方法获取到了Looper
对象,当然,也有可能没获取到。不过,你如果没获取到就惨了,就要抛异常了。
在职责分析中我们提到了, 这个Looper
对象作为消息循环的核心,不断从它的MessageQueue
中取出消息然后进行分发。
说人话可以不?
刚才说到MessageQueue
那个队列中那么多的消息没人拿,MessageQueue
的老板Looper
看不下去了,说你这也太浪费了,来我拿吧,然后它专门负责一个个拿,然后看这是谁发的,然后让谁去处理。
那我们看看这个Looper.myLooper()
方法做了什么事情呢。它是如何返回一个Looper
对象的呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal
又是什么鬼?咱们看一下它的定义
//sThreadLocal.get() will return null unless you’ve called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
可以看到这个sThreadLocal
是一个ThreadLocal
类,并且它的泛型是Looper
对象。ThreadLocal
提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal
中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
呀呵,源代码中还有行注释,这行注释的意思是除非您已调用prepare(),否则sThreadLocal.get()将返回null。这行注释就有趣了,刚才我还寻思这个ThreadLocal
的get方法得有数据才能返回,可这个数据是啥时候塞进去的呢?你这注释就告诉我了,只有我调用了prepare()
方法,才有值啊。那我们就去看看这个方法做了些什么。
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(boolean quitAllowed)
方法,而这个方法首先判断,如果sThreadLocal
有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare
方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常。我好难哦~不过大家还记得上面我们重点关注的一个内容吗,在Handler中的有参构造函数中有这么一行代码会报异常:
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;
如果Looper
为空就抛异常,现在我们知道了,什么时候Looper
为空呢?没有调用prepare
方法的时候会为null
,也就是说在构造Handler
之前,必须得有Looper
对象,换言之,**在构造Handler之前,必须调用Looper
的prepare
方法创建Looper
。**这句话非常重要,所以我又是下划线又是加粗的,一定要记住这句话。在后面自定义一个Looper的时候会用到。
接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));
做了什么吧,它是如何塞进去的呢
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set
方法首先获取到了当前的线程,然后获取一个map
。这个map
是以键值对形式存储内容的。如果获取的map
为空,就创建一个map
。如果不为空就塞进去值。要注意的是,这里面的key
是当前的线程,这里面的value
就是Looper
。也就是说,线程和Looper
是一一对应的。也就是很多人说的Looper
和线程绑定了,其实就是以键值对形式存进了一个map
中。没什么高大上的。你来你也行。
而这个Looper的构造方法我们也得去看一下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper
的构造方法中,可以看到它创建了一个MessageQueue
,没错就是那个被Handler
无耻使用的MessageQueue
。需要注意的一点,上面的分析中提到了prepare
方法必须调用但也只能调用一次,调用以后就会创建Looper对象,也就是说一个线程中只会创建一个Looper
对象,而一个Looper
对象也只会创建一个MessageQueue
对象。
现在我们来梳理一下这个流程哈~
首先创建一个无参数的Handler
,在这个Handler
的构造方法中又去获取Looper
对象,当然获取Looper
对象其实是为了它的MessageQueue
,Handler
巴结上了人家Looper
对象的MessageQueue
以后,发送消息的时候,把要发送的消息给了MessageQueue
,添加到了队列中。是不是感觉缺少了什么?没错,好像在这个里面Looper
的作用没体现出来,说好的分发消息呢?而且你刚刚说了得调用prepare()
方法才会创建Looper
,可我没调用过这个方法啊。那这个Looper
谁创建的?
刚才提到了,Looper
在创建的时候会被当成value
塞入到一个map
中去,这个map
是ThreadLocal
。而key
就是创建Looper
时所在的线程。也就是所谓的Looper
和线程绑定。我们一般在用的时候从没创建过Looper
,但是我们知道handle
中的回调handleMessage
方法是运行在主线程中的。Looper
的职责不就是分发消息么,也就是说Looper
对象在主线程中把消息分发给了Handler
。那么这下就明白了,在我们创建Looper
的时候,Looper
所在的线程是主线程,换言之,与这个Looper
绑定的线程就是主线程。
明白了,我这就去和面试官对线。
既然是主线程,那么大家应该知道,主线程是谁创建的?ActivityThread
类。ActivityThread
类也正是整个app的入口。以前我也很好奇,既然Android
是用Java
写的,按理说Java
不应该是有个什么main
方法么?怎么我写Android
没用过这个main
方法呢?其实呢,在ActivityThread
中就有这个main
方法,它是程序的入口,也就说当你点开app
以后,首先会进入到这个main
方法中,然后做了一大堆事情,这里就不分析了。你只需要知道,这个main
方法才是真正的入口。
那我们来看看这个main方法到底干了什么事情:
//ActivityThread.java
//省略部分代码
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);
Process.setArgV0("");
//1 敲黑板,划重点,就是这一句!
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, “ActivityThread”));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//2 敲黑板,划重点,这一句!
Looper.loop();
throw new RuntimeException(“Main thread loop unexpectedly exited”);
}
这段代码是不是很符合我们平常写的java程序呢?熟悉的main方法又回来了。main方法中可以看到,它调用了Looper的prepareMainLooper
方法:
public static void prepareMainLooper() {
//设置不允许退出的Looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
可以看到注释1,这个方法最终还是调用了Looper
的prepare
方法,这个方法干嘛的?创建Looper
并且把它和当前线程一起塞进map
中的啊。当前线程是哪个线程?主线程啊!
一切到这里就真相大白了,在APP
启动的时候,入口方法中已经自动帮我们创建好了Looper
,并且也自动的帮我们和主线程绑定了。也就是说我们平常用的Handler
中的Looper
就是主线程中创建的这个Looper
。细心的小伙伴应该会发现,这个prepareMainLooper
方法你是不能调用的。为啥?因为这个方法在入口的时候执行了一次,所以里面的sMainLooper
不为Null了,如果你在调用一次,不就要抛异常了么~
现在Looper
也有了,Looper
的MessageQueue
也有了。接下来该分发消息了吧?我Handler
发送消息可是已经很久过去了,你这里分析一大通,我还干不干活了?
好,我们现在先假设一个场景。
你买了一个快递,你知道迟早会给你送到,但是不确定到底什么时候才会送到。你想早点拿到快递应该怎么做?
你会不停的问快递公司,我的快递到哪了,到哪了。当然,现实中一般都是等快递员打电话才去拿快递~问题在于,这是程序。
Looper
虽说要分发消息,但是它又不知道你什么时候会发送消息,只能开启一个死循环,不断的尝试从队列中拿数据。这个死循环在哪里开始的?没错就是注释2处,Looper.loop()
开启了一个死循环,然后不断的尝试去队列中拿消息。
// Looper.java
public static void loop() {
//拿到当前线程的Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
//拿到Looper的消息队列
final MessageQueue queue = me.mQueue;
// 省略一些代码…
//1 这里开启了死循环
for (;😉 {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
//省略一些代码…
} catch (Exception exception) {
//省略一些代码…
throw exception;
} finally {
//省略一些代码…
}
//省略一些代码…
msg.recycleUnchecked();
}
}
在循环中Looper
不停的取出消息,拿到Message
对象以后,会去调用Message
的target
字段的dispatchMessage
方法,这个target
字段还有印象吗?没错,就是发送它的Handler
,message
在被发送出去的时候就已经暗暗记下了是谁发送出去的。现在轮到它报仇了~
我们可以跟进看一下这个dispatchMessage
方法:
//Handler.java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,消息会先分发给Meesgae
的callback
,我们没有定义这个callback
,那我们接下来看,还有一个mCallback
。这个mCallback
是创建Handler
的时候可以选择传一个CallBack
回调,里面依然是handleMessage
方法。也就是说你可以自定义一个类继承Handler
,重写handleMessage
方法,也可以直接new
一个Handler
传一个回调。当然,这个都很简单,我就不再赘述了,大家可以自行尝试体验。
我们关注重点,当Looper
拿到Message
以后,并且根据Message
的target
字段找到了发送消息的Handler
,紧接着调用了Handler
的handleMessage
方法。重点来了,这个Looper
是在哪个线程运行的?主线程,它调用方法是在哪个线程运行的?依然是主线程!handleMessage
方法此时在哪个线程运行的?依然是主线程!不知不觉中,线程已经切换过来了,神奇不?其实并不神奇,其实就是主线程中的Looper
不断的尝试调用handleMessage
方法,如果有消息就调用成功了,此时handleMessage
方法就是在主线程中调用的。而handler
在哪个线程,Looper
并不关心,我反正只在主线程调用你的handleMessage
方法。这就是线程切换的本质。就是没有线程切换,主线程的Looper
不断的尝试调用而已。
可能有的小伙伴已经懵逼了,我们再次从头到尾梳理一下哈~
mainThread
中ActivityThread
首先创建了一个运行在主线程的Looper
,并且把它和主线程进行了绑定。Looper
又创建了一个MessageQueue
,然后调用Looper.loop
方法不断地在主线程中尝试取出Message
Looper
如果取到了Message
,那么就在主线程中调用发送这个Message
的Handler
的handleMessage
方法。- 我们在主线程或者子线程中通过
Looper
.getMainLooper
为参数创建了一个Handler
。 - 在子线程中发送了
Message
,主线程中的Looper
不断循环,终于收到了Message
,在主线程中调用了这个Handler
的handleMessage
方法。
这里需要注意的是,Looper.loop
方法中取到的Looper
对象并不一定就是主线程的,因为它是取出当前线程的Looper
对象。只不过在ActivityThread
这里是主线程,所以拿到的是主线程的Looper
对象。所以如果我们要在子线程中创建一个Looper
也是可以的,一会我们就实现一下。
到这里可能有的小伙伴还是懵逼,我还是不太明白怎么切换的线程。我们通过一个比喻很好的解释一下
首先有一个小学生小明,小明的任务是写作业。然后有一个老师,老师的任务是批改作业。这个班里还有一个学习委员,学习委员的任务就是负责收作业然后交给老师去批改。
一般情况下,老师是学校已经聘请好的,我们不需要自己去聘请老师。老师一般也就只在办公室批改作业,办公室我们可以理解为主线程。学校就是我们的app
。老师就是Looper
对象。而小明同学就是Handler
,学习委员就是MessageQueue
,作业就是Message。老师(Looper
)在办公室(主线程)不断的从学习委员(MessageQueue
)那里拿到下一本要批改的作业(Message
),老师突然发现作业里有错误,老师很生气,于是就从作业本上的姓名知道了是谁写的这个作业(对应Message
的target
字段),于是老师把小明(Handler
)叫到办公室(主线程),让小明在办公室(主线程)把作业改好(handleMessage
)。在这个例子中,小明作为Handler,他可以在任何地方写作业(sendMessage
),也就是说他可以在家里写作业,可以在教室写作业,也可以在小公园写作业,这里的各个地方就是不同的线程。但是写完作业以后一定要交给学习委员,学习委员手里有一摞作业,这一摞作业就是消息的队列,而学习委员就是MessageQueue
,他负责收集作业,也就是收集Message
。老师在办公室批改作业,发现出错了,就把小明叫到了办公室,让小明在办公室改错。在这里,办公室就是主线程,老师不会管小明是在哪里写的作业,老师只是关心作业出错了,需要小明在办公室里改错。小明在办公室里改错这就是handleMessage
方法运行在了主线程。但是也有个问题,不能说你小明在办公室改错改个没完没了,那岂不是影响了后面同学作业的批改?如果小明真的改错改的没完没了,也就是在主线程上作耗时操作很久,那么老师也无法进行下一个同学的作业批改,时间一长,教学就没法进行了。这就是著名的ANR问题。不知道这样比喻,小伙伴们能不能理解线程切换的意思和ANR的意思。如果还不能理解,那么你来砍我吧~
Looper和ANR
很多面试官喜欢问,Looper
的loop
方法是个死循环,而loop
方法又是运行在主线程的,主线程上有死循环为什么不会导致ANR存在呢?
其实这里面很有趣的一个点就是,很多小伙伴把Looper
的loop
方法当做一个普通方法来看待,所以才会有这样的疑问。但是这个loop
方法并不是一个普普通通的方法。
我们先思考一点,如果我们写一个app
,里面一行代码也不写的话,app会不会崩溃?
答案显而易见,是不会的。
可是在上面提到了,本质上App
就是一个Java
程序,Java
程序就有main
方法,在ActivityThread
类中也确实有这个main
方法。我们一般写java
程序的时候,是不是main
方法中的代码执行完,程序也就结束了。但是app
并没有,只要你不退出,它一直运行。那这是为什么呢?
很多小伙伴应该想到了,没错,让程序不退出的话,写一个死循环,那么main
方法中的代码永远不会执行完,这样程序就不会自己退出了。Android当然也是这么干的,而且不止Android,基本上所有的GUI程序都是这么干的。正是因为Looper.loop
方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。那你可能有疑问了,那既然这里有死循环了,那我其他的代码怎么运行?界面交互怎么办?你问到点子上了。
本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message
发送到了MessageQueue
中。由Looper
来进行分发,然后在进行处理。用人话来说就是,我们的Android程序就是运行在这个死循环中的。一旦这个死循环结束,app也就结束了。
那么ANR
是什么呢?ANR
是Application Not Responding也就是Android程序无响应。为什么没响应呢?因为主线程做了耗时操作啊。可我还是不明白,明明Looper
的loop
方法就是阻塞了主线程,为什么不ANR
呢。那我们就来说道说道,什么是响应?响应就是界面的刷新,交互的处理等等对吧。那么这个响应是谁来响应的?没错,就是loop
方法中进行响应的。没响应什么意思?就是loop方法中被阻塞了,导致无法处理其他的Message
了。
所以结论就来了,主线程做耗时操作本质上不是阻塞了主线程,而是阻塞了Looper
的loop
方法。导致loop
方法无法处理其他事件,导致出现了ANR事件。对比小明这个比喻的话,就是因为小明在办公室里没完没了的改作业,占用了老师的时间,让老师没法批改下一个同学的作业,才导致了教学活动无法正常进行。而老师不断的批改作业,这本身就是正常的教学活动,也正是因为老师不断批改作业,同学们才有提高,教学才能继续。
Handler在Java层几个需要注意的点
如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。如下是创建了一个子线程的Looper对象:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定义消息处理逻辑.
}
};
Looper.loop();
}
}
在生成消息的时候,最好是用 Message.obtain()
来获取一个消息,这是为什么呢?
// Message.java
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();
}
可以看到,obtain方法是将一个Message对象的所有数据清空,然后添加到链表头中。sPool就是个消息池,默认的缓存是50个。而且在Looper的loop方法中最后一行是这样的
msg.recycleUnchecked();
Looper在分发结束以后,会将用完的消息回收掉,并添加到回收池里。
什么是内存泄露?简而言之就是该回收的东西没有回收。在Handler中一般是这样使用:
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子线程中通过自定义的 Handler 发消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i(“HandlerActivity”, “主线程:handleMessage: ${msg.what}”)
}
}
}
乍一看没有问题,但是有没有想过一个问题,就是说再发送延时消息之前,app推出了,那么handleMessage方法还会执行吗?答案是会的。为什么?我明明退出了,为什么还会执行呢?其实这和java有关系
MyHandler
是HandlerActivity
的内部类,会持有HandlerActivity
的引用。在进入页面以后,发送了一个延时 1s 的消息,如果
HandlerActivity
在 1s 内退出了,由于Handler
会被Message
持有,保存在其target
变量中,而Message
又会被保存在消息队列中,这一系列关联,导致HandlerActivity
在退出的时候,依然会被持有,因此不能被GC
回收,这就是内存泄漏!当这个 1s 延时的消息被执行完以后,HandlerActivity
会被回收。
虽然最终结果还是会被回收,但是内存泄露问题我们也必须去解决,如何解决?
1.将
MyHandler
改为静态类,这样它将不再持有外部类的引用。可以将HandlerActivity
作为弱引用放到MyHandler
中使用,页面退出的时候可以被及时回收。2.页面退出的时候,在
onDestroy
中,调用Handler
的removeMessages
方法,将所有的消息 remove 掉,这样也能消除持有链。
什么是同步消息屏障?
在Looper的loop方法中通过Message msg = queue.next();
这么一行代码拿到Message进行分发,这个MessageQueue的next方法中有这么一行:
//MessageQueue.java
//省略部分代码
Message next() {
for (;😉 {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//1 这一行很关键,同步消息屏障的关键点所在
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
}
}
}
注释1下面的这一行代码,首先会判断msg
不为null,然后紧接着判断msg
的target
为null。我们知道message
的target
就是发送它的handler
,所有的message
都有一个handler
,这里怎么可能没有handller
呢?针对同步消息还真的是所有的message
都有handler
,而这里是异步消息。满足target == null
的消息就是异步消息。同步屏障
是用来阻挡同步消息执行的。说得好,那么同步屏障有什么用呢?
似乎在日常的应用开发中,很少会用到同步屏障。那么,同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。简而言之,如果在启动绘制之前,用户(开发者)插入了一个非常耗时的消息到队列中,那就会导致 UI
不能按时绘制,导致卡顿掉帧。,同步消息屏障就可以用来保证 UI
绘制的优先性。
Handler在C++层
如果你的目标是理解Handler在Java层是如何实现的,下面就不需要看了。下面主要讲解Handler在C++层是如何工作并实现的。
首先,细心的小伙伴们可能会有疑问。Looper一直处于死循环中,就像老师一直不断的问学习委员要作业批改,老师也是人,不会累么?你问对了,老师当然不会一直不断的问学习委员要作业,正常情况下,是有人交了作业以后,学习委员送过来,老师才会去批改。没有作业的时候,老师可能在休息,可能在玩游戏**。Looper也是一样,在消息队列为空的时候,Looper实际上处于休眠状态,只要当有Handler发送消息的时候,Looper才会被唤醒,去进行分发消息**。那么是怎么实现的呢?
在整个消息机制中,MessageQueue
是连接Java
层和Native
层的纽带,换言之,Java
层可以向MessageQueue
消息队列中添加消息,Native
层也可以向MessageQueue
消息队列中添加消息。
这是MessageQueue
中的Native方法:
结尾
我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料,免费分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
领取方式:关注+点赞+点击我的GitHub 免费获取!
息**。那么是怎么实现的呢?
在整个消息机制中,MessageQueue
是连接Java
层和Native
层的纽带,换言之,Java
层可以向MessageQueue
消息队列中添加消息,Native
层也可以向MessageQueue
消息队列中添加消息。
这是MessageQueue
中的Native方法:
结尾
我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料,免费分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。