code小生,一个专注 Android 领域的技术平台
公众号回复 Android 加入我的安卓技术群
作者:桑小年链接:https://www.jianshu.com/p/70b00186e25a 声明:本文已获
桑小年授权发表,转发等请联系原作者授权
之前在学习Hanlder源码的时候,刚好涉及到 Looper.loop 方面的知识,这里进行一下回答
首先,在ActivityThread.main 方法中,可以找到Looper相关的初始化代码,在这段代码里面做了两件事,
1、初始化当前线程的Looper2、开启循环
public static void main(String[] args) { //省略掉部分不相关代码 //.......... //prepareMainLooper 方法在当前线程初始化了一个消息队列不允许退出Looper Looper.prepareMainLooper(); //.......... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
进入loop方法
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //1、获取到消息队列 final MessageQueue queue = me.mQueue; //省略部分不相关的代码 //.................. //开启死循环 for (;;) { //2、拿到队列中的消息 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } //省略部分不相关的代码 //.................. try { //3、执行队列中的消息 msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } //省略部分不相关的代码 //.................. msg.recycleUnchecked(); }
loop()方法中,代码非常简单,去除掉一些无用的日志打印和不相关的代码,剩余的就非常简单了,分三步走
-
1、获取到looper中的 MessageQueue
-
2、开启一个死循环,从MessageQueue 中不断的取出消息
-
3、执行取出来的消息 msg.target.dispatchMessage(msg);(顺便说一下,Handler的handleMessage()方法就是在这一步执行的,有兴趣的可以自己看看,这里就不细说了)
在第二步里面,会发生阻塞,如果消息队列里面没有消息了,会无限制的阻塞下去,主线程休眠,释放CPU资源,直到有消息进入消息队列,唤醒线程。从这里就可以看出来,loop死循环本身大部分时间都处于休眠状态,并不会占用太多的资源,真正会造成线程阻塞的反而是在第三步里的 msg.target.dispatchMessage(msg)方法,因此如果在生命周期或者handler的Handler的handleMessage执行耗时操作的话,才会真正的阻塞UI线程;
到这里,已经从java层解释了Looper.loop为什么不会阻塞掉UI线程;最后,再看一下queue.next()方法,毕竟代码留个尾巴不看实在太憋屈了
Message next() { // mPtr保存了NativeMessageQueue的指针,调用nativePollOnce进行等待 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 两个参数,nextPollTimeoutMillis 表示的是等待时间,-1的时候表示无限制等待 //在这里可以看出,如果消息队列里面没有消息,就会一直等待,直到队列里面加入新的消息,唤醒线程 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) { if (now < msg.when) { //如果下一条消息执行时间还未到,则计算出剩余需要阻塞的时间,给nativePollOnce方法,让他阻塞指定的时间后,继续执行 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 在这里,直接取出方法 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 { // 队列里面没有消息了,再次无限期阻塞 nextPollTimeoutMillis = -1; } //省略部分不相关的代码 //.................. // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } //省略部分不相关的代码 //.................. } }
从上面代码可以看出来,在调用next方法的时候,如果有当前消息可以被立刻执行,就会直接返回,如果消息需要延迟执行,则会接着阻塞一段时间,到消息可以被执行的时候再继续线程执行消息,如果队列里面没消息了,就会无限期的阻塞下去,直到新的消息进入队列唤醒线程;
至于什么时候唤醒阻塞,就需要看看enqueueMessage(Message msg, long when) 方法了,在这个方法里面,会将新的消息放到消息队列里面,并且判断如果此时线程处于阻塞状态,就会调用nativeWake()方法唤醒线程,继续执行next()方法,取出队列中的消息
最后总结一下:loop()开启死循环后,会命令MessageQueue通过 next()方法 取出之前储存的消息,如果有立刻被拿出来执行msg.target.dispatchMessage(msg);如果此时MessageQueue中已经没有消息了(大部分时候都没有),MessageQueue就会无限期的阻塞下去nativePollOnce(ptr, nextPollTimeoutMillis),释放cpu资源,这时候并不会造成UI线程卡顿,直到有新的消息存入队列enqueueMessage(Message msg, long when),唤醒之前阻塞的线程 nativeWake(mPtr),继续执行next()方法;
我只是从java层对问题进行了解答,时间仓促,可能有不完善的地方,对于有错误的地方,欢迎指正,共同学习进步
推荐阅读Android 消息机制(Looper Handler MessageQueue Message)项目模块重构分享与思考