1、Handler机制简介

handler/Looper/Message/MessageQueue/thread

一文读懂Handler面试

首要熟悉下怎样运用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面试

  • 相关类

    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、推迟音讯是怎样完结的?

  1. 推迟音讯的完结首要跟音讯的统一存储办法有关,即MessageQueue的enqueueMessage办法。。
  2. 无论是即时音讯仍是推迟音讯,都是计算出详细的时刻,然后作为音讯的when字段进程赋值。
  3. 然后在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

一文读懂Handler面试

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办法。

所以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中的关键字,是一种同步锁。它润饰的目标有以下几种:

  1. 润饰一个代码块,被润饰的代码块称为同步语句块,其效果的规模是大括号{}括起来的代码,效果的目标是调用这个代码块的目标;
  2. 润饰一个办法,被润饰的办法称为同步办法,其效果的规模是整个办法,效果的目标是调用这个办法的目标;
  3. 修正一个静态的办法,其效果的规模是整个静态办法,效果的目标是这个类的一切目标;
  4. 修正一个类,其效果的规模是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锁确保安全的。

一文读懂Handler面试

经过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;
 }
}
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。