前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入Handler、Looper、MessageQueue

深入Handler、Looper、MessageQueue

原创
作者头像
笔头
修改2022-01-25 10:05:26
3650
修改2022-01-25 10:05:26
举报
文章被收录于专栏:Android记忆

Handler、Looper、MessageQueue基本了解可以看下这篇文章 Android源码解读-Handler、Loop、MessageQueue

一、MessageQueue如何实现Message添加和延迟获取?

一、如何添加

enqueueMessage

代码语言:javascript
复制
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ......
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //---------------------------------------1
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
        //----------------------------------------2
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
    
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

上面代码,我们可以知道,添加Message方式分两种情况

第一种情况

如果当前待添加的Message的when等于0或小于头Message的when,那就直接吧待添加的Message添加到队列头部。

假如原有队列是这样

现在要添加新Message when=1

看下上面源码,分两种情况。

1.如果mMessages==null

这个情况是第一次添加Message,原本队列为空,则队列内容就是msg。

2.when ==0|| when < p.when

这种情况就如上图,在mMessages不为空的情况下msg的next指向mMessages,也就是msg作为对头,when值小的放在对头,结果如下

由mMessages = msg;可知,mMessages就是对头。

第二种情况

when > p.when

假设队列情况如下

我们现在要添加Message when=6

这个时候我们会遍历队列,一直找到msg的when大于当前队列中次最大when值的Message或者找不到最大when的时候,直接添加到次最大when的Message或者对尾。结果如下

此时的对头mMessages,是指向谁呢,这个取决于next方法。

二、如何延迟获取

代码语言:javascript
复制
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    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) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

1.每次取Message为 Message msg = mMessages,就是取对头Message。

2.我们从这边文章中得知,Handler中添加Message最终方法是调用 sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);这个方法得知,when值=SystemClock.uptimeMillis() + delayMillis。

next最主要的参数 nextPollTimeoutMillis,这个决定了什么时候可以取出Message,nextPollTimeoutMillis=msg.when - now。

调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,这是一个本地方法,会调用底层C++代码,C++代码最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟。

1.如果nextPollTimeoutMillis=-1,这个时候msg为空,一直阻塞不会超时。

2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。

3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

二、Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

我们在ActivityManagerService中发现 调用Process.start接口来创建一个新的进程,新的进程会导入android.app.ActivityThread类,并且执行它的main函数。ActivityThread没有继承Thread,他不是主线程。个人猜测当前进程就是主线程。

1.为什么要阻塞

一个线程,如果执行完成,该线程会消亡。如果当前Looper不阻塞,执行完成,那app直接嗝屁。

2.阻塞后为什么还能跳转页面等操作

这个问题是2个问题。第一个是如何执行页面跳转等操作,第二个是阻塞后为什么还能操作页面。

第一个,我们在ActivityThread main函数发现thread.attach(false, startSeq);行为,该行为就是创建binder线程,使得当前进程和和system_server系统进程通讯。

比如要启动新Activity:

1.应用程序的MainActivity通过Binder进程间通信机制通知ActivityManagerService,它要启动一个新的Activity;

2.ActivityManagerService通过Binder进程间通信机制通知MainActivity进入Paused状态;

3.MainActivity通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态;

4.ActivityManagerService通过Binder进程间通信机制通知MainActivity所在的ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。

第二个,阻塞后还能操作,跳转页面,还能调用各种声明周期。我们可以看到ActivityThread定义了内部类Handler,他和Looper都是在同一线程即主线程。Handler可以接受来自binder线程的数据,比如收到msg=H.LAUNCH_ACTIVITY,添加到MessageQueue中,Looper监测有数据了,通过Handler,把消息发出去,最终则调用ActivityThread.handleLaunchActivity()方法,通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;

3.源码中哪里执行ANR报错

从源码中我们可以知道

  • 用户的输入在5s内没被App响应
  • BroadcastReceiver的onReceiver()超过10s
  • Service中各生命周期函数执行超过20s

这三个情况会报ANR提示。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、MessageQueue如何实现Message添加和延迟获取?
    • 一、如何添加
      • 第一种情况
      • 第二种情况
    • 二、如何延迟获取
    • 二、Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?
      • 1.为什么要阻塞
        • 2.阻塞后为什么还能跳转页面等操作
          • 3.源码中哪里执行ANR报错
          相关产品与服务
          消息队列 CMQ 版
          消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档