public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
继而调用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<Looper> sThreadLocal = new ThreadLocal<Looper>();
可以看到这个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("<pre-initialized>");
//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方法不断地在主线程中尝试取出MessageLooper如果取到了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层几个需要注意的点
-
子线程Looper 如果要创建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导致的内存泄露问题 什么是内存泄露?简而言之就是该回收的东西没有回收。在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 绘制的优先性。
最后
其实要轻松掌握很简单,要点就两个:
- 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
- 多练。 (视频优势是互动感强,容易集中注意力)
你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
阿里P7Android高级教程
下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。

附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。
Android核心高级技术PDF资料,BAT大厂面试真题解析; 
制,导致卡顿掉帧。,同步消息屏障就可以用来保证 UI 绘制的优先性。
最后
其实要轻松掌握很简单,要点就两个:
- 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
- 多练。 (视频优势是互动感强,容易集中注意力)
你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
阿里P7Android高级教程
下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。
[外链图片转存中…(img-opjB7EU2-1630823693082)]
附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。
Android核心高级技术PDF资料,BAT大厂面试真题解析; [外链图片转存中…(img-QuOnR2bi-1630823693083)]
|