一文读懂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;

            }
}
相关推荐
众拾达人22 分钟前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌1 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley2 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei4 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng4 小时前
安卓多渠道apk配置不同签名
android
枫_feng5 小时前
AOSP开发环境配置
android·安卓
叶羽西5 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_171538857 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)8 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
mmsx8 小时前
android 登录界面编写
android·登录界面