【Android】Android的消息机制

 Android的消息机制主要是指Handler的运行机制,Handler的运行是需要底层的MessageQueue和Looper的支撑。在本消息机制中,主要有如下几个部分:

1、Message
 Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于不同的线程之间交换数据。可以使用what字段、arg1、arg2携带一些整型数据,使用obj字段携带一个Object对象。
2、Handler
 Handler就是处理者的意思,它主要用于发送和处理消息,发送消息一般在主线程构造一个Handler,在其他线程调用Handler的sendMessage()方法,此时主线程的MessageQueue中会插入一条message,然后被Looper使用。Handler在创建时会通过ThreadLocal来获取当前线程的Looper来构造消息循环系统。
3、MessageQueue
 MessageQueue是消息队列,每个线程中只会有一个MessageQueue对象,单链表维护,在插入和删除上有优势。主要用于存放所有通过Handler发送的消息,这部分消息会一直存在于消息队列中,等待被处理。在其next()中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
4、Looper
 Looper创建时会创建一个MessageQueue,调用loop()方法时消息循环开始(死循环),会不断调用MessageQueue的next()方法,当有消息就传递到Handler的handlerMessage()方法中处理,否则阻塞在messageQueue的next()中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也跟着退出。每个线程中也只会有一个Looper对象。

注意事项:
1、系统的主线程在ActtivityThread的main()为入口开启主线程,其中定义了内部类Activity.H定义了一系列消息类型,包括四大组件的启动和停止。
2、MessageQueue和Looper是一对一的关系,Handler和Looper是多对一的关系。
3、ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。由于线程默认是没有Looper的。如果需要使用Handler就必须为线程创建Looper,但是有一个特例,就是主线程ActivityThread,ActivityThread被创建时就会初始化Looper,因此主线程中可以直接使用Handler。

Android的消息机制概述

 由于Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。ViewRootImpl中的checkThread()方法对UI操作进行验证:

void checkThread(){
    if(mThread != Thread.currentThread()){
        throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
    }
}

 由于有必须在主线程访问UI的规定,但是在主线程中进行过多的耗时操作,会导致ANR。因此需要有一个部件在子线程中处理耗时的操作,处理完毕后再通知主线程更新UI。因此Handler的主要作用是将一个任务切换到某个指定的线程中去执行,同时Handler解决了在子线程中无法访问UI的矛盾。

 创建Handler后,重写hangleMessage()方法,然后通过Handler的post()方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send()方法,发送一个message,该消息同样会在Looper中处理。send()方法的工作过程:当send()方法被调用时,它会调用MessageQueue的enqueueMessage()方法,该方法将这个消息放入到消息队列中(post()方法也是通过send()方法实现)。而Looper则会尝试在MessageQueue中取出待处理的消息,然后Runnable或者handler的handleMessage()方法就会被调用。由于Looper是运行在创建Handler所在的线程中的,这样Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。
这里写图片描述
 Handler使用demo:https://github.com/zzhuazi/AndroidThreadTest.git

Android的消息机制分析

 1、ThreadLocal的工作原理

 ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取到数据,在其他线程中则无法获取数据。在Android源码中,Looper、ActivityThread以及AMS都用了ThreadLocal。对于应用场景来说,①一般是当数据是以线程为作用域而且不同的线程具有不同的数据副本时,就可以考虑采用ThreadLocal,比如ThreadLocal可以在不同的线程中获取不同的Looper。②另一个使用场景是复杂逻辑下的对象传递。比如监听器的传递,有些时候,一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听能够贯穿整个线程的执行,这时就需要采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只需要通过get()方法就可以获取到监听器。
 如果不考虑ThreadLocal,则可以想到有如下两种方法(都具有局限性):
 ①将监听器通过参数的形式在函数调用栈中传递
 ②将监听器作为静态变量供线程访问。
下面以一个简单的demo了解ThreadLocal的原理:
https://github.com/zzhuazi/ThreadLocal.git

private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBooleanThreadLocal.set(true);
        Log.d(TAG, "[Main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

        new Thread("Thread#1"){
            public void run(){
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "[1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();

        new Thread(){
            public void run(){
                Log.d(TAG, "[2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();
    }

demo运行结果:

08-05 11:41:46.480 1140-1140/com.ljh.threadlocal D/MainActivity: [Main]mBooleanThreadLocal=true
08-05 11:41:46.480 1140-1152/com.ljh.threadlocal D/MainActivity: [1]mBooleanThreadLocal=false
08-05 11:41:46.490 1140-1153/com.ljh.threadlocal D/MainActivity: [2]mBooleanThreadLocal=null

 不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。因此在线程2中,由于没有设置boolean的值,所以ThreadLocal中get出来的值就是null。

 ThreadLocal是一个泛型类,主要的两个方法是set和get方法,在该类中有一个专门存放线程数据的成员:ThreadLocal.Values localValues,在localValues内部又存在一个table数组,因此线程中的数据主要存储在该table数组中。以下是set方法的源码:

public void set(T value){
    Thread currentThread = Thread.currentThread();
    Values values = Values(currentThread);
    if(values == null){         //若数据为空,则先初始化数据
        values = initalizeValues(currentThread);
    }
    values.put(this, values);   //this指ThreadLocal,values指数据
}

 同样get方法从localValues中的table取出数据。

public T get(){
    Thread currentThread = Thread.currentThread();
    Values values = Values(currentThread);
    if(values != null){  //values不为空,则直接在table中取值
        Object[] table = values.table;
        int index = hash & values.mask;
        if(this.reference == table[index]){
        return (T) table[index + 1];   //由于set方法中数据的存储是在index + 1,因此取值也要index + 1
    }
    }else {    //values为null,初始化table
        values = initalizeValues(currentThread);
    }
    return (T) values.getAfterMiss(this);
}
 2、MessageQueue的工作原理

 消息队列主要包括两个操作:插入和读取。插入操作对应的是enqueueMessage()方法,读取操作对应的是next()方法,在读取的同时也会将该message,从队列中移除。MessageQueue是通过一个单链表的数据结构来维护消息列表。
 enqueueMessage()和next()方法都是通过死循环来实现。在next方法中,如果消息队列中没有消息,那么next方法就会阻塞,当有新消息时,next方法会返回这条消息并将其从单链表中移除。

 3、Looper的工作原理

 Looper会不停的从messageQueue中查找是否有新消息,如果有新消息就会立即处理,否则就会阻塞。在Looper的构造方法中,会创建一个MessageQueue对象,并保存当前线程的对象。

private Looper(boolead quitAllowed){
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

 在Looper类中,主要有如下方法:

方法 作用
prepare() 为当前线程创建Looper
loop() 开启消息循环
prepareMainLooper() 给主线程也就是ActivityThread创建Looper
getMainLooper() 在任何地方获取到主线程的Looper
quit() 直接退出Looper
quitSafely() 设定一个标记,然后把消息队列中所有已知的消息处理完毕后才安全地退出

 在子线程中,如果手动创建Looper,那么在所有事情完成后应该调用quit()方法终止消息循环,否则子线程一直处于等待状态,在Looper退出后,线程就会立即终止。
 当looper的quit或者quitSafely方法被调用时,Looper就会调用MessageQueue的quit方法来通知消息队列退出,当消息队列被标记为退出状态,next()方法就会返回null,那么Looper就必须退出(MessageQueue的next()方法返回Null是Looper退出死循环的唯一方式。)如果没有消息时,MessageQueue的next()是一个阻塞状态,Looper也会是一个阻塞状态,如果MessageQueue的next方法返回了新Message,则Looper就会调用msg.target(Handler对象)的.dispatchMessage(msg)方法对消息进行处理。需要留意的是Handler的dispatchMessage方法是在创建Handler时所用的Looper中执行,这样就将代码切换到指定的线程中执行。

 4、Handler的工作原理

 Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过send()或者post()方法实现。
 Handler发送消息的过程是向消息队列中插入了一条消息,随后MessageQueue的next方法将该message返回给Looper,Looper接收消息后进行处理,最后消息传递到Handler中的dispatchMessage方法进行处理。
 dispatchMessage源码如下:

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

 首先检查Message的callback是否为空,不为空则调用handleCallback来处理消息,Message的Callback其实是一个Runnable对象,也就是Handler的post方法所传递的Runnable参数。通过Callback可以用来创建一个Handler的实例但是不需要派生Handler的子类。

Runnable runnable = new Runnable() {
    @Override
    public void run() {

    }
};

Handler handler = new Handler();
handler.post(runnable);

 上述部分内容整理自《Android开发艺术探索》

猜你喜欢

转载自blog.csdn.net/J675620982/article/details/81412918