从一场面试说起-Android多线程入门(2)

写在前面

接着昨天写的继续吧。还是先围绕我们的Handler来写。https://blog.csdn.net/weixin_46130326/article/details/107677615

正文

昨天写到了Looper的初始化,初始化之后需要调用Looper.loop()方法,使得主线程进入一个死循环,然后通过不断的从MessageQueue中获取元素,进行操作。

里面有这么几个问题需要仔细想想

1. 既然是个死循环,为什么线程不会阻塞,占用大量的CPU资源?

2. 我们使用sendMessage传递Message给主线程,是怎么传的?

先呆着这两个问题来看吧,暂时没想到别的。

首先跟着昨天的进度,来到Looper.loop()方法中,内容比较多,边看边学

final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

先初始化本线程的looper,从ThreadLocal中获取当前线程的Looper对象,对了,ThreadLocal是静态的,所以在多线程使用时,也不会受到干扰。之后初始化了一堆看不太懂的东西,比如这个

final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

拿到一个thresholdOverride,看起来是记录了looper所在的进程和线程

选择性的跳过一些log记录和调试用的代码(以后研究)

进入到for循环中,先取出queue中的message,为空则直接结束

 Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

之后进行如下操作

 final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

创建了一个logging,看起来是日志相关的东西,还有个Observer是个接口,里面主要有三个函数

Object messageDispatchStarting();
void messageDispatched(Object token, Message msg);
void dispatchingThrewException(Object token, Message msg, Exception exception);

看下注释

  1. Called right before a message is dispatched.
    *
    * <p> The token type is not specified to allow the implementation to specify its own type.
    *
    * @return a token used for collecting telemetry when dispatching a single message.
    *         The token token must be passed back exactly once to either
    *         {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
    *         and must not be reused again.

    当Message分发之前被执行。未指定令牌类型以允许实现指定自己的类型。

  2. Called when a message was processed by a Handler

    当消息被程序处理时调用

  3. Called when an exception was thrown while processing a message.
    当处理一个message时抛异常时调用

看起来是一个作为回调,记录当前运行状态的东西

貌似也不是主要做的。接着往下看是一堆log记录的东西和一些时间节点

下面的内容是主要的流程


                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            

首先使用调用msg.target.dispatchMessage(msg),什么鬼,用自己的target执行自己?看了下Message里面的target是一个Handler,那这个handler是哪个handler?继续往下看,dispatchMessage()就是Handler中的内容了,如下

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这个callback先不管,没见过,调用后面的handleMessage就是我们在初始化Handler时复写的方法。这看起来已经是传进来做处理了

中间那段去哪了,target怎么设置的?handler是如何对应的?这个时候应该从我们的sendMessage来看

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

继续

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

再来

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

当这个queue不为空的时候,调用enqueueMessage方法,在这里入队,往下看

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

ok,找到了,在这里我们设置了msg.target为这个handler,然后又调用queue.enqueueMessage,看下

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

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

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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 {
                // 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;
    }

主要看for()中的代码,是吧目标msg放到message的相应的时间位置(每个Message中都有一个when时间,相当于往排序链表中插入一个元素),message构成一个链表。

prev->msg->p

中间还有是否被阻塞的状态判断。

可以看出来,sendMessage和sendMessgeDelayed都调用了sendMessageAtTime(),然后把message和时间点传入,插入到队列中。

除了sendMessage之外,我们经常还会使用到post(Runnable r)这种类型的方式作为发送消息的方式。

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

这样看起来是不是和sendMessage很像呢?只是传入队列的变成了一个Runnable对象

所以重点就是这个getPostMessage(r)

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

往里看

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

注释:

*
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.

翻译:

从全局池返回一个新的消息实例。让我们在许多情况下避免分配新对象。

这段代码大家可以再结合Message这个类详细去看下

这样就是获取到一个Message,然后把这个Message的callback设置为Runnable对象,设置为对象以后也总得运行吧,那运行是在哪里呢?由于之后还是会一样还有入队操作,所以估计会在执行的的时候运行吧,带着这个猜测,回去看下dispatchMessage()

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

注意了,这段在之前出现过,当时我还不太明白这里的callback是做什么的,所以调用了post之后,其实是把Runnable对象存在了Message.callback中,然后再dispatchMessage时判断callback是否为空,再执行。

private static void handleCallback(Message message) {
        message.callback.run();
    }

这下真相大白了,运行确实是在dispatchMessage中进行的。

感觉分析的差不多了,再回到一开始提出来的问题

1. 既然是个死循环,为什么线程不会阻塞,占用大量的CPU资源?

2. 我们使用sendMessage传递Message给主线程,是怎么传的?

对于问题1,我们的主线程就跑在这个Looper里面,就像是那种原始的监听方式,没有回调,只能靠一个循环一直去监听一个对象的值是否有变化,再执行相应的操作。应该是包括TouchEvent在里面的,这个还不确定,以后应该会写个东西详细看下。至于占用大量的CPU,可以看到我们的MessageQueue中也有一些阻塞状态的判断,初步判断通过状态来减少CPU使用吧。在获取Message的时候是这样用的

 Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

好像也没做什么操作,进入queue.next()中查看,代码很长,里面也有个for循环,估计是用来获取message的主要部分,开头有个方法

nativePollOnce(ptr, nextPollTimeoutMillis);

非常引人注意。其实这个不是我自己发现的,我上网查了查资料

https://www.cnblogs.com/jiy-for-you/archive/2019/10/20/11707356.html

讲的比较详细,引用里面大神的一段话

简短答案:

nativePollOnce 方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 则您的主线程没有实际工作要做, 而是等待下一个事件处理.无需担心.

说明:

因为主线程负责绘制 UI 和处理各种事件, 所以主线程拥有一个处理所有这些事件的循环. 该循环由 L​​ooper 管理, 其工作非常简单: 它处理 MessageQueue 中的所有 Message.
例如, 响应于输入事件, 将消息添加到队列, 帧渲染回调, 甚至您的 Handler.post 调用. 有时主线程无事可做(即队列中没有消息), 例如在完成渲染单帧之后(线程刚绘制了一帧, 并准备好下一帧, 等待适当的时间). MessageQueue 类中的两个 Java 方法对我们很有趣: Message next()和 boolean enqueueMessage(Message, long). 顾名思义, Message next() 从队列中获取并返回下一个消息. 如果队列为空(无返回值), 则该方法将调用 native void nativePollOnce(long, int), 该方法将一直阻塞直到添加新消息为止. 此时,您可能会问nativePollOnce 如何知道何时醒来. 这是一个很好的问题. 当将 Message 添加到队列时, 框架调用 enqueueMessage 方法, 该方法不仅将消息插入队列, 而且还会调用native static void nativeWake(long)nativePollOnce 和 nativeWake 的核心魔术发生在 native 代码中. native MessageQueue 利用名为 epoll 的 Linux 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用 epoll_wait, 而 nativeWake 写入一个 IO 操作到描述符, epoll_wait 等待. 然后, 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息. 如果您熟悉 Java 的 Object.wait()和 Object.notify()方法,可以想象一下 nativePollOnce 大致等同于 Object.wait()nativeWake 等同于 Object.notify(),但它们的实现完全不同: nativePollOnce 使用 epoll, 而 Object.wait 使用 futex Linux 调用. 值得注意的是, nativePollOnce 和 Object.wait 都不会浪费 CPU 周期, 因为当线程进入任一方法时, 出于线程调度的目的, 该线程将被禁用(引用Object类的javadoc). 但是, 某些事件探查器可能会错误地将等待 epoll 等待(甚至是 Object.wait)的线程识别为正在运行并消耗 CPU 时间, 这是不正确的. 如果这些方法实际上浪费了 CPU 周期, 则所有空闲的应用程序都将使用 100% 的 CPU, 从而加热并降低设备速度.

结论:

nativePollOnce. 它只是表明所有消息的处理已完成, 线程正在等待下一个消息.

cpp的代码从上完密码学就没碰过了,这里就不出来丢人了。这样我们就知道为什么主线程不会阻塞。

对于问题2相信不用多说大家都明白了,其实就是在子线程里面获取到主线程的handler,然后通过handler的sendMessage方法把Message对象推到我们的Message中,这个时候其实是调用了我们的enqueueMessage(),此时设置Message的target为相应的Handler,之后经过Looper获取message,然后使用dispatchMessage方法进行处理。大概就是这样的。

 

总结

Handler的消息处理和调度分析的差不多了,对于多线程的处理相信我和各位看官都有了更进一步的掌握。对于其他的多线程使用方式,之后也会继续写写,但是要整理下,毕竟我还是个菜鸟,哈哈,今天不早了,还没洗澡,就先到这里。

 

感觉写blog还有点上瘾,淦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值