1、Handler机制简介
handler/Looper/Message/MessageQueue/thread

首要熟悉下怎样运用Handler?
//主线程
private Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case UPDATETEXT:
Log.e("handler","做ui线程中的事");
break;
default:
break;
}
}
};
//子线程:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "";
message.what = UPDATETEXT;
mHandler.sendMessage(message);
}
}).start();
}
什么是Handler
Handler一般的来说,便是咱们在线程之间处理音讯通知及使命调度的东西。
2、Handler被规划出来的原因?有什么用?
Handler的含义便是切换线程。作为Android音讯机制的首要成员,它办理着一切与界面有关的音讯事情。
常见的运用场景有:
1、跨进程之后的界面音讯处理
比方Activity的发动,便是AMS在进行进程间通讯的时分,经过Binder线程将音讯发送给ApplicationThread的音讯处理者Handler,然后再将音讯分发给主线程中去履行。
2、网络交互后切换到主线程进行UI更新
当子线程网络操作之后,需求切换到主线程进行UI更新。
总之一句话,Hanlder的存在便是为了处理在子线程中无法拜访UI的问题。
3、为什么主张子线程不拜访(更新)UI?
由于Android中的UI控件不是线程安全的,假如多线程拜访UI控件那还不乱套了。
那为什么不加锁呢?
- 会下降UI拜访的效率。自身UI控件便是离用户比较近的一个组件,加锁之后自然会产生堵塞,那么UI拜访的效率会下降,终究反应到用户端便是这个手机有点卡。
- 太杂乱了。自身UI拜访时一个比较简单的操作逻辑,直接创立UI,修正UI即可。假如加锁之后就让这个UI拜访的逻辑变得很杂乱,没必要。
所以,Android规划出了单线程模型来处理UI操作,再搭配上Handler,是一个比较适宜的处理方案。
4、子线程拜访UI的 崩溃原因 和 处理办法?
- 崩溃产生在ViewRootImpl类的checkThread办法中:其实便是判别了当时线程是否是ViewRootImpl创立时分的线程,假如不是,就会崩溃。
- 而ViewRootImpl创立的机遇便是界面被制作的时分,也便是onResume之后,所以假如在子线程进行UI更新,就会发现当时线程(子线程)和View创立的线程(主线程)不是同一个线程,产生崩溃。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
处理办法有三种:
- 在新建视图的线程进行这个视图的UI更新,主线程创立View,主线程更新View。
- 在ViewRootImpl创立之前进行子线程的UI更新,比方onCreate办法中进行子线程更新UI。
- 子线程切换到主线程进行UI更新,比方Handler、view.post办法。
5、Handler的作业机制?
5.1 首要函数
Handler的首要函数调用逻辑如下所示的流程图,不论是哪个函数,终究都调到Handler.enqueueMessage函数
graph TD
post-->sendMessageDelayed
postAtTime-->sendMessageAtTime
sendMessage-->sendMessageDelayed
sendEmptyMessage --> sendEmptyMessageDelayed-->sendMessageDelayed-->sendMessageAtTime-->Handler.enqueueMessage-->MessageQueue.enqueueMessage
5.2 履行流程

-
相关类
Handler机制的完结离不开与之相关的其他三个类,Message是Handler发送的音讯实体,大部分的音讯都是经过Message来封装传递的;MessageQueue是用来将音讯按次序排队的行列;Looper实质便是一个循环,不停的从MessageQueue中取出音讯然后处理。
-
履行进程
- 首要,如上图所示使命的开始是由创立一个Message开始的,Message创立结束后交给Handler目标发送,sendMessage和sendMessageDelay终究都是在底层调用了sendMessageAtTime()办法,将Message目标放入MessageQueue中的。
- 之后,由Looper的loop()办法循环从MessageQueue中取出Message目标,调用message.getTarget()获取到发送音讯的Handler目标,然后再调用handler.dispatchMessage()办法将信息分发给对应handler履行。
- 终究,Handler在dispatchMessage()办法中判别是否有callback 存在,存在则履行callback的onMessageHandler(),终究交由Message.callback履行;否则则履行handler的onMessageHandler()办法。
6、音讯是怎样发送到MessageQueue中的?
Message创立结束后交给Handler目标发送,sendMessage和sendMessageDelay终究都是在底层调用了sendMessageAtTime()办法,将Message目标调用到MessageQueue的queueMessage()放入MessageQueue中的。
Handler类
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
final MessageQueue mQueue;
final Looper mLooper;
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
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);
}
Looper类
final MessageQueue mQueue;
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
MessageQueue
Handler target;
7、音讯行列中的音讯是怎样存储的?为什么没有用“惯例容器类”。
链表特色
- 利用单链表进行存储,链表是一种非线性,非次序的物理结构,由n个节点组成。
- 链表选用的“见缝插针”的存储办法,不需求内存连续,靠next指针关联;
- 存储的时分是随机的,拜访办法是次序拜访;
7.1 为什么选用链表结构存储音讯,而不是用数组结构?
1、由于咱们履行进程中,堆里边或许现已建了很多个目标,假如咱们初始化一个数组时分,或许空间不行,也没办法很好的利用碎片空间。
2、Handler音讯存储与读取应该遵循先进先出,一般在队尾增加数据,在队首进行取数据或许删去数据。先发的音讯肯定就会先被处理。
可是,Handler中还有比较特殊的状况,比方延时音讯。延时音讯的存在就让这个行列有些特殊性了,并不能完全确保先进先出,而是需求依据时刻来判别,所以Android中选用了链表的办法来完结这个行列,也方便了数据的刺进。
7.2 详细的音讯存储进程?
- 音讯的发送进程,无论是哪种办法发送音讯,都会走到sendMessageDelayed办法,终究调用sendMessageAtTime办法。sendMessageDelayed办法首要计算了音讯需求被处理的时刻,假如delayMillis为0,那么音讯的处理时刻便是当时时刻。
- 然后便是关键办法MessageQueue的enqueueMessage办法。
- 首要设置了Message的when字段,也便是代表了这个音讯的处理时刻
- 然后判别当时行列是不是为空,是不是即时音讯,是不是履行时刻when小于表头的音讯时刻,满足恣意一个,就把当时音讯msg刺进到表头。
- 否则,就需求遍历这个行列,也便是链表,找出when小于某个节点的when,找到后刺进。
- 总之,刺进音讯便是经过音讯的履行时刻,也便是when字段,来找到适宜的位置刺进链表。
- 详细办法便是经过for死循环,运用快慢指针p和prev,每次向后移动一格,直到找到某个节点p的when大于咱们要刺进音讯的when字段,则刺进到p和prev之间。或许遍历到链表结束,刺进到链表结尾。
- 所以,MessageQueue便是一个用于存储音讯、用链表完结的特殊行列结构。
8、推迟音讯是怎样完结的?
- 推迟音讯的完结首要跟音讯的统一存储办法有关,即MessageQueue的enqueueMessage办法。。
- 无论是即时音讯仍是推迟音讯,都是计算出详细的时刻,然后作为音讯的when字段进程赋值。
- 然后在MessageQueue中找到适宜的位置(安排when小到大摆放),并将音讯刺进到MessageQueue中。 这样,MessageQueue便是一个按照音讯时刻摆放的一个链表结构。
9、Handler所发送的Delayed音讯时刻精确吗?
实际上,这个问题与线程安全性为同一个问题,多线程中线程一旦安全,时刻就不能精确;时刻一旦精确,线程就必定不安全。 所以,Handler所发送的Delayed音讯时刻根本精确,但不完全精确。 由于多个线程去拜访这个行列的时分,在放入对列和取出音讯的时分都会加锁,当第一个线程还没有拜访完结的时分,第二个线程就无法运用,所以他实际的时刻会被推迟。
10、怎样从音讯行列中取音讯然后履行?
由Looper的loop()办法循环从MessageQueue中取出Message目标(经过调用MessageQueue的next()办法), 调用msg.target获取到发送音讯的Handler目标,然后再调用handler的dispatchMessage()办法将信息分发给对应handler履行。
Looper类
final MessageQueue mQueue;
public static void loop() {
final Looper me = myLooper();
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
} finally {}
msg.recycleUnchecked();
}
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);//会堵塞的进程
synchronized (this) {
// Try to retrieve the next message. Return if found.
//取音讯进程
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
}
}
11、为什么取音讯也是用的死循环呢?
死循环便是为了确保必定要回来一条音讯,假如没有可用音讯,那么就堵塞在这儿,一向到有新音讯的到来。 其间,nativePollOnce办法便是堵塞办法,nextPollTimeoutMillis参数便是堵塞的时刻。
那什么时分会堵塞呢?两种状况:
1、有音讯,可是当时时刻小于音讯履行时刻,
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX\_VALUE);
}
这时分堵塞时刻便是音讯时刻减去当时时刻,然后进入下一次循环,堵塞。
2、没有音讯的时分,
if (msg != null) {}
else {
// No more messages.
nextPollTimeoutMillis = -1;
}
\-1就代表一向堵塞。
12、MessageQueue没有音讯时分会怎样?堵塞之后怎样唤醒呢?说说pipe/epoll机制?
当音讯不可用或许没有音讯的时分就会堵塞在next办法,而堵塞的办法是经过pipe/epoll机制
epoll机制是一种IO多路复用的机制,详细逻辑便是一个进程能够监督多个描述符,当某个描述符安排妥当(一般是读安排妥当或许写安排妥当),能够通知程序进行相应的读写操作,这个读写操作是堵塞的。在Android中,会创立一个Linux管道(Pipe)来处理堵塞和唤醒。 当音讯行列为空,管道的读端等候管道中有新内容可读,就会经过epoll机制进入堵塞状况。 当有音讯要处理,就会经过管道的写端写入内容,唤醒主线程。
13、子线程中保护的looper,音讯行列无音讯的时分处理方案是什么?有什么用,主线程呢?
音讯行列无音讯时分,此刻Looper会一向堵塞状况。
处理方案:
-
在loop机制中有个quit和quitSafely函数,他们调用messageQueue中的quit函数,然后将音讯行列中的悉数的音讯给悉数remove掉,然后开释内存;
-
紧接着就会调运转到quit办法中终究一行nativeWake函数唤醒一切等候的当地。
-
醒来之后然后持续在堵塞的当地放下履行,发现msg==null,直接return null;这个时分looper结束,退出死循环,开释线程。
子线程创立 Looper 并履行 loop() 的线程在使命结束的时分,需求手动调用 quit。否则,线程将由于 loop() 的轮询一向处于可运转状况,CPU 资源无法开释。更有或许由于 Thread 作为 GC Root 持有超出生命周期的实例引发内存走漏。当 quit 调用后,Looper 不再由于没有 Message 去等候,而是直接取到为null的 Message,这将触发轮循死循环的退出。
假如在子线程中创立了一个Handler,那么就有必要做三个操作
1. prepare();
2. loop();
3. quit();
主线程中能否调用quit()办法?
是不能的。它会抛出一个反常,让程序挂掉。在内存不足的时分 App 由 AMS 直接收回进程
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
}
源码剖析:
Looper
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
MessageQueue
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
//remove:将音讯行列中的音讯悉数干掉,把音讯悉数干掉,也就开释了内存
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr); //叫醒一切等候的当地,醒了之后,持续往下履行。
}
}
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
//便是在这儿依据nextPollTimeoutMillis判别是否要堵塞
//native的办法,在没有音讯的时分回堵塞管道读取端,只需nativePollOnce回来之后才能往下履行
//堵塞操作,等候nextPollTimeoutMillis时长
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
} else {
//没有音讯,nextPollTimeoutMillis复位
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
//假如音讯行列正在处于退出状况回来null,调用dispose();开释该音讯行列
if (mQuitting) {quit时分设置mQuitting = true;
dispose();
return null;
}
}
}
14、同步屏障和异步音讯是怎样完结的?
其实在Handler机制中,有三种音讯类型:
- 同步音讯。也便是一般的音讯。
- 异步音讯。经过setAsynchronous(true)设置的音讯。
- 同步屏障音讯。经过postSyncBarrier办法增加的音讯,特色是target为空,也便是没有对应的handler。
这三者之间的关系怎样呢?
- 正常状况下,同步音讯和异步音讯都是正常被处理,也便是依据时刻when来取音讯,处理音讯。
- 当遇到同步屏障音讯的时分,就开始从音讯行列里边去找异步音讯,找到了再依据时刻决议堵塞仍是回来音讯。
- 也便是说同步屏障音讯不会被回来,他只是一个标志,一个东西,遇到它就代表要去先行处理异步音讯了。
所以同步屏障和异步音讯的存在的含义就在于有些音讯需求“加急处理”。
15、同步屏障和异步音讯有详细的运用场景吗?
运用场景就很多了,比方制作办法scheduleTraversals。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障,堵塞一切的同步音讯
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 经过 Choreographer 发送制作使命
mChoreographer.postCallback(
Choreographer.CALLBACK\_TRAVERSAL, mTraversalRunnable, null);
}
}
Message msg = mHandler.obtainMessage(MSG\_DO\_SCHEDULE\_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
在该办法中参加了同步屏障,后续参加一个异步音讯MSG_DO_SCHEDULE_CALLBACK,终究会履行到FrameDisplayEventReceiver,用于请求VSYNC信号。
16、音讯是怎样被保护的?被谁保护的?
Message由Looper,MessageQueue进行保护;Looper是在ActivityThread中调用Looper.prepareMainLooper()中进行创立,创立的一起也创立好了MessageQueue。
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
17、Message音讯被分发之后会怎样处理?音讯怎样复用的?
- loop办法,在音讯被分发之后,也便是履行了dispatchMessage办法之后,
- 还悄悄做了一个操作——recycleUnchecked。在recycleUnchecked办法中,开释了一切资源,然后将当时的空音讯刺进到sPool表头。这儿的sPool便是一个音讯目标池,它也是一个链表结构的音讯,最大长度为50。
那么Message又是怎样复用的呢?
在Message的实例化办法obtain中:直接复用音讯池sPool中的第一条音讯,然后sPool指向下一个节点,音讯池数量减一。
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();
}
Looper类
final MessageQueue mQueue;
public static void loop() {
final Looper me = myLooper();
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
} finally {}
msg.recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG\_IN\_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID\_NONE;
workSourceUid = UID\_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX\_POOL\_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
18、为什么Message目标经过Obtain获取?为什么运用复用池?为什么运用复用池就能够优化?
运用复用池,让内存运用愈加高效,优化内存。运用复用池能够削减目标的创立进程,由于咱们音讯比较多,要创立多个目标,创立就要分配内存。假如目标太多的就要频频的进行废物收回,咱们音讯一般处理的比较快,创立后很快就被履行完了,可是要进行废物收回,相对耗时,收回时分也或许形成内存抖动。
19、咱们运用Message时应该怎样创立他?
经过obtain进行创立,为什么要用复用池?让内存运用愈加高效,优化内存, 为什么运用复用池就能优化,就能更高效?原因是什么?削减目标的创立进程?为什么?创立就要分配内存; 由于Message创立非常频频,假如不断以new的办法去创立它,或许会导致废物收回机制中新生代被占满,然后触发GC,产生内存抖动。 所以在Android的Message机制里边,对Message的办理选用了享元规划形式,经过obtain进行创立,复用一个音讯池中现已被收回的message。
- obtain()保持了一个Message的pool(池子)咱们在构建一个音讯的时分,一般的步骤是先obtain一个音讯,然后对它的各个字段进行设置,像target、data、when、flags…
- 当MessageQueuez去开释音讯的时分(quit),它只是把音讯的内容置空了,然后再把这条处理的音讯放到池子里边来,让池子不断变大。
- 在这个池子里边,最多放置50个音讯。假如音讯超过了50个音讯,这个池子也不要了,然后mMessage也为空,则它也会被及时的收回。
首要的 Message 收回机遇是:
- 在 MQ 中 remove Message 后,调用msg. recycleUnchecked
- 单次 loop 结束后,调用msg. recycleUnchecked.
- 咱们主动调用 Message 的 recycle 办法,后然后调用 recycleUnchecked;
20、MessageQueue是怎样确保线程安全的?
增加音讯到行列和获取数据的办法都加了synchronized同步锁。
21、一个线程有几个Handler?
咱们能够屡次new一个Handler;所以一个线程能够有无数个handler。
一个线程能够具有多 Handler,由于 Handler 终究是被 Message 持用的(post 里边的 Runnable 终究也会被包装成一个 Message),以便 Looper 在拿到 Message 后调用 Handler 的 dispatchMessage 完结回调,而且项目中细心去看也的确如此,咱们能够每个 Activity 中都创立一个 Handler 来处理回调到主线程的使命。
msg.target = this;(this便是当时handler)
looper类中
Handler target;
msg.target.dispatchMessage(msg);
22、为什么一个线程只能由一个Looper和MessageQueue?
由于参加咱们有多个MesaageQueue,假如咱们存一个延时2s音讯到行列1,延时4s的音讯到行列2,假如咱们先从行列2中读取,则延时4s的音讯先履行,不符合规划要求。
23、Looper是干嘛的?怎样获取当时线程的Looper?一个线程有几个Looper?怎样确保?
23.1 looper干嘛的?
在Handler发送音讯之后,音讯就被存储到MessageQueue中,而Looper便是一个办理音讯行列的人物。Looper会从MessageQueue中不断的查找音讯,也便是loop办法,并将音讯交回给Handler进行处理。
23.2 Looper的获取?
便是经过ThreadLocal机制:
经过prepare办法创立Looper而且参加到sThreadLocal中,经过myLooper办法从sThreadLocal中获取Looper。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
23.3 一个线程只需一个Looper。
首要要了解线程thread和Looper的关系? 主线程Looper哪里初始化?ActivityThread zygote为运用分配虚拟机,ActivityThread的main函数的的触发是由 AMS触发,在main函数中做了以下两件事
Looper.prepareMainLooper();
Looper.loop();
生命周期办理,页面响应、点击时刻事情,按键事情等等都会变成MSG,经过looper办理。 main函数中的这两件事,为进程内的MSG办理供给初始化。
Looper.prepareMainLooper();//初始化looper,
Looper.loop();//需求Looper.loop()发动,后履行。里边又个for(;;)死循环,一向循环取音讯
23.4 为什么么只需一个looper?
答:Looper.prepareMainLooper()调用时分,调用prepare()函数,经过ThreadLocal来确保,它经过static和final润饰,只能初始化一次,确保线程仅有ThreadLocal。
- 首要要经过ThreadLocal的get办法获取当时线程所对应的ThreadLocalmap中的key==ThreadLocal的value值。
- 假如value不为null,也便是looper不为空,就抛出一个运转时反常,提示只能有一个looper。
- 假如map为空的话,就调用ThreadLocal的set函数,将该ThreadLocal作为key,对应的looper作为value存储在thread中的ThreadLocalmap。
只需线程不变,ThreadLocal目标也不变,key也就不变了,然后经过throw new RuntimeException(“Only one Looper may be created per thread”)确保只能有一个value,Map中也就无法刺进第二个键值对,因而确保了一个线程只需一个。
Looper.prepareMainLooper();————>
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));
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
23.5 能够屡次创立Looper吗?
Looper的创立是经过Looper.prepare办法完结的,而在prepare办法中就判别了,当时线程是否存在Looper目标,假如有,就会直接抛出反常。所以同一个线程,只能创立一个Looper,屡次创立会报错。
24、Looper中的传入的quitAllowed字段是啥?有什么用?
QuitAllowed判别是否允许退出,假如这个字段为false,代表不允许退出,就会报错。 Looper中的quit办法便是退出音讯行列,终止音讯循环。调用到MessageQueue中的quit时,判别quitAllowed字段。
- 首要设置了mQuitting字段为true。
- 然后判别是否安全退出,假如安全退出,就履行removeAllFutureMessagesLocked办法,它内部的逻辑是清空一切的推迟音讯,之前没处理的非推迟音讯仍是需求取处理,然后设置非推迟音讯的下一个节点为空(p.next=null)。
- 假如不是安全退出,就履行removeAllMessagesLocked办法,直接清空一切的音讯,然后设置音讯行列指向空(mMessages = null)
当调用quit办法之后,
- 音讯的发送:当调用了quit办法之后,mQuitting为true,音讯就发不出去了,会报错。
- 音讯的处理,loop和next办法:当mQuitting为true的时分,next办法回来null,那么loop办法中就会退出死循环。
那么这个quit办法一般是什么时分运用呢?
主线程中,一般状况下肯定不能退出,由于退出后主线程就中止了。所以是当APP需求退出的时分,就会调用quit办法,涉及到的音讯是EXIT_APPLICATION,咱们能够查找下。
子线程中,假如音讯都处理完了,就需求调用quit办法中止音讯循环。
Looper
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
MessageQueue
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
//remove:将音讯行列中的音讯悉数干掉,把音讯悉数干掉,也就开释了内存
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr); //叫醒一切等候的当地,醒了之后,持续往下履行。
}
}
发送音讯
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
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;
}
//读取音讯
Message next() {
synchronized (this) {
// Process the quit message now that all pending messages have been handled.
//假如音讯行列正在处于退出状况回来null,调用dispose();开释该音讯行列
if (mQuitting) {//quit时分设置mQuitting = true;
dispose();
return null;
}
}
}
25、Handler机制内存走漏原因?为什么其他的内部类没有说过这个问题?
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

1、handler创立时分就对持有当时Activity得引证,一起message持有对handler的引证。MessageQueue持有Message;Message持有了Handler;handler是匿名内部类,持有this Activity,Activity持有很多内存,就会形成内存走漏。
2、发送一个推迟音讯时分,音讯被处理前,该音讯一向保存在音讯行列中,在持续保存这段时刻,messageque持有对message的引证,Message持有了handler;在咱们的activity中树立一个Handler的实例,该handler实例默认持有了外部的目标acticity的引证,当咱们调用acticity的ondestroy办法时,activity毁掉了,可是依据可达性剖析,咱们的需求的activity存在被handler引证,只需handler不被开释,没办法会毁掉,就形成了内存走漏。
处理该办法的两个办法:
1、将handler变为static,就会不会引证activity,由于静态内部类不会持有外部的类的引证。 2、运用弱引证weakreference持有activity;废物收回器在收回进程中,不过内存空间够与否,都将弱引证目标进行收回。 3、在外部结束生命周期的ondestroy办法中,清除音讯行列中的音讯;只需清除该msg,引证就会断开,防止内存走漏。调用这个办法mHandler.removeCallbacksAndMessages(null)
为什么其他内部类没有这个说法?
由于正常来说其他内部类持有外部类的目标,可是在内部持有外部类的时分没有做耗时操作,也就不存在这种持续引证的,形成内存走漏。
26、Message是怎样找到它所属的Handler然后进行分发的?
在loop办法中,找到要处理的Message,然后调用了这么一句代码处理音讯:
msg.target.dispatchMessage(msg);
所以是将音讯交给了msg.target来处理,那么这个target是啥呢?Hanlder发送音讯的时分,会设置msg.target = this,所以target便是当初把音讯加到音讯行列的那个Handler。
//Handler
private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
27、Handler 的 post(Runnable) 与 sendMessage 有什么差异
Hanlder中首要的发送音讯能够分为两种:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
post办法给Message设置了一个callback。
那么这个callback有什么用呢?咱们再转到音讯处理的办法dispatchMessage中看看:
这段代码能够分为三部分看:
- 假如msg.callback不为空,也便是经过post办法发送音讯的时分,会把音讯交给这个msg.callback进行处理,然后就没有后续了。
- 假如msg.callback为空,也便是经过sendMessage发送音讯的时分,会判别Handler当时的mCallback是否为空,
- 假如不为空就交给Handler.Callback.handleMessage处理。
- 假如mCallback.handleMessage回来true,则无后续了。
- 假如mCallback.handleMessage回来false,则调用handler类重写的handleMessage办法。
- 假如不为空就交给Handler.Callback.handleMessage处理。
所以post(Runnable) 与 sendMessage的差异就在于后续音讯的处理办法,是交给msg.callback仍是 Handler.Callback或许重写的Handler.handleMessage。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
public void handleMessage(@NonNull Message msg) {
}
28、Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么规划?
接着上面的代码说,这两个处理办法的差异在于Handler.Callback.handleMessage办法是否回来true: 假如为true,则不再履行Handler.handleMessage
假如为false,则两个办法都要履行。
那么什么时分有Callback,什么时分没有呢?这涉及到两种Hanlder的 创立办法:
val handler1= object : Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
val handler2 = Handler(object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true
}
})
常用的办法便是第1种,派生一个Handler的子类并重写handleMessage办法。而第2种便是体系给咱们供给了一种不需求派生子类的运用办法,只需求传入一个Callback即可。
————————————————
29、handler.post办法,它和常用的handler.sendmessage办法的差异是什么?
1、先看用法1之主线程中运用:
new Handler().post(new Runnable() {
@Override
public void run() {
mTest.setText("post");//更新UI
}
});
new了Runnable像是敞开了一个子线程,可是否则,这儿调用的是run办法,而不是start办法,因而并不是子线程,其实仍是在主线程中;
那为什么又要运用post办法呢?其实一般不这样用,也没人这样用,并没有多大含义,这就像是在主线程中给主线程sendmessage,并没有什么含义(咱们知道sendmessage是子线程为了通知主线程更新UI的),主线程是能够直接更新UI的。
2、 再看用法2之子线程中运用:
Handler handler;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
mTest.setText("post");//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
这儿的post并不是新敞开的子线程,存在的子线程只需一个,即为new的Thread,那么为什么咱们在其间能够setText做更新UI的操作呢? 其实post办法post曩昔的是一段代码,相当于将这个Runable体放入音讯行列中,那么looper拿取的即为这段代码去交给handler来处理,其实也相当于咱们常用的下面这段代码:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mTest.setText("handleMessage");//更新UI
break;
}
}
};
用这个Runnable体代替了上面这一大段代码,当然,咱们的post办法就能够履行UI操作了。
往常状况下咱们一个activity有很多个子线程,那么咱们都会选用上面这种handleMessage(msg)办法,然后case 0:case 1:等等,可是当咱们只需一个子线程时呢,用post反而比上面一大串代码轻便了不少,何乐而不为呢?
30、为什么主线程能够new Handler?子线程new handler需求做什么作业。
由于在运用发动时分就发动了loop,所以主线程能够new handler。
- 主线程在ActivityThread.java里有一个main()函数,它是Android每一个运用最早履行的函数。prepare是初始化,初始化了一个Looper。
- 然后又接着调用的Looper.loop()函数,这个loop()函数其实便是履行了那个for循环,它不断的调用next()函数,经过调用next()函数去轮询咱们的MessageQueue。
- 假如不调用prepare(),Looper没有被初始化;假如不调用loop(),Looper的机制滚动不起来。所以,一切的履行有必要先prepare(),然后再loop()。
ActivityThread中做了哪些关于Handler的作业?(为什么主线程不需求单独创立Looper) 首要做了两件事:
1、在main办法中,创立了主线程的Looper和MessageQueue,而且调用loop办法敞开了主线程的音讯循环。
public static void main(String\[] args) {
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
2、创立了一个Handler来进行四大组件的发动中止等事情处理
final H mH = new H();
class H extends Handler {
public static final int BIND\_APPLICATION= 110;
public static final int EXIT\_APPLICATION= 111;
public static final int RECEIVER = 113;
public static final int CREATE\_SERVICE= 114;
public static final int STOP\_SERVICE= 116;
public static final int BIND\_SERVICE= 121;
在子线程中new Handler需求做哪些预备?
假如子线程中想要进行Handler操作,就有必要在子线程中履行prepare() 和 loop()。
子线程
Thread thread = new Thread() {
Looper looper;
@Override
public void run() {
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
public Looper getLooper() {
return looper;
}
};
thread.start();
//过错的做法
Handler handler = new Handler(thread.getLooper()); //创立handler。要有必要传入looper。
//可是主线程和子线程履行不同步不必定能拿到looper,
//无法确保或缺的looper不为null,所以是过错的
//正确做法利用创立子线程
HandlerThreadhandler = new HandlerThread();
线程创立时分,prepare在run办法,handler在主线程中创立的的,不能确保prepare履行之后再履行handler的创立。
- 处理办法能够让子线程sleep或许wait,等一下。
- 真实的办法是利用HandlerThread处理同步问题。
- 利用synchronized完结的。synchronized是一个内置锁,内置到JVM中,开锁关锁是由JVM内部完结的。
31、HandlerThread源码剖析及运用
运用实例:
public class HandlerThreadActivity extends BaseActivity {
private static final String TAG = "HandlerThreadActivity";
private Button mStartBtn;
private Handler mHandler;
private HandlerThread mHandlerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity\_handler\_thread);
mStartBtn = findViewById(R.id.start\_btn);
//子线程
mHandlerThread = new HandlerThread("THREAD\_NAME");
mHandlerThread.start();
//主线程
mHandler = new Handler(mHandlerThread.getLooper());
mStartBtn.setOnClickListener(new View\.OnClickListener() {
@Override
public void onClick(View v) {
//主线程发送耗时音讯给子线程
mHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, Thread.currentThread().getId() + " " + String.valueOf((Looper.myLooper()
== Looper.getMainLooper())) + " 使命:" + this.hashCode());
SystemClock.sleep(3000);
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}
}
HandlerThread源码剖析
- HandlerThread 运转 start() 办法,回调 run() 办法。
- 在 run() 办法中经过 Looper.prepare() 来创立音讯行列,并经过 Looper.looper() 办法来敞开音讯循环。
- 由于 Loop.loop() 是一个死循环,导致 run() 也是无线循环,因而当咱们不需求运用 HandlerThread 的时分,要调用它的 quit() 办法或许 quiteSafely() 办法。
HandlerThread
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
//code后续代码
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {//
try {
wait();//此刻开释锁,等候。
//code
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
参加锁的机制确保mLooper = Looper.myLooper(),先于getlooper履行,一起参加wait也确保了mylooper先履行。
履行Looper的prepare()办法,生成Looper目标,而是把Looper目标和当时线程目标形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler目标,调用Looper的myLooper()办法,得到与Handler所对应的Looper目标,这样的话,handler、looper 、音讯行列就形成了一一对应的关系
- Wait:等候;开释锁,线程能够处理其他事务。
- sleep:线程等候,线程不会做其他事情。
一个线程或许会有多个handler,多个当地获取looper,所以要notifyall。唤醒一切handler。可是notifyAll之后不会立即履行,其他的要代码要处在安排妥当状况,先履行完synchronized里边的一切代码,才会履行wait之后的代码。
- notify:唤醒对应线程
- notifyAll:唤醒一切线程。
synchronized是Java中的关键字,是一种同步锁。它润饰的目标有以下几种:
- 润饰一个代码块,被润饰的代码块称为同步语句块,其效果的规模是大括号{}括起来的代码,效果的目标是调用这个代码块的目标;
- 润饰一个办法,被润饰的办法称为同步办法,其效果的规模是整个办法,效果的目标是调用这个办法的目标;
- 修正一个静态的办法,其效果的规模是整个静态办法,效果的目标是这个类的一切目标;
- 修正一个类,其效果的规模是synchronized后边括号括起来的部分,效果的目标是这个类的一切目标。
总结: A. 无论synchronized关键字加在办法上仍是目标上,假如它效果的目标对错静态的,则它获得的锁是目标;假如synchronized效果的目标是一个静态办法或一个类,则它获得的锁是对类,该类一切的目标同一把锁。 B. 每个目标只需一个锁(lock)与之相关联,谁拿到这个锁谁就能够运转它所操控的那段代码。 C. 完结同步是要很大的体系开支作为代价的,乃至或许形成死锁,所以尽量防止无谓的同步操控。
子线程向主线程发送音讯:
主线程中定义handler,承受音讯并处理
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case UPDATETEXT:
Log.e("handler","做ui线程中的事");
break;
default:
break;
}
}
};
子线程中发送音讯:处理完耗时使命操作时发送音讯给主线程,更新UI。
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "";
message.what = UPDATETEXT;
mHandler.sendMessage(message);
}
}).start();
主线程向子线程发送音讯:
主线程碰到耗时操作要子线程完结,让子线程履行指定的操作,利用handler创立子线程,主动创立looper。
项目中的一段代码:传感器注册时比较耗时,交给子线程处理。
private Handler mRegisterThreadHandler;
private ApplicationLifeObserver() {
HandlerThread mThread = new HandlerThread("register-sensor");
mThread.start();
/**
* 这儿要将HandlerThread创立的looper传递给threadHandler,即完结绑定;
*/
mRegisterThreadHandler = new Handler(mThread.getLooper()) {
@Override
public void handleMessage(Message msg) {//这儿可做主线程耗时的操作;
if (msg.what == REGISTER\_SENSOR) {
registerSensorListeners();
}
if (msg.what == UN\_REGISTER\_SENSOR) {
unregisterSensorListeners();
}
}
};
}
32、既然能够存在多个handler往MessageQueue中增加数据(发送音讯时各个handler或许处于不同线程),那么他内部是怎样确保线程安全的?取音讯呢?
handler往messageQueue中增加音讯是经过synchronized确保线程安全。在发送音讯时分,如行列的办法中参加synchronized锁。取音讯也是经过synchronized锁确保安全的。

经过synchronized来确保线程安全的。 发送音讯时分,入行列的办法equeueMessage加synchronized锁。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
………
}
}
取音讯时
looper.loop中完结for(;;){loopOnce}—>oopOnce:
1⃣Message msg =me.mQueue.next(); // might block;2⃣️msg.target.dispatchMessage(msg);——>handler.dispatchMessage——>handler.handleMessage。
其间取音讯MessageQueue中的next办法完结参加synchronized。
Message msg = me.mQueue.next(); // might block
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
}
}
33、IntentService是啥?有什么运用场景?
public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService\[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
- 首要,这是一个Service,而且内部保护了一个HandlerThread,也便是有完好的Looper在运转。
- 还保护了一个子线程的ServiceHandler。
- 发动Service后,会经过Handler履行onHandleIntent办法。
- 完结使命后,会主动履行stopSelf中止当时Service。
- 所以,这便是一个能够在子线程进行耗时使命,而且在使命履行后主动中止的Service。
34、BlockCanary运用过吗?说说原理
BlockCanary是一个用来检测运用卡顿耗时的三方库。
View的制作也是经过Handler来履行的,所以假如能知道每次Handler处理音讯的时刻,就能知道每次制作的耗时了?那Handler音讯的处理时刻怎样获取呢?
再去loop办法中找找细节:
public static void loop() {
for (;;) {
// 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);
}
}
}
能够发现,loop办法内有一个Printer类,在dispatchMessage处理音讯的前后分别打印了两次日志。
那咱们把这个日志类Printer替换成咱们自己的Printer,然后统计两次打印日志的时刻不就相当于处理音讯的时刻了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
这便是BlockCanary的原理。
35、IdleHandler是啥?有什么运用场景?
当MessageQueue没有音讯的时分,就会堵塞在next办法中,其实在堵塞之前,MessageQueue还会做一件事,便是查看是否存在IdleHandler,假如有,就会去履行它的queueIdle办法。
private IdleHandler\[] mPendingIdleHandlers;
Message next() {
int pendingIdleHandlerCount = -1;
for (;;) {
synchronized (this) {
//当音讯履行结束,就设置pendingIdleHandlerCount
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//初始化mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler\[Math.max(pendingIdleHandlerCount, 4)];
}
//mIdleHandlers转为数组
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 遍历数组,处理每个IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers\[i];
mPendingIdleHandlers\[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//假如queueIdle办法回来false,则处理完就删去这个IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
}
}
当没有音讯处理的时分,就会去处理这个mIdleHandlers集合里边的每个IdleHandler目标,并调用其queueIdle办法。终究依据queueIdle回来值判别是否用完删去当时的IdleHandler。
IdleHandler是怎样加进去的(IdleHandler便是当音讯行列里边没有当时要处理的音讯了,需求堵塞之前,能够做一些空闲使命的处理。)
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//做事情
return false;
}
});
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
常见的运用场景有:发动优化。
咱们一般会把一些事情(比方界面view的制作、赋值)放到onCreate办法或许onResume办法中。可是这两个办法其实都是在界面制作之前调用的,也便是说必定程度上这两个办法的耗时会影响到发动时刻。
所以咱们能够把一些操作放到IdleHandler中,也便是界面制作完结之后才去调用,这样就能削减发动时刻了。
可是,这儿需求注意下或许会有坑。
假如运用不当,IdleHandler会一向不履行,比方在View的onDraw办法里边无限制的直接或许间接调用View的invalidate办法。
其原因就在于onDraw办法中履行invalidate,会增加一个同步屏障音讯,在比及异步音讯之前,会堵塞在next办法,而比及FrameDisplayEventReceiver异步使命之后又会履行onDraw办法,然后无限循环。
36、handler的音讯堵塞是怎样完结的,Looper.loop办法是死循环,为什么不会卡死(ANR)?
运用卡死,也便是ANR产生:
- 5s之内没有响应输入的事情,比方按键,触摸屏幕等等;
- 广播接收器在10秒之内没有履行结束。
ANR产生的机制:定时器;
可是input按键事情产生机制:下一个按键事情,下一个按键事情来的时分发现上一个事情6s内完结了就不anr,没有完结就anr
AMS办理机制
- 每一个运用都存在于自己的虚拟机中,也便是每一个运用都有自己的一个main函数。
- 发动流程:launcher—–zygote——application——activityThread——main()
- 一切运用地点的生命周期(包括activity,service的一切生命周期)都在这个looper里边,而且,他们都是以音讯的办法存在的。
堵塞后怎样唤醒?
唤醒线程的办法:
1、loop中增加message,经过nativeWait—-loop运作
2、输入事情
Looper.loop办法是死循环,为什么不会卡死(ANR)?
- 主线程自身便是需求一向运转的,由于要处理各个View,界面改变。所以需求这个死循环来确保主线程一向履行下去,不会被退出。
- 真实会卡死的操作是在某个音讯处理的时分操作时刻过长,导致掉帧、ANR,而不是loop办法自身。运用卡死ANR压根和这个looper没有关系,运用在没有音讯需求处理的时分,他是在睡觉,开释线程了;卡死是ANR,而Looper是睡觉。
- 主线程里边的任何事情都是主线程的message,假如没有message,阐明没有事情要处理了,就要sleep,cpu省下来,所以堵塞时分没有埋炸弹定时器,所以不会堵塞。
- 当没有音讯的时分,会堵塞在loop的queue.next()中的nativePollOnce()办法里,此刻主线程会开释CPU资源进入休眠状况,直到下个音讯到达或许有事务产生。所以死循环也不会特别耗费CPU资源。
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}