Android消息机制

1.消息机制存在的原因

消息机制主要讲的就是Handler的运行机制,解决子线程无法操作主线程UI的问题

2.出现的问题

当在其他线程中操作主线程ui的时候,则会抛出异常

 

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

 

Android规定只有主线程允许修改Activity中的UI组件,而在其他线程中操作UI组件,会导致UI组件操作的不安全,为了保证Android的UI操作是线程安全的,Android提供了Handler消息机制。

 

为什么系统不允许在子线程中访问UI?

 

因为Android的UI控件不是线程安全的,如果在多线程中访问UI控件会导致不可预期的状态,为什么不对UI控件访问加锁呢?缺点有两个:首先加锁会让UI控件的访问的逻辑变得复杂。其次,锁机制会降低UI的访问效率。又因为Android主线程不可以执行耗时操作,否则会产生ANR。所以,Android消息机制是为了解决在子线程无法访问UI的问题。

Android消息机制涉及到的概念

 

1.主线程(UI线程)

1.定义:当程序第一次启动时,Android同时会启动一条主线程(MainThread)

2.作用:主线程主要负责与UI相关的事件

2.Message(消息)

1.定义:Handler接收和处理的消息对象(Bean对象)

2.作用:通信时相关信息的存放和传递

3.ThreadLocal

1.定义:线程内部的数据存储类

2.作用:负责获取和存储本线程内的Looper

4.MessageQueue

1.定义:采用单链表的数据结构来存储消息列表

2.作用:用来存放通过Handler发过来的Message,按照先进先出原则

5.Handler

1.定义:Message的主要处理者

2.作用:负责发送Message到消息队列,处理Looper分派过来的Message

6.Looper

1.定义:扮演MessageQueue 和 Handler 之间的桥梁角色

2.作用:

1.消息循环:循环取出MessageQueue的Message

2.消息派发:将取出的Message交付给Handler

三者关系

Android消息机制

 

Android消息机制

上图转载自https://blog.csdn.net/qq_30379689/article/details/53394061

看下相对具体的代码逻辑

1.Handler发送消息

 

看下Handler的构造函数

Android消息机制

在创建Handler的时候,同时会把Handler对应的Looper和MEssageQueue创建出来

Android消息机制

post消息会调用sendMessageDelayed方法

Android消息机制

sendMessageDelayed方法中会调用sendMessageAtTime方法

Android消息机制

sendMessageAtTime方法中调用了enqueueMessage方法,同时将Handler绑定的queue(消息队列)和 消息本身都传了进去。

Android消息机制

看下enqueueMessage都干了些什么

Android消息机制

最主要的就是调用了MessageQueue的enqueueMessage方法,把消息添加到了消息队列中

 

2.消息队列添加新消息

 

Handler调用了当前queue的enqueueMessage的方法。

Android消息机制

Android消息机制

enqueueMessage方法中做了些什么?

1.判断Message是否绑定了一个Hamdler对象,即msg.target对象

2.判断当前消息是否正在使用,

3.条件满足后,锁定MessageQueue对象,判断消息是否正在推出,如果正在退出则回收当前消息并返回false

4.设置当前消息为正在使用,配置其他信息,

5.MEssage p = mMessages; 当第一次执行到这里时,mMessages是空的,所以后面是执行if里面的代码,即把当前消息赋值到mMessages中,然后把当前消息的next置为空(when == 0 || when < p.when )说明上一个消息已经执行完毕或者是当前消息比上一个消息先执行,所以相当于第一次执行到这里

6.当消息队列里还有消息的时候就执行else里面的代码,首先循环找到最后一个message,通过判断message.next返回的值是否为空来判断是否是最后一个,以你为当是最后一个消息时,此值必定为null,那个时候就跳出循环。

7.因为p已经是null了,所以给当前要处理的message的next置为null,确定这是最后一个消息,同时,因为pre经过赋值已经是mMessages,即之前插入到消息队列的消息,只是还未处理而已

8.把当前传入的message放到mMessages消息后面,在else代码里都未对mMessages进行重新赋值,这是因为mMessages还没有读取,当执行next方法的时候才会重新对mMessages赋值。

 

3.Looper

Looper在消息机制中扮演消息循环的角色,它不停的从消息队列里查看是否有新消息,如果有,则立即处理新消息。没有则一直阻塞在那里。当需要为一个线程创建一个Looper时,需要调用Looper的两个静态方法就可以给这个线程创建一个Looper对象了,分别为Looper.prepare()和Looper.loop()

Android消息机制

prepare方法中执行了这些,向threadlocal中存储了looper对象

Android消息机制

而Looper的构造函数中创建了messagequeue,同时也将当前线程对象赋值到了Looper的成员变量。

 

这样Handler中是如何拿到Looper对象的呢

Android消息机制

myLooper中执行了什么,来看一下。

Android消息机制

也就是从Looper的静态成员变量threadlocal中获取到了创建Looper时存进去的looper对象。

 

prepare方法之后,再看下loop方法干了什么

Android消息机制

Android消息机制

1.获取到当前线程的Looper对象和绑定到Looper对象的这个消息队列

2.死循环

当消息队列没有消息时,则跳出循环,loop结束;当有消息时,则把这个消息通过Handler(msg.target是一个Handler对象)的dispatchMessage方法来处理这个消息,这也就到了Handler里面去执行了,就成功的将代码逻辑切换到指定的线程中去了。

 

如果当这个loop执行到一半的时候退出怎么办?Looper有一个quit的方法,此方法被调用时,Looper会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,并标识为退出状态,当消息队列被标记为退出状态时,next方法就会返回null,这样loop里面的死循环就跳出去了。

 

上述通过prepare方法和loop方法只是对普通线程来说的,对于主线程来说,由于主线程情况比较复杂,所以提供了prepareMainLooper来给ActivityThread创建Looper对象,但是其本质还是通过prepare来实现的。同时,也可以通过getMainLooper方法在其他任何地方获取到主线程的Looper对象。

 

注:

1.Looper退出后,通过Handler发送的消息会失败, 这个时候Handler的send方法会返回false

2.在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后,调用quit方法来终止消息循环,否则这个子线程会一直处于等待状态,而如果退出Looper之后,这个线程就会立即终止,因此建议不需要的时候终止Looper。

 

ThreadLocal

Android消息机制

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而且只有在指定的线程里才能获取到存储的数据,对于其他线程来说是获取不到的。

 

为何要聊ThreadLocal,对于Handler来说,要获取到当前线程的Looper,而Looper的作用域是当前线程,并且不同线程有不同的Looper,这个时候就可以很方便的通过ThreadLocal来对Looper进行存取。

Android消息机制

Looper中的myLooper方法,就是从threadLocal中把looper对象取出来。

Android消息机制

创建Looper的时候,同样也是把一个new 出来的Looper对象set到threadLocal中。

 

总结

Android消息机制流程:Handler向MessageQueue发送一条消息,MessageQueue通过next方法把消息传递给Looper,Looper接收到消息后开始处理,然后最终交给Handler处理。

 

面试题

请解释一下Android通信机制中Message,Handler,MessageQueue,Looper之间的关系

首先,是这个MessageQueen,MessageQueen是一个消息队列,它可以存储Handler发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和next()方法。这里提到的Message,其实就是一个Bean对象,里面的属性用来记录Message的各种信息。

然后,是这个Looper,Looper是一个循环器,它可以循环的取出MessageQueen中的Message,其内部提供了Looper的初始化和循环出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper会关联一个MessageQueen,而且将Looper存进一个ThreadLocal中,在loop()方法中,通过ThreadLocal取出Looper,使用MessageQueen的next()方法取出Message后,判断Message是否为空,如果是则Looper阻塞,如果不是,则通过dispatchMessage()方法分发该Message到Handler中,而Handler执行handlerMessage()方法,由于handlerMessage()方法是个空方法,这也是为什么需要在Handler中重写handlerMessage()方法的原因。这里要注意的是Looper只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的Looper。

最后,是这个Handler,Handler是Looper和MessageQueen的桥梁,Handler内部提供了发送Message的一系列方法,最终会通过MessageQueen的enqueueMessage()方法将Message存进MessageQueen中。我们平时可以直接在主线程中使用Handler,那是因为在应用程序启动时,在入口的main方法中已经默认为我们创建好了Looper。