美文网首页
Looper的分析

Looper的分析

作者: 编程的猫 | 来源:发表于2021-03-27 22:27 被阅读0次

    在android中Handler通信机制大家早就耳濡目染,而Looper在Handler中扮演着重要的角色。
    先大致看下Looper的源码:(为什么要先大致看下源码?先看源码做到心中有数,对于这个类有哪些东西、使用了哪些东西,心里要有个大概,然后再结合常用的API功能去分析源码,这样会有一个深刻的认知)

    public final class Looper {
     
        private static final String TAG = "Looper";
    
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        private static Looper sMainLooper;  
    
        final MessageQueue mQueue;
        final Thread mThread;
    
        private Printer mLogging;
        private long mTraceTag;
    
        private long mSlowDispatchThresholdMs;
    
        private long mSlowDeliveryThresholdMs;
    
        public static void prepare() {
            prepare(true);
        }
    
        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 void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }
    
        public static void 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;
    
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow", 0);
    
            boolean slowDeliveryDetected = false;
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
    
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false;
                        }
                    } else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true;
                        }
                    }
                }
                if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }
    
        private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
                String what, Message msg) {
            final long actualTime = measureEnd - measureStart;
            if (actualTime < threshold) {
                return false;
            }
            // For slow delivery, the current message isn't really important, but log it anyway.
            Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
                    + Thread.currentThread().getName() + " h="
                    + msg.target.getClass().getName() + " c=" + msg.callback + " m=" + msg.what);
            return true;
        }
    
        /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
        public static @NonNull MessageQueue myQueue() {
            return myLooper().mQueue;
        }
    
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
        public boolean isCurrentThread() {
            return Thread.currentThread() == mThread;
        }
    
        public void setMessageLogging(@Nullable Printer printer) {
            mLogging = printer;
        }
    
        public void setTraceTag(long traceTag) {
            mTraceTag = traceTag;
        }
    
        public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) {
            mSlowDispatchThresholdMs = slowDispatchThresholdMs;
            mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
        }
    
        public void quit() {
            mQueue.quit(false);
        }
    
        public void quitSafely() {
            mQueue.quit(true);
        }
    
        public @NonNull Thread getThread() {
            return mThread;
        }
    
        public @NonNull MessageQueue getQueue() {
            return mQueue;
        }
    
        public void dump(@NonNull Printer pw, @NonNull String prefix) {
            pw.println(prefix + toString());
            mQueue.dump(pw, prefix + "  ", null);
        }
    
        public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
            pw.println(prefix + toString());
            mQueue.dump(pw, prefix + "  ", handler);
        }
    
        /** @hide */
        public void writeToProto(ProtoOutputStream proto, long fieldId) {
            final long looperToken = proto.start(fieldId);
            proto.write(LooperProto.THREAD_NAME, mThread.getName());
            proto.write(LooperProto.THREAD_ID, mThread.getId());
            mQueue.writeToProto(proto, LooperProto.QUEUE);
            proto.end(looperToken);
        }
    
        @Override
        public String toString() {
            return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
                    + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
        }
    }
    

    可以看到Looper中使用了静态的对象sThreadLocal来存储Looper,了解ThreadLocal用法的都知道ThreadLocal是用来在线程中隔离存储数据的(对ThreadLocal不了解的人可以看我的这篇文章),那么在这里保存Looper对象的作用是为了隔离保存各线程中的Looper对象(对于同一线程的Looper,这里用ThreadLocal保存就相当于是单例:也就是同一个线程通过Looper.myLooper和Looper.getMainLooper方法拿到的Looper都是同一个Looper),使得不同线程间的Looper对象相互独立。

    Looper是一个final类,不允许继承。

    下边进入源码分析:

    前边说了threadLocal用来存储Looper,那么就从threadLocla存储Looper切入:

    // 对外开放的Api,
    //也就是在子线程中使用handler跟其他线程通信的时候,需要给该线程实例化Looper的方法
    public static void prepare() {
        //这里的参数传递的true,也就是说子线程中实例化Looper的时候参数传递的是true
        prepare(true);
    }
    // 私有的API
    private static void prepare(boolean quitAllowed) {
        //判断sThreadLocal中是否存在Looper,存在就抛出异常
        //说明一个线程中只能实例化一个Looper对象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //如果sThreadLocal中没有实例化当前线程的Looper,
        //那么就实例化Looper并赋值给sThreadLocal缓存
        sThreadLocal.set(new Looper(quitAllowed));
    }
    

    上边分析说到了子线程实例化Looper,下边看下主线程实例化Looper

    // 开放API,主线程实例化Looper
    public static void prepareMainLooper() {
        //这里也调用了私有的prepare方法,但是主线程这传递的参数是false,
        //而子线程传递的参数是true,从参数名称的意思上看表示主线程创建的Looper是不会退出的,
        //子线程创建的Looper会退出(这样说不是特别准确,暂时可以这样理解,在Looper构造的时候会明白)
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //给sMainLooper变量赋值
            sMainLooper = myLooper();
        }
    }
    
    // 从sThreadLocal中获取Looper对象
    public static @Nullable Looper myLooper() {
        //再次强调ThreadLocal具有线程隔离的作用
        return sThreadLocal.get();
    }
    
    // 获取主线程的Looper
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    

    看看Looper的构造方法

    //获取Looper绑定的MessageQueue对象
    public static @NonNull MessageQueue myQueue() {
        //先拿到当前线程的Looper,再拿到Looper绑定的MessageQueue对象
        return myLooper().mQueue;
    }
    //Looper的构造方法
    private Looper(boolean quitAllowed) {
        //实例化消息队列的对象,并赋值给Looper中的mQueue变量(可以理解为将MessageQueue对象绑定到当前Looper)
        //参数quitAllowed表示实例化的消息队列是否可以退出
        mQueue = new MessageQueue(quitAllowed);
        //将当前Looper与当前的线程绑定
        mThread = Thread.currentThread();
    }
    //这个方法的作用是:判断调用当前方法的线程与Looper绑定的线程是否是同一个线程
    public boolean isCurrentThread() {
        return Thread.currentThread() == mThread;
    }
    

    重点说一下Looper构造方法中的MessageQueue的实例化,参数为true表示消息队列可以退出,参数为false表示消息队列不能退出,这里立马能够想到Android主线程的Handler消息机制是一直存活的,这个参数就是一个体现,说明主线程的消息队列中的looper一直处于循环的存活状态。主线程的Looper实例化是在应用的入口函数ActivityThread,这里不做展开(可以查看我的文章主线程Handler消息机制:)

    Looper作为消息队列的循环器,那么功能的核心肯定是在loop循环这个方法

    // 循环从消息队列获取消息的方法
    public static void loop() {
        // 获取当前线程的Looper对象
        final Looper me = myLooper();
        if (me == null) {
            // 如果当前线程没有实例化Looper抛出异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 取出当前线程的Looper中绑定的Queue对象
        final MessageQueue queue = me.mQueue;
    
        // 清空远程进程的PID和UIP,确保此线程的标识是本地进程的标识
        //(涉及到Binder进程间通信机制,这里不做扩展,其他文章会讲解)
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
    
        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);
    
        boolean slowDeliveryDetected = false;
        // 开启死循环不断从消息队列中试图取出消息
        for (;;) {
            // 从消息队列中取出消息,这个方法可能会阻塞(其他文章会讲解MessageQueue)
            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);
            }
    
            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;
    
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
    
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                //拿到msg绑定的target(handler)对象下发消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }
    
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
    
            msg.recycleUnchecked();
        }
    }
    

    总结:

    looper方法中开启一个死循环,从MessageQueue中不断的尝试取出消息msg,后然通过msg绑定的handler对象下发消息。消息队列中没有消息的时候,处于阻塞状态

    这里会出现一个面试中经常会问到的一个假命题:主线程的MessageQueue发生阻塞的时候为什么不会阻塞主线程?

    答案:Android是基于事件驱动的,事件就需要通过消息机制来传递。Android进程间的消息机制大多是基于Binder,而Android线程间的消息传递是基于Handler通信。Android应用本质是一段可执行代码,对于一个应用来说,肯定不允许执行完一段代码后就死亡,那么最简单的方式就是开启一段死循环保证程序不会退出。

    真正会发生卡死主线程的操作是在回调方法onCreate()/onStart()/onResume()等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop()方法本身是不回导致应用卡死的。

    还有一个点:Looper.loop()方法一直运行那不是很耗费资源吗?

    其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

    在loop方法死循环中,当取得的msg==null时会return退出,那岂不是就退出了死循环,什么时候会走这里?有知道的同仁麻烦解惑一下,感谢🙏

    部分内容参考自该博文

    相关文章

      网友评论

          本文标题:Looper的分析

          本文链接:https://www.haomeiwen.com/subject/qhkuhltx.html