Android消息机制

Posted by Cedar7 on March 7, 2019

Android消息机制

Android的消息机制主要是指Handler的运行机制。它的运行需要MessageQueueLooper的支持。
MessageQueue:消息队列(只是一个容器)。内部存储一些消息,实际上采用单链表的数据结构存储消息。
Looper:消息循环(处理消息)。它已无限循环的方式处理MessageQueue中的消息,有的话就处理消息,否则就一直等待。

1 概述

Handler的主要作用是讲一个任务切换到某个指定的线程中去执行。Android中为什么要提供这个功能呢?因为:Android规定访问UI只能在主线程中进行,否则就会抛出异常。所以我们一般都将耗时的操作(如访问网络请求数据)放在子线程中,待数据返回时,再切换到主线程

    //在子线程中
    new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        //在主线程中
                    }
                });

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

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

系统为什么不对UI控件的访问加上锁机制呢?

  1. 让UI访问的逻辑变得复杂。
  2. 降低UI访问的效率。

所以最简单、高效的方法就是采用单线程的模型处理UI操作。

2 分析

2.1 ThreadLocal的工作原理

它是线程内部的一个数据存储类,保证各个线程中存储的值不会错乱。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,如创建Handler的Looper。
ThreadLocal的另一个使用场景就是复杂逻辑下的对象传递,通过get/set方法就可以存储某一线程的数据。否则(1️⃣通过参数的形式进行传递2️⃣定义静态变量)进行对象传递。

    private ThreadLocal<Boolean> threadLocal = new ThreadLocal();
    threadLocal.set(true);
    Log.e("TAG", threadLocal.get())

    
    new Thread(new Runnable() {
        @Override
        public void run() {
            threadLocal.set(false); 
            Log.e("TAG", threadLocal.get())
        }
    }).start();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("TAG", threadLocal.get())  
        }
    }).start();

ture
false
null

在不同的线程中访问同一个ThreadLocal对象的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。

实际分析(内部实现)

ThreadLocal其实就是一个泛型类,我们主要分析它的get和set方法。

set()方法
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value)
    }
get()方法
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value)
    }

2.2 MessageQueue的工作原理

MessageQueue实际上是通过一个单链表的数据结构来维护消息列表。
boolean enqueueMessage(Message msg, long when):插入操作。
Message next():读取操作(伴随着删除操作)。它是一个无限循环的方法,如果队列中没有消息,它就一直阻塞,当有新消息来的时候,它就会返回这条消息并将其从单链表中删除

2.3 Looper的工作原理

它扮演着消息循环的角色,不停的从MessageQueue中查看是否有新消息,有消息就会立刻处理,否则就一直阻塞在那里。

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

在它的构造方法中会创建一个MessageQueue,然后将当前线程的对象保存起来

Looper.prepare()

为一个线程创建一个Looper,并通过Looper.loop()来开启消息循环。

Looper.prepareMainLooper()

这个方法主要是给主线程(ActivityThread)创建Looper使用的,其本质也是通过prepare()方法来实现的。

Looper.getMainLooper()

通过他可以在任何地方获取到主线程的Looper,以便于将程序切换到主线程。

quit()

直接退出Looper。

quitSafely()

设定一个退出标记,当消息队列中已有的消息处理完毕之后才安全的退出。这是Handler的send会返回false。在一个子线程中创建Looper后,当处理完消息队列中的消息后,应该调用quit(),否则子线程会一直处于等待状态,只有退出Looper后,线程才会终止。

Looper.loop()

它就是一个死循环,跳出循环的唯一方式就是MessageQueue的next()方法返回null(当Looper调用quit()时,next()就会返回null)。loop()回调用MessageQueue的next()方法,而next()是一个阻塞方法,所以也导致lop()阻塞。当next()返回消息时,Looper就会处理这条消息:
msg.target.dispatchfMessage(msg)
这里的msg.target就是发送这条消息的Handler,这样就将代码逻辑切换到指定的线程中去了。

2.4 Handler的工作原理

Handler的主要工作包括消息的发送接收。消息的发送可以通过postsend一系列方法来实现(post其实也是通过send实现的)。最后都会调用enqueueMessage(queue, msg, uptimeMillis)

Handler的发送消息只是向消息队列中插入了一条消息,MessageQueue的next方法就会将这条消息传给Looper,Looper再将消息传送给Handler处理(即Handler的dispatchMessage方法),这时就进入了消息处理阶段。

public void dispatchMessage(Message message) {
    if (msg.calllback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {    //如果返回true直接return结束方法,不再调用handler中的handleMessage方法
                return;
            }
        }
        handleMessage(msg);
    }
}
  1. 首先检查Message的callback是否为null,如果不为null就调用handleCallback(msg)来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法传递的Runnable参数。
public static void handleCallback(Message message) {
    message.callback.run();     //其实就是Runnable的run方法。
}
  1. 其次,再检查mCallback是否为null,不为null的话就调用mCallback的handleMessage来处理消息。mCallback是Handler内部的接口
/**
* Callback interface you can use when instantiating a Handler to avoid
having to implement your own subclass of Handler.
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
     */
public interface Callback {
    public boolean handleMessage(Message message);
}

在日常开发中,我们创建一个Handler通常是创建一个子类并重写起handeMessage方法,

private Handler smsHandler = new Handler(){
		public void handleMessage(Message msg) {
			
		};
	};

但是有了Callback就不需要了,就像下面这样

private Handler mHandler = new Handler(new Handler.Callback() {
 
		@Override
		public boolean handleMessage(Message msg) {
			return false;
		}
	});
  1. 最后调用Handler的handleMessage方法来处理消息。
/**
* Subclasses must implement this to receive messages.
为了接收消息,子类必须实现这个方法
*/
public void handleMessage(Message msg) {
}

3 主线程的消息循环

Android的主线程就是ActivityThread,它的入口方法及时main, main方法会通过Looper.prepareMainLooper(),来创建主线程的Looper及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

在消息循环开启后,还需要一个Hanler来和四大组件进行交互。这个Handler就是ActivityThread.H,通过它就可以进行各个组件与主线程的通信了。

相关面试题

Handler消息机制详解(29题)