一文读懂Handler面试

1、Handler机制简介

handler/Looper/Message/MessageQueue/thread

首先熟悉下怎么使用Handler?

java 复制代码
//主线程
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创建的线程(主线程)不是同一个线程,发生崩溃。
java 复制代码
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的工作机制?

  • 相关类

    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中的。

java 复制代码
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执行。

java 复制代码
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、有消息,但是当前时间小于消息执行时间,

java 复制代码
if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX\_VALUE);
}

这时候阻塞时间就是消息时间减去当前时间,然后进入下一次循环,阻塞。

2、没有消息的时候,

java 复制代码
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,那么就必须做三个操作

java 复制代码
1.  prepare();
2.  loop();
3.  quit();

主线程中能否调用quit()方法?

是不能的。它会抛出一个异常,让程序挂掉。在内存不足的时候 App 由 AMS 直接回收进程

java 复制代码
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
}

源码分析:

java 复制代码
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。

java 复制代码
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。

java 复制代码
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指向下一个节点,消息池数量减一。

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();
}

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 来处理回调到主线程的任务。

java 复制代码
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。

java 复制代码
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函数中做了以下两件事

java 复制代码
    Looper.prepareMainLooper();  
    Looper.loop();       

生命周期管理,页面响应、点击时间事件,按键事件等等都会变成MSG,通过looper管理。 main函数中的这两件事,为进程内的MSG管理提供初始化。

java 复制代码
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中也就无法插入第二个键值对,因此保证了一个线程只有一个。

java 复制代码
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方法停止消息循环。

java 复制代码
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。

java 复制代码
//Handler
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;
        return queue.enqueueMessage(msg, uptimeMillis);
    }

27、Handler 的 post(Runnable) 与 sendMessage 有什么区别

Hanlder中主要的发送消息可以分为两种:

java 复制代码
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。

java 复制代码
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的 创建方式:

java 复制代码
    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之主线程中使用:

java 复制代码
new Handler().post(new Runnable() {
        @Override
        public void run() {
              mTest.setText("post");//更新UI
        }

    });

new了Runnable像是开启了一个子线程,但是不然,这儿调用的是run方法,而不是start方法,因此并不是子线程,其实还是在主线程中;

那为什么又要使用post方法呢?其实一般不这样用,也没人这样用,并没有多大意义,这就像是在主线程中给主线程sendmessage,并没有什么意义(我们知道sendmessage是子线程为了通知主线程更新UI的),主线程是可以直接更新UI的。

2、 再看用法2之子线程中使用:

java 复制代码
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来处理,其实也相当于我们常用的下面这段代码:

java 复制代码
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方法开启了主线程的消息循环。

java 复制代码
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来进行四大组件的启动停止等事件处理

java 复制代码
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()

java 复制代码
子线程
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,所以是错误的
                                                   
//正确做法利用创建子线程
HandlerThread handler = new HandlerThread();

线程创建时候,prepare在run方法,handler在主线程中创建的的,不能保证prepare执行之后再执行handler的创建。
  • 解决办法可以让子线程sleep或者wait,等一下。
  • 真正的方法是利用HandlerThread解决同步问题。
    • 利用synchronized实现的。synchronized是一个内置锁,内置到JVM中,开锁关锁是由JVM内部完成的。

31、HandlerThread源码分析及使用

使用实例:

java 复制代码
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() 方法。
java 复制代码
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,接受消息并处理

java 复制代码
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。

java 复制代码
new Thread(new Runnable() {
    @Override
    public void run() {
        Message message = new Message();
        message.obj = "";
        message.what = UPDATETEXT;
        mHandler.sendMessage(message);
    }
}).start();

主线程向子线程发送消息:

主线程碰到耗时操作要子线程完成,让子线程执行指定的操作,利用handler创建子线程,自动创建looper。

项目中的一段代码:传感器注册时比较耗时,交给子线程处理。

java 复制代码
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锁。

java 复制代码
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。

java 复制代码
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是啥?有什么使用场景?

java 复制代码
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方法中找找细节:

java 复制代码
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,然后统计两次打印日志的时间不就相当于处理消息的时间了?

java 复制代码
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

这就是BlockCanary的原理。

35、IdleHandler是啥?有什么使用场景?

当MessageQueue没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue还会做一件事,就是检查是否存在IdleHandler,如果有,就会去执行它的queueIdle方法。

java 复制代码
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就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。)

java 复制代码
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资源。
java 复制代码
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;

            }
}
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui