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-------------------