Handler消息机制
代码参考为Android 8.0;
安卓系统设计要求UI的更新只能在主线程中进行更新,但是我们需要将耗时操作放在子线程中以免ANR的发生,就需要我们把子线程的数据传递到主线程中,Hanlder消息机制就是安卓系统实现该情景设计的。但是,Handler不仅仅能将子线程的数据传递给主线程,还能实现任意两个线程之间的数据传递。
Handler消息机制使用的类
类 | 描述 |
---|---|
Handler | 发送和接受消息 |
Message | 消息的载体 |
MessageQueue | 消息传输的通道 |
Looper | 维护通道 |
Handler类
消息辅助类,主要功能是向消息池发送各种消息事件(Handler.sendMessage())和处理响应消息事件(Handler.handleMessage())。
方法 | 描述 |
---|---|
obtainMessage() | |
obtainMessage(int) | |
obtainMessage(int,Object) | |
obtainMessage(int,int,int) | |
obtainMessage(int,int,int,Object) | |
post(Runnnable) | |
postAtTime(Runnable,long) | |
postAtTime(Runnable,Object,long) | |
postDelayed(Runnable,long) | |
postDelayed(Runnable,Object,long) | |
postAtFrontOfQueue(Runnable) | |
sendMessage(Message) | |
sendEmptyMessage(int) | |
sendEmptyMessageDelayed(int,long) | |
sendEmptyMessageAtTime(int,long) | |
sendMessageDelayed(Message,long) | |
sendMessageAtTime(int,long) | |
sendMessageAtFrontOfQueue(Message) | |
getLooper() |
1 . Handler的post(Runnable)与sendMessage()方法的区别?
2 . Handler的sendMessageDelayed()或者postDelayed()是如何实现的?
在向MessageQueue队列中插入Message时,会根据Message的执行时间排序,而消息的延时处理的核心实现是在获取Message的阶段。如果当前系统时间大于或等于Message.when属性,那么会返回Message给Looper.loop()方法,但是这个逻辑只能保证在when之前消息不会被处理,不能够保证一定在when时被处理。
Message类
需要传递的消息,可以传递数据。
变量 | 描述 |
---|---|
what | int型变量 |
arg1 | int型变量 |
arg2 | int型变量 |
obj | Object类型 |
MessageQueue
消息队列,但是它的内部实现并不是用的队列,实际上通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池投递消息(MessageQueue.enqueueMessage())和取走消息池的消息(MessageQueue.next())。
Looper
题外话:
启动一个Java程序的入口函数是main()方法,当main()函数执行完毕之后,此程序就会停止运行,也就是进程自动终止。但是当打开一个Activity之后,只要不按下返回键,Activity就会一直显示在屏幕上,也就是一个Activity所在线程会一直处于运行状态。是Looper内部维护一个无限循环,保证App进程持续进行的。不断循环执行,从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。
//App进程入口;
//在ActivityThread.java类中;
public static void main(String[] args) {
//---;
Looper.prepareMainLooper();
//---;
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper的作用有两点,第一是为调用该类中静态函数prepare()的线程创建一个消息队列;第二是提供静态函数loop(),使调用该函数的线程进行无限循环,并从消息队列中读取消息;
方法 | 描述 |
---|---|
prepare() | 调用prepare(boolean)方法 |
prepare(boolean) | 进行判断ThreadLocal |
prepareMainLooper() | |
getMainLooper() | 获取sMainLooper,也就是Looper变量 |
loop() |
Looper.java类中主要代码如下:
public final class Looper{
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;
final MessageQueue mQueue;
final Thread mThread;
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
//设置应用的主Looper,由安卓系统创建;
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//获取应用的主Looper;
public static Looper getMainLooper(){
synchronized(Looper.class){
return sMainLooper;
}
}
//将Looper开启循环进行消息队列的消息的传输;
public static void loop(){
final Looper me = myLooper();
if(me == null){
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for(;;){
···//开启Looper中的消息循环;
}
}
public static @Nullable Looper myLooper(){
return sThreadLocal.get();
}
}
prepare()方法在一个线程中只能被调用一次,Looper的构造方法在一个线程中也只能被调用一次,最终导致MessageQueue在一个线程中只会被初始化一次。
1 . Looper.loop()方法为什么不会阻塞主线程?
在MessageQueue类中的next()方法获取Message消息对象时,调用了nativePollOnce()方法,这个方法是一个native方法,当调用此native方法时,主线程会释放CPU资源进入休眠状态,直到下条消息到达或者有事务发生通过往pipe管道写端写入数据来唤醒主线程工作。
消息机制的架构
消息机制的运行流程:在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage()方法,向消息队列中添加消息。当通过Looper.loop()方法开启循环后,会不断从线程池中读取消息,即调用MessageQueue.next()方法,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage()方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage()方法,接收消息,处理消息。
MessageQueue、Handler、Looper三者之间的关系:每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
变量的常见作用域
- 函数内部的变量:其作用域是该函数,即每次调用该函数时,该变量都会重新回到初始值;
- 类内部的变量:其作用域是该类所产生的对象,即只要该对象没有被销毁,即对象内部的变量值一直保持;
- 类内部的静态变量:其作用域是整个进程,即只要在该进程中,则该变量的值就一直保持,无论使用该类构造过多少个对象,该变量只有一个赋值,并一直保持;
不同作用域的变量类型:
变量作用域类型 | 意义 |
---|---|
函数成员变量 | 仅在函数内部有效 |
类成员变量 | 仅在对象内部有效 |
静态变量 | 在本进程内的任何对象内保持一致 |
线程局部存储(TLS)变量 | 在本线程内的任何对象内保持一致 |
跨进程通信(IPC)变量 | 一般使用Binder进行定义,在所有进程中保持一致 |
总结
应用启动是从ActivityThread的main()方法开始的,先是执行了Looper.prepare()方法,该方法先是new 了一个Looper对象,在私有的构造方法中又创建了MessageQueue作为此Looper对象的成员变量。Looper对象通过ThreadLocal绑定MainThread中。
当创建Handler子类对象时,在构造方法中通过ThreadLocal获取绑定的Looper对象,并获取此Looper对象的成员变量MessageQueue作为该Handler对象的成员变量。
在子线程中调用上一步创建的Handler对象的sendMessage(msg)方法时,在该方法中将msg的target属性设置为自己本身,同时调用成员变量MessageQueue对象的enqueueMessage()方法将msg放入MessageQueue中。
主线程创建好以后,会执行Looper.loop()方法,该方法中获取与线程绑定的Looper对象,继而获取该Looper对象的成员变量MessageQueue对象,并开启一个会阻塞(不占用资源)的死循环,只要MessageQueue中有msg,就会获取该msg并执行msg.target.dispatchMessage(msg)方法,在此方法中会调用创建Handler子类对象时复写的handleMessage()方法。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
------------------last line for now-------------------