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

news2024/11/15 8:53:45

一. 简介

        我们接上一篇文章: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://www.coloradmin.cn/o/382507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL运维知识

1 日志1.1 错误日志1.2 二进制日志查看二进制日志&#xff1a;mysqlbinlog ./binlog.000007purge master logs to binlog.000006reset mastershow variables like %binlog_expire_logs_seconds%默认二进制文件只存放30天&#xff0c;30天后会自动删除。1.3 查询日志1.4 慢查询日…

React(四):事件总线、setState的细节、PureComponent、ref

React&#xff08;四&#xff09;一、事件总线二、关于setState的原理1.setState的三种使用方式&#xff08;1&#xff09;基本使用&#xff08;2&#xff09;传入一个回调&#xff08;3&#xff09;第一个参数是对象&#xff0c;第二个参数是回调2.为什么setState要设置成异步…

Android kotlin实战之协程suspend详解与使用

前言 Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同&#xff0c;async 与 await 在 Kotlin 中并不是关键字&#xff0c;甚至都不是标准库的一部分。此外&#xff0c;Kotlin 的 挂起函数 概念为异步操作提供…

π型滤波器 计算_π型滤波电路

滤波器在功率和音频电子中常用于滤除不必要的频率。而电路设计中&#xff0c;基于不同应用有着许多不同种类的滤波器&#xff0c;但它们的基本理念都是一致的&#xff0c;那就是移除不必要的信号。所有滤波器都可以被分为两类&#xff0c;有源滤波器和无源滤波器。有源滤波器用…

重新认识 Java 中的内存映射(mmap)

mmap 基础概念 mmap 是一种内存映射文件的方法&#xff0c;即将一个文件映射到进程的地址空间&#xff0c;实现文件磁盘地址和一段进程虚拟地址的映射。实现这样的映射关系后&#xff0c;进程就可以采用指针的方式读写操作这一段内存&#xff0c;而系统会自动回写脏页到对应的文…

电源程控软件下载安装教程

软件&#xff1a;电源程控软件NS-PowerSupply 语言&#xff1a;简体中文 环境&#xff1a;NI-VISA 安装环境&#xff1a;Win10以上版本&#xff08;特殊需求请后台私信联系客服&#xff09; 硬件要求&#xff1a;CPU2GHz 内存4G(或更高&#xff09;硬盘500G(或更高&#xf…

2023年2月安全事件盘点

一、基本信息 2023年2月安全事件共造成约3796万美元损失&#xff0c;相较于上个月&#xff0c;安全事件数量与损失金额都有显著上升&#xff0c;其中Platypus Finance闪电贷攻击为单次利用损失之最高达850万美元。本月RugPull数量基本与上月持平&#xff0c;损失金额占比显著降…

网站打不开数据库错误等常见问题解决方法

1、“主机开设成功&#xff01;”上传数据后显示此内容&#xff0c;是因为西部数码默认放置的index.htm内容&#xff0c;需要核实wwwroot目录里面是否有自己的程序文件&#xff0c;可以删除index.htm。 2、恭喜&#xff0c;lanmp安装成功&#xff01;这个页面是wdcp的默认页面&…

用 Real-ESRGAN 拯救座机画质,自制高清版动漫资源

内容一览&#xff1a;Real-ESRGAN 是 ESRGAN 升级之作&#xff0c;主要有三点创新&#xff1a;提出高阶退化过程模拟实际图像退化&#xff0c;使用光谱归一化 U-Net 鉴别器增加鉴别器的能力&#xff0c;以及使用纯合成数据进行训练。 关键词&#xff1a;Real-ESRGAN 超分辨率 视…

一文彻底搞懂cookie、session、token、jwt!

前言 随着Web应用程序的出现&#xff0c;直接在客户端上存储用户信息的需求也随之出现。者背后的想象时合法的&#xff1a;与特定用户相关的信息都应该保存在用户的机器上。无论是登录信息、个人偏好、还是其他数据&#xff0c;Web应用程序提供者都需要有办法 将他们保存在客户…

电子技术——CMOS 逻辑门电路

电子技术——CMOS 逻辑门电路 在本节我们介绍如何使用CMOS电路实现组合逻辑函数。在组合电路中&#xff0c;电路是瞬时发生的&#xff0c;也就是电路的输出之和当前的输入有关&#xff0c;并且电路是无记忆的也没有反馈。组合电路被大量的使用在当今的数字逻辑系统中。 晶体管…

Educational Codeforces Round 144 (Rated for Div. 2)(A~C)

A. Typical Interview Problem从1开始&#xff0c;遇到3的倍数就在字符串后面加F&#xff0c;遇到5的倍数就在字符串后面加B&#xff0c;若遇到3和5的倍数&#xff0c;就加入FB&#xff0c;这样可以写一个无限长的字符串&#xff0c;给出一个长度最多为10的字符串&#xff0c;判…

CLion+Opencv+QT开发相关

一、QT安装和配置其实我并没有直接在Qt上开发&#xff0c;下载Qt而是因为&#xff1a;CLion可以通过Qt的MinGW作为Toolset&#xff0c;并且可以将Qt creator作为external tool&#xff1b;在进行Opencv的编译安装中可以用Qt自带的MinGW进行编译和安装&#xff0c;不用另外下载M…

C++类和对象:初始化列表、static成员和友元

目录 一. 初始化列表 1.1 对象实例化时成员变量的创建及初始化 1.2 初始化列表 1.3 使用初始化列表和在函数体内初始化成员变量的效率比较 1.4 成员变量的初始化顺序 1.5 explicit关键字 二. static成员 2.1 static属性的成员变量 2.2 static属性的成员函数 三. 友元 …

废气处理设备远程监控

当今工业迅速的发展&#xff0c;工业带给人们的经济效益显著&#xff0c;而同时污染问题也备受关注。国家环保标准对排放至大气的废气指标提出了更高的要求。面临着环保压力&#xff0c;企业为走可持续发展之路&#xff0c;为维护员工利益、改善工作环境及周边环境不受影响&…

一、Sping框架引入

OCP开闭原则 什么是OCP&#xff1f; OCP是软件七大开发原则当中最基本的一个原则&#xff1a;开闭原则 对什么开&#xff1f;对扩展开放。 对什么闭&#xff1f;对修改关闭。OCP原则是最核心的&#xff0c;最基本的&#xff0c;其他的六个原则都是为这个原则服务的。OCP开闭原则…

计算机行业回暖?看网友怎么说?

就业寒潮之下&#xff0c;去年的应届生们可谓哀嚎一片&#xff0c;不少人晒出自己的0offer秋招战绩。 就连过去无往不利的计算机行业&#xff0c;亦不例外。但今年开始&#xff0c;计算机行业逐渐有了回暖的迹象和讨论。 陆续有不少之前哭诉收获惨淡的计算机专业同学&#x…

防静电和浪涌TVS layout设计要点

电子产品精密化刚看过了CES2023&#xff0c;雷卯的外贸伙伴们看了最新的AR,VR,5G产品&#xff0c;新的电子产品更智能、更复杂&#xff0c;嵌入了脆弱和敏感的集成电路。这些设备的环境往往很恶劣&#xff0c;产生高水平静电和快速瞬态浪涌。这些ESD事件可能会干扰设备&#xf…

IIS之web服务器的安装、部署以及使用教程(图文详细版)

WEB服务器的部署 打开虚拟机后查看已经开放的端口&#xff0c;可以看到没有TCP 80、TCP 443&#xff0c;说明HTTP服务端口没有打开 打开我的电脑—双击CD驱动器 选择安装可选的Windows组件 选择应用程序服务器—打开Internet信息服务—选择万维网服务和FTP服务 一路确…

uniapp-首页配置

为了获取到后台服务器发来的数据&#xff0c;需要配置相应的网络地址。位置在main.js入口文件中。 import { $http } from escook/request-miniprogramuni.$http $http // 配置请求根路径 $http.baseUrl https://api-hmugo-web.itheima.net// 请求开始之前做一些事情 $http.…