Android Handler机制(三) Looper源码分析

news/2024/7/7 23:44:08

一. 简介

        我们接上一篇文章:Android Handler机制(二) Handler 实现原理 继续分析Looper

        Looper 的职责很单一,就是单纯的从 MessageQueue 中取出消息分发给消息对应 的宿主 Handler,因此它的代码不多(400行左右) . Looper 是线程独立的且每个线程只能存在一个 Looper。 Looper 会根据自己的存活情况来创建和退出属于它自己的 MessageQueue。

二. 创建和退出Looper

2.1 创建Looper

        上面的结论中提到了 Looper 是线程独立的且每个线程只能存在一个 Looper。所以构造 Looper 实例的方法类似于单例模式。隐藏构造方法,对外提供了两个指定 的获取实例方法 prepare()和 prepareMainLooper()。

源码解析:

// 应用主线程(UI 线程)Looper 实例 
private static Looper sMainLooper; 

// Worker 线程 Looper 实例,用 ThreadLocal 保存的对象都是线程独立的 
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

// 与当前 Looper 对应的消息队列 
final MessageQueue mQueue;


// 当前 Looper 所在的线程 
final Thread mThread;


//对外公开初始化方法 在普通线程中初始化 Looper 调用此方法
public static void prepare() {
// 初始化一个可以退出的 Looper ,注意这里的传递的参数为true 表示可以退出
    prepare(true);
}


//对外公开初始化方法, 在应用主线程(UI 线程)中初始化 Looper 调用此方法
 public static void prepareMainLooper() {
        //因为是主线程,初始化一个不允许退出的 Looper
        prepare(false);
        synchronized (Looper.class) {
// 如果 sMainLooper 不等于空说明已经创建过主线程 Looper 了,不 应该重复创建
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
 

//内部私有初始化方法  @param quitAllowed 是否允许退出 Looper
private static void prepare(boolean quitAllowed) {
// 每个线程只能有一个 Looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
    // 保存实例 保证每个Looper对线程一一对应.
        sThreadLocal.set(new Looper(quitAllowed));
    }

//私有构造方法  @param quitAllowed 是否允许退出 Looper
private Looper(boolean quitAllowed) {
// 初始化 MessageQueue
        mQueue = new MessageQueue(quitAllowed);
// 得到当前线程实例
        mThread = Thread.currentThread();
    }

从源码分析可以知道:

 真正创建 Looper 实例的构造方法中其实很简单,就是创建了对应的 MessageQueue 实例,然后得到当前线程,值得注意的是 MessageQueue 和线程 实例都是被 final 关键字修饰的,只能被赋值一次。

对外公开初始化方法 prepareMainLooper()是为应用主线程(UI 线程)准备的, 应用刚被创建就会调用该方法,所以我们不该再去调用它。

开发者可以通过调用对外公开初始化方法 prepare()对自己的 worker 线程创建 Looper,但是要注意只能初始化一次。

调用 Looper.prepare()方法初始化完成后,可以调用 myLooper()和 myQueue() 方法得到当前线程对应的实例:

 /**
     * Returns the application's main looper, which lives in the main thread of the application.
      获取UI线程的Looper
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }



    /**
     * Gets this looper's message queue.
       获取Looper一一对应的 MessageQueue
     *
     * @return The looper's message queue.
     */
    public @NonNull MessageQueue getQueue() {
        return mQueue;
    }


    /**
     * Gets the Thread associated with this Looper.
       获取Looper依附于的线程
     *
     * @return The looper's thread.
     */
    public @NonNull Thread getThread() {
        return mThread;
    }

2.2 退出Looper

退出 Looper 有安全与不安全两种退出方法,其实对应的就是 MessageQueue 的 安全与不安全方法:

public void quit() {
        mQueue.quit(false);
    }


public void quitSafely() {
        mQueue.quit(true);
    }

两个方法的区别

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                //安全退出
                removeAllFutureMessagesLocked();
            } else {
                //直接退出
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

//从队列头中取消息,有一个算 一个,全部拿出来回收掉
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }



private void removeAllFutureMessagesLocked() {
        //System.currentTimeMillis() 代表的是从 1970-01-01 00:00:00 到当前时间的毫秒数
        //SystemClock.uptimeMillis()获取当前系统时间:从系统开机到现在的毫秒数(不包括深度休眠的时间)
        //假设当前时间为 8:00:00
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            // 判断当前消息对象的预处理时间是否晚于当前时间
            if (p.when > now) {
               // 如果当前消息对象的预处理时间晚于当前时间直接全部暴力清除
                removeAllMessagesLocked();
            } else {
                Message n;
// 如果当前消息对象的预处理时间并不晚于当前时间 
// 说明有可能这个消息正在被分发处理 
// 那么就跳过这个消息往后找晚于当前时间的消息
                for (;;) {
                    n = p.next;
                    //消息链表最后一个为null,说明没有消息了,就直接return掉
                    if (n == null) {
                        return;
                    }
         // 如果找到了晚于当前时间的消息结束循环
         // 消息队列已经是通过时间先后排列好的, 当你找到了第一个 8:00:05的消息, 那么链表后面的肯定都是大于8:00:05的消息,所以这里就可以直接break退出循环了.
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
             // n 就是那个晚于当前时间的消息 
             // 从 n 开始之后的消息全部回收
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

removeAllMessagesLocked(),简单暴力直接清除掉队列中所有的消息。 

removeAllFutureMessagesLocked(),清除掉可能还没有被处理的消息(就是说正在处理的消息不处理, 剩下的全部清除回收)

这么看来这个方法名字起的还挺合理的,很好的解释了是要删除还没有被处理的消息。

三. 运行 Looper 处理消息

调用 Looper.prepare()方法初始化完成 Looper 后就可以让 Looper 去工作了,只需要调用 Looper.loop()方法即可。

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
// 得到当前线程下的 Looper
        final Looper me = myLooper();

// 如果还没初始化过抛异常  Looper.prepare() 还没有被调用
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

// 得到当前线程下与 Looper 对应的消息队列
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        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 (;;) {
// 从队列中取出一个消息,这可能会阻塞线程
            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);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            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;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
//尝试将消息分发给宿主(Handler) 
//dispatchMessage 为宿主 Handler 的接收消息方法
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                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必须掌握的知识点

1. Question: 一个线程有几个Looper?

      Answer: 一个

2. Question :子线程中创建Handler 需要做什么?

     Answer:   三部曲

  1. Looper.prepare  
        |
        | 
     //这里是工作区
        |
        |
  2.  Looper.loop     


  3.  Looper.quit 

3. Question: 子线程工作完后,需要退出(Looper.quit) ,那么UI线程需要退出嘛?

     Answer:   不需要, 主线程不让退出

 

五. 总结

        Looper 的功能很简单,核心方法 Looper.loop()就是不断的从消息队列中取出消 息分发给对应的宿主 Handler,它与对应 MessageQueue 息息相关,一起创建, 一起退出。

        Looper 更想强调的是线程的独立性与唯一性,利用 ThreadLocal 保证每个线程只 有一个 Looper 实例的存在。利用静态构造实例方法保证不能重复创建 Looper。

        Looper.prepareMainLooper()是比较特殊的方法,它是给 UI 线程准备,理论上 开发者在任何情况下都不应该调用它。

六. 致谢

感谢享学的老师,让人理解了这些晦涩难懂的源码, 也加入了自己的一些理解. 供大家参考,谢谢


http://lihuaxi.xjx100.cn/news/955257.html

相关文章

关于如何合理设置线程池参数解决方案

关于如何合理设置线程池参数解决方案&#xff08;ThreadPoolExecutor&#xff09; 线程池参数有哪些 我们直接来看构造方法 ... public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6,ThreadFactory var7, Rejecte…

Linux系统的稳定性优势有哪些

Linux系统的一些优点 在线使用Linux服务器为您提供了如此多的优势。其实它的优势比其劣势更重&#xff0c;其中一些是&#xff1a; 1、提供稳定性&#xff0c;因为基于Linux的服务器不容易崩溃。在遇到碰撞的情况下&#xff0c;整个系统都不受影响。 2、降低对潜在的系统威胁…

【2023】DevOps、SRE、运维开发面试宝典之Istio相关面试题

文章目录 1、什么是Service Mesh服务网格?2、微服务治理网格的特点?3、Istio服务网格的组件有那些?4、Istio常见的资源控制器有哪些?5、应用程序接入Sidecar6、Istio与K8S集成架构7、Istio Gateway网关资源控制器8、Istio VirtualService虚拟服务资源9、Istio DestinationR…

Gitlab普通用户转管理员

GitLab是常用的分部式代码库版本开源软件&#xff0c;默认系统中只有一个管理员。在工作中&#xff0c;如果有多个项目&#xff0c;则需要多个管理员分别管理各个的代码仓库&#xff0c;需要把多个普通用户配置成管理员&#xff0c;在Gitlab页面上&#xff0c;不能直接通过操作…

消息队列 面试题 整理

消息队列 为什么要使用消息队列&#xff1f; 异步解耦&#xff1a;关注的是通知而非处理。 流量削峰&#xff1a;将短时间内高并发的请求持久化&#xff0c;然后逐步处理&#xff0c;削平高峰期的请求。 日志收集&#xff1a; 事务最终一致性 系统间的消息通信方式&#xff…

测开:vue入门(1)

目录 一、背景 二、介绍 三、创建项目 3.1 创建vue项目 方式二&#xff1a;直接在html页面中&#xff0c;引入vue 3.2 直接在html页面中&#xff0c;引入vue 3.2.1 引入在线的vue&#xff08;方式一&#xff09; 3.2.2 将vue 下载到本地&#xff08;方式二&#xff09; …

第 46 届世界技能大赛浙江省选拔赛“网络安全“项目B模块任务书

第46届世界技能大赛浙江省选拔赛"网络安全"项目B模块&#xff08;网络安全事件响应、数字取证调查&#xff09;第46届世界技能大赛浙江省选拔赛"网络安全"项目B模块2.1 第一部分 事件响应2.2 第二部分 数字取证调查2.3 第三部分 应用程序安全第46届世界技能…

Shader(光线追踪二)

辐射度量Irradiance &#xff08;入射在表面点上的单位面积的能量&#xff09;Radiance&#xff08;单位立体角、单位投影面积发射、反射、传输或接收的能量&#xff09;BRDF(从每个入射方向到每个输出方向反射了多少光)1.反射方程2.渲染方程期望p&#xff08;x&#xff09;》P…