Android---Handler 源码解析

news2024/10/3 0:22:53

在 android 开发中,经常会在子线程中进行一些操作,当操作完后会通过 handler 发送一些数据给主线程,通知主线程做响应的操作。原理:子线程 handler 主线程,其实构成了线程模型中的经典问题,生产者-消费者模型(子线程往消息队列中放消息,主线程从消息队列中取消息)。生产者-消费者模型:生产者和消费者在同一时间段内共用一个存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。

 

1 Handler 源码分析

1.1 handler 机制的相关类

\bullet Handler: 发送和接收消息;

\bullet Looper: 用于轮询消息队列,一个线程只能有一个Looper;

\bullet Message: 消息实体;

\bullet MessageQueue: 消息队列,用于存储消息和管理消息。

1.2 创建 Looper

创建 Looper 的方法是调用 Looper.prepare() 方法。在 ActivtyThread 中的 main 方法为我们 prepare 了

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    //其他代码省略...
    Looper.prepareMainLooper(); //初始化Looper以及MessageQueue

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLogging(new
        LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop(); //开始轮循操作

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLopper()

public static void prepareMainLooper() {
    prepare(false);//调用prepare(), 消息队列不可以quit
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been
            prepared.");
        }
        sMainLooper = myLooper();
    }
}

prepare 有两个重载的方法,主要看 prepare(boolean quitAllowed),quitAllowed 的作用是创建 MessageQueue 时标识消息队列是否可以销毁,主线程不可以销毁。

public static void prepare() { 
    prepare(true);//消息队列可以quit
  }
  // 私有的构造函数
  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//不为空表示当前线程已经创建了Looper
            throw new RuntimeException("Only one Looper may be created per thread");
            //每个线程只能创建一个Looper
        }
        // ThreadLocal 多线程中重要的知识点,线程上下文的存储变量
        sThreadLocal.set(new Looper(quitAllowed));//创建Looper并设置给sThreadLocal,这样get的
        时候就不会为null了
  }

注意:一个线程只要一个Looper。如何保证只有一个?

       只有一个ThreadLocal(源码里为final),就保证只有一个 Looper。ThreadLocalMap里的<key, value>  ---->    <唯一的ThreadLocal, Looper>。为了保证 ThreadLocalMap.set(value) 时,value 不会被覆盖(即Looper不会改变),会先进行上面代码的 if 操作,if (sThreadLocal.get() != null) , 不为空表示当前线程已经创建了Looper,然后直接抛异常结束prepare。如果 if 不成立,则 new Looper()。

1.3 创建MessageQueue以及Looper与当前线程的绑定

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue
    mThread = Thread.currentThread(); //当前线程的绑定
}
MessageQueue 的构造方法
MessageQueue(boolean quitAllowed) {
    //mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的            quit()方法
    //就不贴源码了
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

1.4 Looper.loop() 

同时是在 main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 开始轮询

public static void loop() {
    final Looper me = myLooper();//里面调用了sThreadLocal.get()获得刚才创建的Looper对象
    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();
    for (;;) {
        //这是一个死循环,从消息队列不断的取消息
        Message msg = queue.next(); // might block
        if (msg == null) {

            //由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage
            enqueueMessage后
            //队列里才有消息
            // 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
    Printer logging = me.mLogging;
    if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " " +
        msg.callback + ": " + msg.what);
    }
    msg.target.dispatchMessage(msg);//msg.target就是绑定的Handler,详见后面Message的部
    分,Handler开始
    //后面代码省略.....
    msg.recycleUnchecked();
    }
}

1.5 创建 Handler

最常见的创建 handler 的方式

Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    }
};

在内部调用 this(null, false);

public Handler(Callback callback, boolean async) {
    //前面省略
    mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**!
    if (mLooper == null) {
        throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//创建消息队列MessageQueue
    mCallback = callback; //初始化了回调接口
    mAsynchronous = async;
}
Looper.myLooper()
//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
sThreadLocal.get() will return null unless you’ve called prepare(). 这句话告诉我们 get 可能返回 null 除非先调用 prepare()方法创建 Looper 。在前面已经介绍了

1.6 创建 Message

可以直接 new Message()  但是有更好的方式 Message.obtain()。因为可以检测是否有可以复用的 Message,复用避免过多的创建、销毁 Message 对象达到优化内存和性能的目的。
public static Message obtain(Handler h) {
    Message m = obtain();//调用重载的obtain方法
    m.target = h;//并绑定的创建Message对象的handler

    return m;
}
public static Message obtain() {
    synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全
    if (sPool != null) {
        //sPool是就是handler dispatchMessage 后 通过recycleUnchecked回收用以复用的Message
        Message m = sPool;
        sPool = m.next;
        m.next = null;
        m.flags = 0; // clear in-use flag
        sPoolSize--;
        return m;
        }
    }
    return new Message();
}

1.7 Message 和 Handler 的绑定

创建 Message 的时候可以通过 Message.obtain(Handler h) 这个构造方法绑定。当然也可以在 Handler 中的 enqueueMessage() 绑定,所以发送 Message 的方法都会调用此方法队列,所以在创建 Message 的时候是可以不绑定的。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; //Message 与 Handler 绑定
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }

    return queue.enqueueMessage(msg, uptimeMillis);
}

1.8 Handler 发送消息

Handler 发送消息的重载方法很多,但是主要的只有2种。sendMessage(Message), sendMessage方法通过一些列重载方法调用,sendMessage --(调用)--> sendMessageDelayed --(调用)--> sendMessageAtTime  --(调用)--> enqueueMessage(MessageQueue里),将消息保存在消息队列中,最终由 Looper 取出,交给 Handler 的 dispatchMessage 进行处理。

我们可以看到在 dispatchMessage 方法中,message 中 callback 是一个 Runable 对象,如果 msg.callback != null,则直接调用 callback 的 run 方法,否则判断 mCallBack 是否为空,mCallback 在 Handler 构造方法初始化,在主线程直接通过无参的构造方法 new 出来的为 null,所以会直接执行后面的 handleMessage() 方法。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//callback在message的构造方法中初始化或者使用
handler.post(Runnable)时候才不为空
    handleCallback(msg);
    } else {
        if (mCallback != null) {//mCallback是一个Callback对象,通过无参的构造方法创建出来的handler,
该属性为null,此段不执行
            if (mCallback.handleMessage(msg)) {
            return;
            }
        }
    handleMessage(msg);//最终执行handleMessage方法
    }
}
    private static void handleCallback(Message message) {
    message.callback.run();
}

1.9 Handler 处理消息

在 handleMessage(Message) 方法中,我们可以拿到 message 对象,根据不同的需求进行处理,整个 Handler 机制的流程就结束了。

handler不仅仅是用于线程间通信,它只是 handler 的一个附属功能。而 handler 正真的功能是"所以代码都是在 handler 中运行,它维持着我们 Android app 运行的整个框架"。

Handler 线程间通信的整个调度流程

handler.sendMessage(开始) -->  MessageQueue.enqueueMessage(消息队列,把Message 插入消息队列) --> looper.loop() --> messageQueue.next() --> handler.dispatchMessage--> handler.handleMessage(结束)

子线程 到 主线程的切换:handler.sendEmptyMessage(子线程) -到-> handler.handleMessage(主线程)

MessageQueue: 由单链表实现的优先级队列(按时间排序)

MessageQueue 里有一个 message 对象,一个 next(Message)也是 Message 对象。

MessageQueue(fianl) 是跟随 Looper 创建的

Handler 相关面试题:

1  一个线程有几个 Handler ?

Hanler 机制只有一个,Handler 想要几个就几个(new Handler) 

2 一个线程有几个 Looper ?如何保证?

略....

3 Handler 内存泄漏原因?为什么其它的内部类没有说过这个问题?

java基础:内部类会持有外部类对象(Handler 是一个内部类)。例如 RecyclerView 的 Adapter 里的 ViewHolder 也是一个内部类,它就没有内存泄漏的问题

答:与生命周期有关。

4 子线程里 new Handler 要做什么准备工作?

在子线程里准备 Looper.prepare() 和 Looper.loop()两个工作,而主线程是在程序启动时,ActivtyThread 里的 main 方法就为我们准备了。

子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

调用 quit()。调用 quit 首先会唤醒线程,然后 MessageQueue 给了一个 null,最后退出 loop (结束了)

既然可以存在多个 Handler MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?

锁。

synchronized: 内置锁。可以修饰代码块、函数。上锁、开锁都是由 JVM 完成(内置到系统里面的锁 --> 内置锁)。

7 Looper 死循环为什么不会导致应用卡死?

ANR(Application Not Response)应用不响应。Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR

ThreadLocal 全局唯一(整个App 就一个, 由 static final 修饰),Looper 是线程唯一(一个线程一个)

线程1 --》ThreadLocalMap 1 --》<唯一的ThreadLocal, Looper1> --》MessageQueue1

线程2 --》ThreadLocalMap 2 --》<唯一的ThreadLocal, Looper2> --》MessageQueue2

享元设计模式

用于减少创建对象的数量,以减少内存占用和提高性能。可以防止内存抖动,内存复用。

Handler中message的内容置空、Activity的管理都是享元设计模式

消息机制之同步屏障 -- 架构思维

同步屏障:处理紧急的消息。

消息是根据执行时间进行先后排序,然后消息是保存在队列中,因而消息只能从队列的队头取出来。当有紧急消息时,就会将 msg.target 置为 null,当我们在轮询消息时,发现当前 msg != null && msg.target == null(如下代码), 就会遍历整个 msg 队列,找到那个紧急消息,然后处理掉。

HandlerThread 存在的意义

HandlerThread 是 Thread 的子类,就是一个线程,只是它在自己的线程里帮我们创建了 Looper

\bullet 方便使用,方便初始化,方便获取线程 looper

\bullet 保证了线程安全

HandlerThread 的用法:在子线程中创建一个自己的 Looper

 ​​​​​​​

 

上面代码为 HandelrThread 里的 run 方法,为我们创建一个自己的 Looper,因为它里面有写 Looper.prepare() 和 mLooper = Looper.myLooper(); 然后通过 getLooper() 方法就能获取到 HandlerThread 为我们创建的 Looper。

当 run() 方法执行的 Looper.prepare() 时,此时右调用了 getLooper() 方法,那么 mLooper 就为空,此时执行 wait() 操作,并释放锁。wait 释放锁后 run() 方法继续运行,给 mLooper 赋值为 Looper.myLooper()。然后执行 notifyAll() 唤醒其它等待的锁(是唤醒,并不代表这里的 getLooper 右继续运行),notifyAll() 并不能释放锁。当 run() 方法全部执行完后,getLooper() 方法里才又重新拿到锁继续运行。注意:run() 方法和 getLooper() 方法里是同一个锁。

IntentService 存在的意义

Service 一般用于处理后台耗时任务。

应用需求:一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功。

这个需求可能用多个线程来处理,一个线程处理完一个子任务 --> 下一个线程处理完一个子任务 --> 下一个线程继续 ....

IntentService 就可以帮我们完成这个工作。而且,能够很好的管理线程,保证一个时间段里只有一个子线程处理一个子任务,而且是一个一个的完成任务,有序进行。

IntentService 的onCreate() 函数里会创建一个 HandlerThread(异步线程),然后onHandleIntent() 函数处理任务(是一个抽象函数,继承),耗时任务就在里面处理。

Service 的生命周期是后台管理的,IntentService extends Servicej, 所以IntentService 的生命周期也是后台管理的。所以 IntentService 的 onCreate() 函数是由后台调度的,以及 onStart() 函数。

 

走 onStart() 方法在子线程里发送一个消息。 再由 ServiceHandler 来处理 IntentService 里子线程发送的消息。

ServiceHandler 处理接收的消息,在 handleMessage里调用 onHandleIntent(我们重写)来处理消息。 

所以综上,我们在使用 IntentService 时,就可以 new 一个 IntentServcie的子类,实现 onHandleIntent 来处理耗时的后台任务。

消息处理完后调用 stopSelf(),service 自动停止 --> 内存自动释放

 Glide 生命周期

glide.with(context).from(url).into(imageView)

这里的 context --> fragment/activity 。当我们的 fragment/activity 生命周期结束时,Glide 也结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369631.html

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

相关文章

HHDESK文本对比功能

对比文件夹和图片而言&#xff0c;文本的更改更加频繁且琐碎&#xff1b;个别词语的更改更是让人“健忘”。 如果有一款工具能够直观的对文本进行对比&#xff0c;并且清晰的划分显示&#xff0c;那么便可以大量节约办公人员的精力和时间。 1 文本对比功能简介 版本更新&…

Ubuntu20.04配置深度学习环境

Ubuntu20.04配置深度学习环境 对于一台新Ubuntu20.04主机&#xff0c;想要直接进行深度学习模型的训练&#xff0c;需要进行一些常规配置。本文针对的情况包括物理机和虚拟机。安装部分本文不介绍&#xff0c;只介绍配置环境部分。 1. 安装中文输入法 参考如下博客&#xff…

Web安全最详细学习路线指南,从入门到入职(含书籍、工具包)

在这个圈子技术门类中&#xff0c;工作岗位主要有以下三个方向&#xff1a; 安全研发 安全研究&#xff1a;二进制方向 安全研究&#xff1a;网络渗透方向 下面逐一说明一下. 第一个方向&#xff1a;安全研发 你可以把网络安全理解成电商行业、教育行业等其他行业一样&#x…

《卓有成效的管理者》系列-总结篇

作者【美】彼得-德鲁克简介书中提到&#xff0c;每一位知识工作者其实都是管理者&#xff0c;而且卓有成效是每个管理者必须做到的事。书中认为所有负责行动和决策而且能够提高机构工作效率的人&#xff0c;都应该像管理者一样工作和思考。而且&#xff0c;一位卓有成效的管理者…

自考都有哪些科目?怎么搭配报考?

第一次自考科目搭配 先报理论课&#xff0c;熟悉学习和考试套路 参考搭配模式&#xff1a; 一、全报考公共课 公共课难度较低&#xff0c;通过率高&#xff0c;复习起来比较轻松。对于不确定考什么专业&#xff0c;后期想换专业的同学&#xff0c;考过公共课&#xff0c…

记一次Binder内存不足导致的应用被杀

每个进程的可用Binder内存大小是 1M-8KB 也就是900多KB 事情的起因的QA压测过程发生进程号变更&#xff0c;怀疑APP被杀掉过&#xff0c;于是开始看日志&#xff08;实际后来模拟的时候可以发现app确实被杀掉了&#xff09; APP的压测平台会上报进程号变更时间点&#xff0c;发…

基于C++环境的gRPC安装配置:vcpkg+CMake+VS2022

前言gRPC 是 Google 提供的一个 RPC 框架&#xff0c;用于在网络上实现多个应用程序之间的通信。gRPC的优点是支持多种语言&#xff0c;因此可以轻松使用C应用程序与Go或Python应用程序进行通信。尽管CSDN、知乎有很多讲解gRPC在C环境安装的文章&#xff0c;其中最常见的是使用…

LaneAF论文解读和代码讲解

论文地址&#xff1a;https://arxiv.org/abs/2103.12040 一、论文简介 LaneAF是一个语义分割聚类后处理的一种方法。相对于之前的用聚类算法对embedding分支聚类的方法&#xff0c;该论文提出了水平和垂直两个向量场&#xff0c;用来取缔之前的普通聚类。根据向量场就可以完成…

HashMap红黑树源码解读

链表转换为红黑树节点当往hashMap中添加元素&#xff0c;在同一个hash槽位挂载的元素超过8个后&#xff0c;执行treeifyBin方法。在treeifyBin方法中&#xff0c;只有当tab数组&#xff08;hash槽位&#xff09;的长度不小于MIN_TREEIFY_CAPACITY&#xff08;默认64&#xff09…

modbus协议

1 MODBUS 应用层报文传输协议 ADU应用数据单元PDU协议数据单元MBMODBUS协议MBAPMODBUS协议 ADU&#xff1a;地址域 PDU 差错校验PDU&#xff1a;功能码数据 串行链路&#xff1a; 最大RS485 ADU 256 字节PDU 256 - 服务器地址&#xff08;1字节&#xff09;- CRC&#xf…

Linux学习(7)文件权限与目录配置

目录 1. 使用者与群组 1&#xff0c;文件拥有者 2&#xff0c;群组概念 3&#xff0c;其他人 Linux 用户身份与群组记录的文件 2.Linux 文件权限概念 Linux的文件属性 第一栏代表这个文件的类型与权限(permission)&#xff1a; 第二栏表示有多少档名连结到此节点(i-no…

linux CUDAtoolkit+cudnn+tensorrt 的安装

windows上 CUDAtoolkitcudnn的安装 CUDAtoolkitcudnn的安装 须知 use command ubuntu-drivers devices查看你的显卡类型和推荐的驱动版本百度 nvidia-driver-*** 支持的 cuda 或 去文档查找驱动(比如450&#xff0c;460)匹配的cuda版本 下载 网盘下载 https://www.aliyundr…

Python实现贝叶斯优化器(Bayes_opt)优化Catboost回归模型(CatBoostRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景贝叶斯优化器 (BayesianOptimization) 是一种黑盒子优化器&#xff0c;用来寻找最优参数。贝叶斯优化器是…

18523-47-2,3-Azidopropionic Acid,叠氮基丙酸,可以与炔烃发生点击化学反应

【中文名称】3-叠氮基丙酸【英文名称】 3-Azidopropionic Acid&#xff0c;3-Azidopropionic COOH【结 构 式】【CAS】18523-47-2【分子式】C3H5N3O2【分子量】115.09【纯度标准】95%【包装规格】1g&#xff0c;5g&#xff0c;10g【是否接受定制】可进行定制&#xff0c;定制时…

龙蜥开发者说:为爱发电!当一个龙蜥社区打包 Contributor 是怎样的体验?| 第16期

「龙蜥开发者说」第 16 期来了&#xff01;开发者与开源社区相辅相成&#xff0c;相互成就&#xff0c;这些个人在龙蜥社区的使用心得、实践总结和技术成长经历都是宝贵的&#xff0c;我们希望在这里让更多人看见技术的力量。本期故事&#xff0c;我们邀请了龙蜥社区开发者 Fun…

无线通信时代的新技术----信标( Beacon)

随着IT技术的发展&#xff0c;无线通信技术也在不断发展。 现已根据预期用途开发了各种无线通信技术&#xff0c;例如 NFC、WIFI、Bluetooth和 RFID。 车辆内部结构的复杂化和数字化&#xff0c;车载通信网络技术的重要性也越来越高。 一个典型的例子是远程信息处理。 远程信息…

注重邮件数据信息安全 保障企业稳步发展

近年来&#xff0c;世界各地的政府、银行、电信公司、制造业以及零售业等&#xff0c;不断发生数据泄密事件。 就企业而言&#xff0c;邮件数据很容易成为竞争对手或者诈骗者窃取的目标。 电子邮件是企业中一种重要的沟通工具但是随着网络攻击手段的不断升级&#xff0c;电子邮…

RN面试题

RN面试题1.React Native相对于原生的ios和Android有哪些优势&#xff1f;1.性能媲美原生APP 2.使用JavaScript编码&#xff0c;只要学习这一种语言 3.绝大部分代码安卓和IOS都能共用 4.组件式开发&#xff0c;代码重用性很高 5.跟编写网页一般&#xff0c;修改代码后即可自动刷…

关系数据库

关系的三类完整性约束实体完整性规则• 保证关系中的每个元组都是可识别的和惟一的 • 指关系数据库中所有的表都必须有主键&#xff0c;而且表中不允许存在如下记录&#xff1a;– 无主键值的记录– 主键值相同的记录• 原因&#xff1a;实体必须可区分• 就像实体-学生&#…

谷歌外推留痕,谷歌搜索留痕快速收录怎么做出来的?

本文主要分享谷歌搜索留痕的收录效果是怎么做的&#xff0c;让你对谷歌留痕技术有一个全面的了解。 本文由光算创作&#xff0c;有可能会被修改和剽窃&#xff0c;我们佛系对待这样的行为吧。 谷歌搜索留痕快速收录怎么做出来的&#xff1f; 答案是&#xff1a;通过谷歌蜘蛛…