android开发艺术(八)之android的消息机制

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue采用单链表的数据结构存储消息列表;Looper以无限循环的形式去查找是否有新消息,如果有就处理消息,否则就一直等待。Looper中存在ThreadLocal,Handler创建时需要采用当前线程的Looper构建消息循环系统,ThreadLocal在不同的线程中互不干扰的存储并提供数据,然后获取Looper,线程默认没有Looper,需要使用主线程ActivityThread,它被创建时会初始化Looper。

1.android的消息机制概述

1.1 Handler作用实例

在线程进行的操作得到的数据想要通过UI呈现出来,但是UI操作是在主线程完成的,所以需要handler切换线程。

那么为什么不能再子线程中访问UI:

  1. UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。

不对UI控件的访问加上锁机制的原因:

  1. 加上锁机制会让UI访问的逻辑变得复杂
  2. 锁机制会降低UI访问的效率,因为会影响某些线程的执行

2.android的消息机制分析

2.1 ThreadLocal的工作原理

线程内部的数据存储类,通过它可以在指定的线程中存储数据,存储之后,只有在指定线程中可以获取到存储的数据
实例:
在主线程中设置ThreadLocal的值为true,然后分别创建两个新的线程,一个线程中设置为false,另一个不处理ThreadLocal的值,然后分别调用其get方法,获取ThreadLocal的不同值

2.2 ThreadLocal内部实现

ThreadLocal属于泛型,定义为public class ThreadLocal< T >
内部实现涉及到的两个重要方法:set和get
在这里插入图片描述
一个存储规则,那就是ThreadLocal的值在table 数组中的存储位置总是为ThreadLocalreference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组中的索引为index, 那么ThreadLocal 的值在table 数组中的索引就是index+1。 最终ThreadLocal的值将会被存储在table数组中: table[index + 1]= value.
在这里插入图片描述

2.3 消息队列的工作原理

消息队列在Android中指的是MessageQueue, MessageQueue 主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessasagenext,其中enqueuneMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。MessageQueue通过一个单链表的数据结构来维护消息列表。

enqueueMessage

分析源码咳得,它的主要操作其实就是单链表的插入操作

next

next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞。当有新消息到来时,next 方法会返回这条消息并将其从单链表中移除。

2.4 Looper的工作原理

构造实现:
在构造方法中创建一个MessageQueue消息队列,然后将当前线程的对象保存起来

方法分析

prepare( ):为当前线程创建一个Looper
prepareMainLooper( ):给主线程也就是ActivityThread创建Looper
getMainLooper( ):在任何地方获取到主线程的Looper

loop( ):开启消息循环
Looper 必须退出,否则loop方法就会无限循环下去:
loop方法退出唯一的方式是MessageQueue的next 方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit 或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next 方法会一直阻塞在那里, 这也导致loop方法一直阻塞在那里。
如果MessageQueue的next方法返回了新消息,Looper 就会处理这条消息:msg. target.dispatchMessage(msg),这里的msg. target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage 方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。

quit( ):直接退出Looper
quitSafely( ):设定一个退出标记,然后把消息队列中的已有消息处理完才会退出

在子线程中如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit 方法来终止消息循环,否则这个子线程就会一直处 于等待的状态,而如果退出Looper以后,这个线程就会立刻终止

2.5 Handler的工作原理

  1. 创建Handler(需要确保存在Looper,有以下两种方式):
    1.1 在当前线程创建Looper
    1.2 在一个有Looper的线程中创建Handler
  2. Handler内部与其协同操作
    2.1 Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理
    2.2 通过Handler的send方法发送一个消息,这个消息会在Looper中进行处理,post方法最终也是通过send方法来完成的
    3 handler工作过程(需要注意)

Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现, post的一系列方法最终是通过send的一系列方法来实现的。Handler 发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper, Looper收到消息后就开始处理,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
Handler处理消息的过程如下:
首先,检查Message的callback是否为null, 不为null 就通过handleCallback来处理消息。Message 的callback是一个 Runnable 对象,实际上就是Handler 的post 方法所传递的Runnable参数。
其次,检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息。Callback 是个接口。创建Handler最常见的方式就是派生一个Handler的子类并重写其handleMessage 方法来处理具体的消息,而Callback提供了另外一种使用Handler的方式,当不想派生子类时,就可以通过Callback来实现。
最后,调用Handler的handleMessage方法来处理消息。

public void dispatchMessage (Message msg) {
		if (msg.callback != null) {
			handleCallback (msg) ;
		} else {
			if (mCallback!= null) {
				if (mCallback. handleMessage (msg)) {
					return;
				}
		}
		handleMessage (msg) ;
	}
}

3.主线程的消息循环

Android的主线程就是ActivityThread, 主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。主线程的消息循环开始了以后,ActivityThread 还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。
ActivityThread通过ApplicationThreadAMS进行进程间通信, AMS以进程间通信的方式完成ActivityThread 的请求后会回调ApplicationThread 中的Binder 方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

4.问题

Q:ThreadLocal有什么作用?
●参考回答: ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
●底层数据结构:每个线程的Thread对象中都有一个ThreadLocalMap对象, 它存储了一 组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是 当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值对中找回对应的本地线程变量。

Q: 主线程中Looper的轮询死循环为何没有阻塞主线程?
●参考回答: Android是依靠事件驱动的,通过Loop.loop()不断进行消息循环, 可以说Activity的生命周期都是运行在Looper.loop()的控制之下,一旦退出消息循环, 应用也就退出了。而所谓的导致ANR多是因为某个事件在主线程中处理时间太耗时,因此只能说是对某个消息的处理阻塞了Looper.loop(),反之则不然。

Q: 使用Hanlder的postDealy()后消息队列会发生什么变化?
●参考回答: postdelay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间, 直到等待足够时间再唤醒执行该Message, 否则唤醒后直接执行。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值