Android开发人员必看的 Handler 消息处理机制(源码实战)

156 篇文章 1 订阅
117 篇文章 1 订阅

Android 异步消息处理机制解析

Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue、Looper

但是当我们提到 Android 异步处理机制的时候,我们首先会想到 Handler,而大多数Android 初学者对于 Handler作用局限子线程如何更新 UI 这一方面;其实 Handler 能做的事情远不止于此,它贯穿于 Android 应用的整个生命周期,如果没有 Handler,我们编写的应用就无法正常运行;总的来说,它的作用体现在两大方面:

  • 处理延时任务(告诉app在将来的某个时间执行某个任务)

  • 线程之间通信(子线程更新UI就是其中的一个应用)

在程序中怎么让 Handler 识别出自己发送的消息呢?针对这个问题我们可以想到,在发送的时候给消息做一个标记,标记出发送消息的 Handler,那么在处理消息的时候就可以根据这个标记来找到应该要处理的 Handler 对象了

异步消息处理的流程

1、在主线程中创建一个 Handler 对象,并重写 handleMessage 方法,我们主要在 handleMessage 中进行一系列的操作 2、当子线程中需要进行 UI 更新时,就在子线程中创建一个 Message 对象,并通过 Handler 将这条消息发送出去 3、经 Handler 发送的消息会被添加到 MessageQueue 中等待被处理,而 Looper 会一直尝试从 MessageQueue 中取出消息进行处理 4、最后 Looper 会将消息发送到 Handler 的 handleMessage() 方法中处理

源码实战

Message

首先来看一下 Message 类中包含的一些重要字段:

	//用来识别消息的字段
	public int what;
	//Message中可以携带的整形数据arg1和arg2
	public int arg1;
	public int arg1;
	//Message中可携带的对象
	public Object obj;
	//记录是哪个Handler发送的消息
	Handler target;
	//指针,指向下一个Message
	Message next;

	//缓存池
	private static Message sPool;
	//当前缓存池的大小
	private static int sPoolSize = 0;
	//缓存池最大容量
	private static final int MAX_POOL_SIZE = 50;

接下来看一下它的构造方法:

	public Message() {
    }

由源码中看到构造方法中什么都没有,然而在开发中并不建议通过 new 的方式创建出 Message 对象;Message 中由一个 static 类型的 sPool 字段,它代表了缓存池

也就是说当一个消息被处理掉之后并不会直接销毁,而是放在缓存池中,下次再需要时可以通过 Message 提供的 obtain 方法从池中得到,这样一来便减少了内存的开销,实现了资源的复用

obtain 方法有很多重载,这里只分析无参的重载,其他方法的原理是类似的:

	public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

这个方法很简单,如果池子不为null则会取出池中第一个 Message(头节点)并将 sPoolSize 减一,否则返回一个 new 出来的 Message 对象

准备工作

在发送消息之前,要进行一些准备工作(初始化操作);这一点很好理解:想坐飞机要通过安检,因此首先要有一个安检机,所以在发消息之前要确保有一个 Looper 对象(MessageQueue 在 Looper 中创建);我们来看一下 Looper 的构造函数:

	private Looper(boolean quitAllowed) {
		//创建出消息队列
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
发送消息

发送消息是通过 Handler 提供的一系列 send 方法完成的,那么首先要有 Handler 对象才能发送,所以我们先来看一下 Handler 的构造方法:

	public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

有了 Handler 对象之后,就可以通过它提供的一系列 send、post 方法来发送消息了;不管用哪一个方法来发送消息,最终都会调用 Hander的 enqueueMessage 方法

Handle r的 enqueueMessage 方法如下所示:

	private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到不管是哪个方法,最终都会调用 MessageQueue 的 enqueueMessage 方法:

	boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            //如果目标为null抛异常
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            //如果已经在使用抛异常
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //如果队列位null或者message的when=0或者message的when小于队列第一个元素的when
            if (p == null || when == 0 || when < p.when) {
                //此时应该将新消息插入到链表首部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //找到p和prev,其中p为message的下一个元素,prev为message的上一个元素
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //将message插入到链表的对应位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

总的来说,一个线程中可以创建许多 Handler,然而这些 Handler 对象持有的 Looer、MessageQueue 引用指向的都是相同的对象(线程中唯一的的);这样一来,在其他线程中用这些 Handler 对象去发送消息(发出的消息持有发消息的 Handler 对象的引用),发出去的消息最终都是被放到了创建 Handler 线程中对应那个 MessageQueue 中

而创建 Handler 的线程中通过 Looper.loop 死循环不断地从消息队列中取出消息,这样便实现了线程之间的通信由于对消息的入队和出队操作都是加了锁的,因此便保证了通信的安全性

好了,以上就是今天要分享的内容,大家觉得有用的话,可以点赞分享一下;如果文章中有什么问题欢迎大家指正;欢迎在评论区或后台讨论哈~

作者:Android小丁
链接:https://juejin.cn/post/7148337440669827102

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值