Android 高级进阶之深入剖析消息机制

Stella981
• 阅读 366

Android 高级进阶之深入剖析消息机制

阅读时间:8 分钟

坐稳了没?要开车了哦

Android 高级进阶之深入剖析消息机制

写在前边

今天分享的内容是深入剖析 Android 的消息机制,有些人问了,一些功能会用不就行了吗?为什么还要分析底层源码呢?今天小鹿告诉你的是很多开源的项目都已经不需要我们造轮子了,重复造轮子是多么愚蠢的一件事。但是,Android 的底层源码和一些功能的实现让我们学习到底层的模式和逻辑实现。

学编程什么最重要,当然是逻辑思维了,即使你什么功能都能实现,逻辑思维能力差照样啥都干不了。你的思维逻辑能力差,在技术路线上已经决定了你的高度。

Android 的消息机制

Android 的消息机制主要是指 Handlerr 的运行需要底层的 MessageQueue 和 Looper 的支撑。

(1)MessageQueue 的中文翻译是消息队列。以队列的形式对外提供插入和删除工作。虽然叫做消息队列,但是内部存储结构并不是真正的队列,而是以单链表的数据结构来存储消息列表。

(2)Looper 的中文翻译为循环,我们叫它消息循环。由于 MessageQueue 只是一个存储单元,不会去处理消息。而 Looper 确弥补了这个功能,Looper 会以无限无限循环的形式去查找是否有新的消息,有的话就去处理消息,否则就一直等待。

学习思维导图:

以后文章中的思维导图是小鹿给大家精心整理的,这样对每篇分享的文章都有一个清晰地结构,有利于复习和整理。

Android 高级进阶之深入剖析消息机制

一、Android 消息机制概述

Android 消息机制主要是指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作过程。Handler 的主要作用就是将一个任务切换到某个指定的线程中去执行。

概述

(1)思考:为什么 Android 要提供这个功能呢?

答:因为 Android 规定访问 UI 线程只能在主线程中进行的,如果在子线程中访问 UI ,那么程序就会抛出异常。

(2)源码:经过查看看源码的 checkThread()方法对更新 UI 是否在主线程中更新,进行抛出异常信息提示开发者(相信在开发中都遇到过这个种情况)。

(3)过程:由于以上限制,这就要求开发者必须在主线程中更新 UI,但是 Android 又建议不要在主线程中进行过于耗时的工作,否则会产生应用程序无响应 ANR。考虑到这种情况,当我们在服务器拉去一些信息并显示到 UI 上时,拉去工作我们将在子线程中进行,拉取完毕之后不能再子线程中直接更新 UI ,没有 Handler ,那我们的确没有办法将访问 UI 的工作切换到主线程去执行。因此,系统之所以给我们提供 Handler,主要原因是为了解决在子线程中无法访问 UI 的矛盾。

(4)问题:

① 为什么不能再子线程中更新 UI? 
    答 : 因为 Android 的 UI 控件不是线性安全的。如果在多线程中并发的访问可能会导致 UI 控件处于不可预期的状态。

② 为什么不对 UI 控件的访问加上锁机制呢?

答 :首先,加上锁机制会让访问 UI 变的复杂,其次锁机制会降低 UI 的访问效率,因为锁机制会阻塞某些线程的执行。

最简单最高效的就是采用单线程模型来处理 UI 操作,只需通过 Handlerr 切换一下 UI 访问的执行线程即可。

Handler 的工作原理

Handler 创建时就会采用当前的 Looper 来构建内部的消息循环系统,如果当前没有 Looper ,那么就会报错。

怎么解决上述问题?两个方法:

① 为当前线程创建 Looper 即可。 
② 在 Looper 的线程中创建 Handler 也可以。

工作原理

(1)Handler 创建过程: Handler 被创建之后,内部的 MessageQueue 和 Looper 就与 Handler 一起工作协同工作了, 然后通过 Handler 的 post 方法将一个 Runnable 投递给 Handler 内部的 Looper 去处理;也可以通过 Handler 的 send 方法发送一个消息,也是通过 Looper 去处理的,其实 Post 方法最终也是通过 send 方法来完成的..

(2)send 方法的工作过程:当 Handler 的 send 方法被调用时,它会调用 MessageQueue 的 enqueueMessage 方法将这个消息放到消息队列中,然后 Looper 发现新消息,就会处理这个消息,最终消息的 Runnable 或者 Handler 的 handlerMessage 方法就会被调用。

Android 高级进阶之深入剖析消息机制

二、 Android 的消息机制分析

消息队列的工作原理

消息队列在 Android 主要是指 MessageQueue ,MessageQueue 主要包括两个操作:插入和删除。消息队列的内部实现并不是队列,实际上通过一条单链表的数据结构来维护消息队列,单链表在插入删除上很有优势。

① 插入(enqueueMessage):往消息队列中插入一条消息。(源码实现就是单链表的插入)

② 删除(next):从消息队列中取出一条消息并将其从消息队列中移除。(next 是一个无限循环的方法,消息队列没有信息就处于阻塞状态,有新消息到来就执行单链表的删除)

Lopper 的工作原理

Looper 在 Android 消息机制中扮演着消息循环的角色,作用:不停地从 MessageQueue 中查看是否有新的消息,如果有消息就会立刻处理,如果没有消息就会处于阻塞状态。

(1) Looper 的构造方法

① 创建一个 MessageQueue 消息队列。

② 将当前线程的对象保存起来。

(2)如何为一个线程创建 Looper

(Handle 的工作需要 Looper,没有 Looper 就会报错)

① 通过 Looper.prepare() 方法为线程创建一个 Looper 。

② 通过 Looper.loop() 方法来开启消息循环。

(3)创建线程的另一种方法

① 主线程 Looper 的获取。 Looper 这个方法主要给线程也就是 ActivityThread 创建 Looper 使用的,本质也是通过 prepare 来实现的,由于主线程的 Looper 比较特殊,所以 Looper 提供了一个 getMainLopper 的方法获取主线程的 Looper。

② Looper 的退出。****Looper 提供了两个方法:quit 方法和 quitSafely 方法。

两者区别:quite 直接退出 Looper。

而 quitSafely 只是设定一个退出标记,先把消息队列中的消息处理完之后再退出

(4)Looper.loop() 方法实现原理

loop 是一个死循环,唯一能跳出循环的方法就是 MessageQueue 的 next 方法返回了 null。当 Looper 的 quit 方法被调用时,MessageQueue 的 quit 方法或者 quitSafely 方法就会通知消息队列退出,当消息队列被标记为退出状态时,next 就会返回一个 null。Looper 是必须退出的,否则 loop 会永远循环下去。

loop 方法会调用 MessageQueue 的 next 方法获取消息,如果 MessageQueue 没有消息,next 就会处于阻塞状态,loop 方法也会处于阻塞状态。

详解 Handler 的工作原理

Handler 的主要工作就是发送和接收消息。消息的发送可以通过 post 一系列方法以及 send 的一系列方法来实现,post 的一系列方法最终都是通过 send 一系列方法来实现的。

代码实现:

 1public class HandlerActivity extends Activity { 2 3    @Override 4    protected void onCreate(@Nullable Bundle savedInstanceState) { 5        super.onCreate(savedInstanceState); 6        setContentView(R.layout.activity_main); 7 8        //开启线程 9        handler();10    }11    //主线程12    Handler handler = new Handler(){1314        @Override15        public void handleMessage(Message msg) {16            super.handleMessage(msg);17            switch (msg.what) {18                case 1:19                    // 获取Message里面的复杂数据20                    Bundle date = new Bundle();21                    date = msg.getData();22                    String name = date.getString("name");23                    int age = date.getInt("age");24                    String sex = date.getString("sex");25                    //这里是主线程,可进行对UI的更新26                    textView.setText(name)27            }28        }29    };3031    //子线程32    public void handler(){33        new Thread(new Runnable() {34            @Override35            public void run() {36                Message message = new Message();37                message.what = 1;3839                // Message对象保存的数据是Bundle类型的40                Bundle data = new Bundle();41                data.putString("name", "李文志");42                data.putInt("age", 18);43                data.putString("sex", "男");44                // 把数据保存到Message对象中45                message.setData(data);46                // 使用Handler对象发送消息47                handler.sendMessage(message);48            }49        }).start();50    }51}

发送消息

通过对源码分析,Handler 发送消息的过程仅仅是向消息队列中插入一条信息,MessageQueue 的 next 方法就会返回这条信息给 Looper ,Looper 接收到消息之后就立即处理,由 Looper 交给 Handler 去处理消息,Handler 的 dispatchMessage 方法就会被调用,这时候 Handler 就进入了消息处理阶段。

消息处理

Android 高级进阶之深入剖析消息机制

深入 dispatchMessage 的源代码进行分析,Handler 处理消息如下:

① 首先检查 Message 的 callback 是否为 null, 不为 null 就通过 handlerCallback 来处理消息。(Message的 callback 是一个 Runnable d 对象,实际上就是 post 方法所递的 Runnable 参数)

② 其次检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handlerMessage 方法来处理消息。Callback 是个接口。

③ 我们通过 Callback 可以采用如下的方式来创建 Handle 对象。

1Handler handler = new Handler(callback);

这样创建的意义就是创建一个实例但是并不需要派生 Handler 的子类。

④ 但是,在我们的日常开发中,经常派生一个 Handler 的子类并重写其 handleMessage 方法来处理具体的消息,如果不想创建派生子类,就可以通过 Callback 来实现。

主线程的消息循环

Android 的主线程就是 ActivityThread,主线程的入口方法为 main ,在 main 方法中系统会通过 Looper.prepareMainLooper();来创建主线程的 Looper 以及 MessageQueue ,并通过 Looper.loop() 来开启主线程的消息循环。

主线程的消息循环开始了以后,ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H 。ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActvityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 向 H 发送消息,H 收到消息会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

推荐阅读

**Android 高级进阶之深入剖析四大启动模式
**

Android 高级进阶之深入剖析生命周期

一个不甘平凡的码农

Not an ordinary code farmer

Android 高级进阶之深入剖析消息机制

Android 高级进阶之深入剖析消息机制

职场丨发现丨习惯丨认知

让学习成为习惯,让编程拥有温度

本文分享自微信公众号 - 小鹿动画学编程(IT_Animation)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这