android消息机制《Android开发艺术探索》笔记

异步消息处理机制

异步消息处理机制概述

1.作用:跨线程间消息传递。一般用于开启子线程执行耗时操作后,需要在执行结束更新UI线程。主线程的UI控件非线程安全,因此android系统不允许直接在子线程更新UI。

2.四元素
异步消息处理机制中主要包括Handler、Looper、MessageQueue、Message。

Handler:消息的处理者与发送者。通过sendmessage可以发送消息;通过handlemessage进行处理信息。

MessageQueue:信息队列,内部其实是一个单链表只负责存储消息,不负责发送消息。

Looper:消息泵,通过loop方法持续的从消息队列取出消息,并发送至消息对应的Handler。

Message:消息。用来存储一定的消息,并在线程中传递。具有msg.what字段,以此区分不同Message

3.关系:

1.一个线程只可以有一个Looper,因为Looper类中有一个静态变量ThreadLocal,线程每创建一个新looper时,
都会将这个looper set到ThreaLocal中,ThreadLocal可以通过线程来找到对应looper。
如果一个线程有多个looper就无法找到对应的拿一个Looper了。

2.一个线程可以有多个Handler

3.一个Looper中只有一个MessageQueue

4.Handler可以根据线程找到对应的Looper。

5.MessageQueue中存放多个Message,这些Message可以来自多个Handler。

注意:Handler可以在任意线程发送消息,这些消息会发送到Handler所关联的MessageQueue。Handler在处理时是在它关联的Looper线程中处理消息的

举个例子:我们首先创建一个Handler对象handler1,其关联的Looper就是主线程的looper1,我们在子线程使用handler1发送信息,信息会发送到looper1的messageQueue中。looper1将信息发送给handler1,handler1在主线程执行UI操作
在这里插入图片描述

异步消息处理机制的工作流程

1.当Handler对象使用sendMessage发送message后,会调用handler所绑定的looper实例中的MessageQueue的enqueueMessage方法,将message添加进MessageQueue。

2.Looper的loop方法会持续调用MessageQueue的next方法去取出消息。取出消息后,会调用msg对应的Handler的dispatchMessage方法。

3.在dispatchMessage方法中,会调用handlemessage方法,进行UI操作。

在这里插入图片描述

异步消息处理机制的具体原理

1.ThreadLocal工作原理

当某些数据以线程为作用域,且不同线程具有不同的数据副本时,我们使用ThreadLocal。

ThreadLocal和信息机制的关系在于,Looper类中有一个静态变量,这个静态变量的类型就是ThreadLocal类。当我们为线程创建Looper时,其实调用了threadLocal的set方法。Handler可以通过threadlocal对象找到当前线程与之对应的Looper。

ThreadLocal的原理是为每个线程都创建了一个数组来存储值。当ThreadLocal使用get函数获取当前线程的值的时候,首先会根据当前线程找到对应的存储数组,并通过ThreadLocal的下标在数组中找到存储的值。

ThreadLocal主要函数有set与get,首先来看set函数。

set函数

可以看到在set函数中,首先通过currentThread()方法得到当前线程。Thread内部有一个Values类专门用于存储线程中的ThreadLocal值。接下来通过values(currentThread)获取当前线程对应的Values对象。之后判断values对象是否为null,如果为null就初始化,否则就调用put函数去设置当前ThreadLocal的值。

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

可以看到在put方法中,会将ThreadLocal的值放置于table的refrence+1下标处。

       void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
				...
            }
        }
get函数

get函数的逻辑比较清晰,也是首先获取当前线程,并找到当前线程对应的Values对象,进而从table数组中取出reference+1位置的结果。

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }

从ThreadLocal的set和get方法可以看出,他们所操作的对象都是当前线程的localValues对象和table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读写操作仅限于各自内部,这就是为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据

2.Lopper工作原理

主线程中可以不创建Looper,但是在子线程中需要手动创建Looper,否则直接在子线程创建的Handler找不到匹配的Looper会报错。

主线程不需要创建Looper是因为已经自动创建了。

//主线程中不需要自己创建Looper
public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
        ......
        Looper.loop();//开启消息轮询
        ......
    }

而在子线程需要调用Looper类的静态方法prepare方法与loop方法。

//子线程中需要自己创建一个Looper
new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//为子线程创建Looper               
                Looper.loop(); //开启消息轮询
            }
        }).start();

prepare方法中我们创建新的Looper,并将Looper存储至ThreadLocal中,以便Handler可以通过ThreadLocal找到当前线程对应的Looper。可以从下述代码看到Looper类有一个全局变量ThreadLocal,并在prepare方法中执行sThreadLocal.set(new Looper())操作。同时在Looper的构造函数中可以看到会创建一个消息队列MessageQueue。

public class Looper {
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    // 。。。其他属性

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

loop方法是一个阻塞的方法,会持续的访问MessageQueue的next方法,直到next方法返回message,之后会将调用message对应Handler(msg.target)的dispatchmessage方法,将message发送给Handler去处理。这里当消息队列被标记为退出状态的时候,他的next方法就会返回null,也就是说,Looper必须退出,否则loop方法就会无限循环下去。

public static void loop() {
        ......
        for (;;) {//死循环
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
         ......   
         try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......   
    }

2.消息队列MessageQueue的工作原理

MessageQueue主要有两个核心函数enqueueMessage方法与next方法,分别对应着读取与插入操作。MessageQueue的内部是一个单链表实现的。

enqueueMessage会向链表尾部中插入一个元素,而next函数会从链表头部移出一个元素next方法是一个阻塞方法,当消息队列中没有消息就会一直阻塞在这里,当有消息后,会将消息移出链表

3.Handler的工作原理

Handler负责对消息的发送与接收,Handler有两种创建方式。不过不管是post还是send方式,最终调用的都是sendMessage方法发送广播。

//第一种:send方式的Handler创建
Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //如UI操作
                
            }
        };

//第二种:post方式的Handler创建
Handler handler = new Handler();

sendMessage方法会调用Handler对应的MessageQueue的enqueueMessage来将消息添加至消息队列。此时Looper会通过next方法获取到这个消息,并调用消息对应Handler的dispatchMessage方法。

Handler发送Message的代码流程:Handler.sendMessage()->Handler.sendMessageDelayed()->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage()

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

dispatchMessage中,首先,他会检查Message的callback是否为null,不为null就通过handlerCallback来处理消息,Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递Runnable参数,handlerCallback的逻辑也很简单。最终都会调用handleMessage方法去处理message

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

异步消息处理机制的使用

开启子线程

开启子线程首先要new一个Thread,并在其内部实现一个Runnable接口,重写其run函数。一般在android中我们开启子线程是用子线程来完成耗时操作,比如网络请求。

new Thread(new Runnable() {
          @Override
          public void run() {
          //耗时操作的逻辑
          }
}).start();

异步消息处理机制

在android中只要在主线程才可以进行UI操作,但在子线程中,我们可能在获取网络数据结束后要更新一些控件的信息,因此Android提供了异步消息处理机制,主要包括Handler(信息的发送者和接收者)Message(信息的载体,可以携带少量信息在不同线程交换)MessageQueue(信息队列),Looper

代码流程

首先在子线程中创建一个Message对象,并为它的what字段赋值,赋值后通过sendMessage(message)发送消息。之后在主线程中,创建Handler对象,并实现其内部的handleMessage(Message msg)方法,并根据msg.what去匹配接收到的是哪个Message,并最终更新UI。
在这里插入图片描述
图片来源

一个实例。

 public static final int UPDATE_TEXT =1;
 /*创建Handler实例并重写handleMessage方法*/
  private Handler handler=new Handler(){
        public  void  handleMessage(Message msg){
            switch (msg.what){
                case UPDATE_TEXT:
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };
/*开启的子线程*/
new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message=new Message();
                        message.what=UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();


总结(复习必看)

1.消息机制是android用来实现线程间通信的,最常用的场景就是让子线程执行一个耗时操作,执行后,需要更新UI控件,这时就需要用消息机制。而UI控件是线程不安全的,所以不能让UI控件直接在子线程更新。

2.消息机制是指Handler的工作模式。其中包含四个部分,Handler:消息的发出者、执行者;MessageQueue:消息队列,用来存储信息;Looper:消息崩,用来取出MessageQueue的信息并发送给消息对应的Handler处理;Message:消息的载体,具有Message.what字段来匹配Message。

3.四个部分的关系

一个线程只能有一个Looper,但可以有多个Handler;MessageQueue中可以存储多个Message,这些Message可以来自不同的Handler;Looper中只有一个MessageQueue;Handler可以在任意线程发送消息至Handler所绑定的MessageQueue,但Handler只会在所绑定的Looper所在线程对信息进行处理。

4.消息机制工作流程

1)Handler会通过sendMessage方法发出消息并调用所绑定的MessageQueue的enqueueMessage方法将消息放入消息队列MessageQueue;
2)Looper的loop方法会持续的调用MessagQueue的next方法,此时next方法会返回message,之后调用message所对应的Handler的dispatchMessage方法交由Handler处理,
3)Handler会调用handleMessage方法进行具体操作。

在这里插入图片描述
5.ThreadLocal在信息机制的作用及原理

Looper类中存在静态的ThreadLocal对象,并在每次为线程创建Looper对象时,都会将Looper对象存入ThreadLocal对象中。ThreadLocal用于帮助Handler找到当前线程对应的Looper,进而找到其MessageQueue,并调用其enqueueMessage方法。

ThreadLocal类为每个线程创建了一个数组,当使用set函数时,会根据当前线程找到对应的数组,并通过ThreadLocal对象将其存入数组中的特定下标reference+1处。而get函数时同理,也会根据当前线程找到存储的对应数组,并根据ThreadLocal对象找到数组的对应下标reference+1,并将数值取出。ThreadLocal在不同线程下变更的是不同数组,所以ThreadLocal可以在多个线程中互不干扰的存储和修改数据。

6.Looper的工作原理与使用

想在子线程创建Handler(此Handler与子线程绑定)并使用,需要首先创建Looper,否则会报错。Looper创建包括两个静态方法:Looper.prepare方法与Looper.loop方法

prepare方法中,会为当前线程创建一个新的Looper实例,在Looper构造方法中还会创建MessageQueue。

loop方法中,会持续地调用MessageQueue的next方法,直至next方法返回了消息,此方法是个阻塞方法。但当消息队列被标记位退出状态时,可以由MessageQueue的next方法返回一个null,来停止这个持续的死循环。

7.MessageQueue的工作原理

MessageQueue内部其实是个单链表,主要包含两个方法:enqueMessagenext方法。enqueueMessage方法会在Handler发送消息后调用并将消息插入链表;而**next方法是一个阻塞方法,会持续的去寻找链表中是否有元素,并删除元素。**

8.Handler的工作原理
Handler有两种创建方式,一种是post一种是send,send方式比较常用,两种方式最终都会调用sendMessage方法将消息传送。之后会调用当前handler所匹配的MessageQueue的enqueueMessage方法将消息添加至链表中。

当Looper调用消息队列的next函数返回msg后,会调用msg匹配Handler的dispatchMessage方法交由Handler处理,最终Handler会调用handlemessage进行实际操作。

9.消息机制的具体使用实例

首先在主线程创建一个Handler实例,并重写其handleMessage方法。开启一个子线程,并在子线程耗时操作结束后使用Handler实例调用sendMessage发送消息。
在这里插入图片描述
10.Message的创建方式:

后两种比较好,后两种都是从Message池中返回一个Message实例,可以避免Message的重复创建。

1.Message = new Message();
2.Message = Message.obtain();
3.Message = handler1.obtainMessage();

11.使用Hanlder的postDelay()后消息队列会发生什么变化?

postDelay发送的消息并不是延迟一会在发送,而是 发送到MessageQueue后,直接阻塞线程。与MessageQueue的队首元素根据触发时间比较,始终让触发时间短的在队首,让触发时间长的在队尾。此时如果队首就是delay的消息,就会将线程阻塞delay的时间,之后在执行该Message。

参考链接

本文中的图出自:
android的消息处理机制(图+源码分析)——Looper,Handler,Message
要点提炼|开发艺术之消息机制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章