Android 开发艺术探索笔记之十 -- Android 的消息机制

学习内容

  • Android 的消息机制
  • Handler 即其底层支撑

原文开篇部分:

  1. 从开发角度来说,Handler 是 Android 消息机制的上层接口,通过它可以将任务切换到 Handler 所在的线程中执行。
  2. 更新 UI 仅仅是 Handler 的一个特殊的使用场景。本质不是专门用来更新 UI,只是常被用来这么做而已。
  3. Andorid 的消息机制主要指 Handler 的运行机制,Handler 的运行底层的 MessageQueueLooper 的支撑。
    1. MessageQueue(消息队列):采用单链表的数据结构存储消息列表,以队列的形式对外提供插入和删除的工作。只负责存储,不管处理。
    2. Looper 负责处理消息:以无限循环的形式查找是否有新消息,有则处理,无则等待。
    3. ThreadLocal 可以在把不同的线程中互不干扰的存储提供数据,通过它来获取每个线程中的 Looper
  4. 线程默认没有 Looper,因此如果需要使用 Handler 必须先为线程创建 Looper。UI 线程(ActivityThread)创建时会初始化 Looper,因此默认可以在主线程中使用 Handler。

Android 的消息机制概述

1.Handler 的作用:

  • 将一个任务切换到某个制定的线程中执行。

2.为什么提供上述 Handler 的功能?

  • Android 规定访问 UI 只能在主线程中执行,如果在子线程中访问 UI,那么程序会抛出异常。
  • 以上限制,源码中 ViewRootImpl 对 UI 操作进行验证,通过 chearThread 方法实现。
  • 同时由于 Android 的限制,导致必须在主线程中访问 UI,但是如果在主线程中进行耗时操作,可能会导致 ANR,因此。系统提供 Handler,主要原因就是为了解决在子线程中无法访问 UI 的矛盾。(子线程进行耗时操作,通过 Handler 访问 UI)

3. Android 为什么不允许子线程访问 UI?

  • 核心点:Andorid 的 UI 控件并非线程安全。
  • 通常方法及其问题:
    • 对 UI 控件的访问加入*上锁机制
    • 问题:使 UI 访问的逻辑变得复杂;锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。
  • 解决方案:
    • 采用单线程模型来处理 UI 操作
    • 通过 Handler 切换 UI 访问的执行线程即可

4.Handler 的工作原理:

  • Handler 创建时会采用当前线程的 Looper 来构建内部的消息循环系统,当前线程没有 Looper 会报错
  • 创建完毕后,通过 Handler 的 post 方法将 Runnable 投递到 Handler 内部的 Looper 中去处理;或者通过 Handler 的 send 方法发送消息,该消息也会在 Looper 中处理。实际 post 最后也会调用 send 方法。
  • send 方法的工作过程:调用 send 方法后,它会调用 MessageQueue 的 enqueueMessage 方法将该消息放入消息队列中,然后 Looper 发现新消息后,会处理这个消息,最终消息中的 Runnable 或者 Handler 的 handleMessage 方法就会被调用。
    • 注意:Looper 运行在创建 Handler 所在的线程中,这样一来 Handler 中的业务逻辑就被切换到创建 Handler 所在的线程中去了。

Android 的消息机制分析

ThreadLocal 的工作原理

1.基本定义

  • ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只有在制定线程中可以获取到存储的数据,而其他线程则无法获取到数据。
  • why ?(为什么通过 ThreadLocal 可以在不同的线程中维护一套数据的副本并且彼此互不打扰?)
    • (简单说明) 不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。显然,不同线程中的数组是不同的,这也就是原因所在。

2.使用场景

  1. 存储以线程为作用于并且不同县城具有不同的数据副本的数据
    1. 如:通过 ThreadLocal 存储不同线程中的 Looper,供 Handler 获取当前线程的 Looper
  2. 复杂逻辑下的对象传递,比如监听器的传递。
    1. 复杂逻辑:比如 函数调用栈比较深以及代码入口的多样性。
    2. 采用 ThreadLoacl 让监听器作为线程内的全局对象存在,而线程内部只要通过 get 方法就可以获取到监听器。

3.原理

  1. ThreadLocal 是一个泛型类,定义为 public class ThreadLocal\,核心在于 ThreadLocal 的 get 和 set 方法。

  2. set 方法:

    ​ (lz这里查看了一下现版本的源码,发现和书中有些许差异)

    public void set(T value) {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);
           if (map != null)
               map.set(this, value);
           else
               createMap(t, value);
       }

    ​ 虽说和书中源码有出入,但是大体思想是一致的:通过一个结构 ThreadLocalMap 存储线程的 ThreadLocal 数据。

    ​ ThreadLocalMap 部分代码如下:

    static class ThreadLocalMap {
           static class Entry extends WeakReference<ThreadLocal<?>> {
               /** The value associated with this ThreadLocal. */
               Object value;
    
               Entry(ThreadLocal<?> k, Object v) {
                   super(k);
                   value = v;
               }
           }
    
       private Entry[] table;
    
       //...

    ​ 内部的这个 private Entry[] table 数组即用来存储 ThreadLocal 的值。

    ​ 回到上面 ThreadLocal 的 set 方法,如果ThreadLocalMap 为空,则创建该线程的 ThreadLocalMap 对象,并将 ThreadLocal 的值存储。如果非空,则直接通过 ThreadLocalMap.set 方法存储ThreadLocal 的值,ThreadLocalMap.set 的源码如下:

    private void set(ThreadLocal<?> key, Object value) {
    
               Entry[] tab = table;
               int len = tab.length;
               int i = key.threadLocalHashCode & (len-1);
    
               for (Entry e = tab[i];
                    e != null;
                    e = tab[i = nextIndex(i, len)]) {
                   ThreadLocal<?> k = e.get();
    
                   if (k == key) {
                       e.value = value;
                       return;
                   }
    
                   if (k == null) {
                       replaceStaleEntry(key, value, i);
                       return;
                   }
               }
    
               tab[i] = new Entry(key, value);
               int sz = ++size;
               if (!cleanSomeSlots(i, sz) && sz >= threshold)
                   rehash();
           }

    ​ ThreadLocal 的值直接存储在 Entry.key 匹配的 Entry.value 中。(和书中有所差异,并非凭借索引位置)

  3. set 方法介绍完,下面介绍 get 方法:

    public T get() {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);
           if (map != null) {
               ThreadLocalMap.Entry e = map.getEntry(this);
               if (e != null) {
                   @SuppressWarnings("unchecked")
                   T result = (T)e.value;
                   return result;
               }
           }
           return setInitialValue();
       }

    ​ get 方法实际就获取 ThreadLocalMap 对象,如果非空,则调用 ThreadLocalMap.getEntry 方法,得到目标 Entry 对象,该 Entry 对象的 value 字段即为 ThreadLocal 值。如果 ThreadLocalMap 为空,那么返回初始值,该初始值可通过重写 initialValue 方法更改。

    ​ ThreadLocalMap.getEntry 方法如下:

    private Entry getEntry(ThreadLocal<?> key) {
               int i = key.threadLocalHashCode & (table.length - 1);
               Entry e = table[i];
               if (e != null && e.get() == key)
                   return e;
               else
                   return getEntryAfterMiss(key, i, e);
           }

    ​ 可以看到,逻辑很清晰,取出 table 数组,并找出 ThreadLocal 的引用对象所在的 Entry ,返回之。

  4. 小结

    1. 操作对象是当前线程的 ThreadLocalMap 内的 private Entry[] table 数组,读写仅限于各自线程的内部,
    2. 因此多个线程可以互不干扰的存储和修改数据。

消息队列的工作原理

1.基本介绍

  • 消息队列在 Android 中指的是 MessageQueue,内部通过一个单链表的数据结构来维护消息列表(方便插入和删除)

2. 原理

  1. enqueueMessage 方法:向消息队列中插入一条消息。
  2. next 方法:无限循环的方法。从消息队列中取出一条消息并将其从消息队列中移除。如果消息队列中没有消息,那么 next 方法会一直阻塞。

Loop 的工作原理

1.基本介绍

  • Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是它会不断的从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就会一直阻塞在那里。
  • Handler 的工作需要 Looper,没有 Looper 的线程会报错

2.原理

  1. 构造方法:创建一个 MessageQueue,然后保存当前线程的对象。
  2. 通过 Looper.prepare() 为当前线程创建一个 Looper,然后就可以创建 Handler 的对象了,接着通过 Looper.loop() 方法来开启消息循环。
  3. 除 prepare 外,还有一个 prepareMainLooper 方法,主要是给主线程 ActivityThread 创建 Looper 使用的,本质也是通过 prepare 方法实现的。对应的,系统提供 getMainLooper 方法来在任意位置获取主线程的 Looper。
  4. Looper 终止:
    1. quit 方法:直接退出 Looper
    2. quitSafely 方法:设定退出标记,当消息队列中的已有消息处理完毕后安全地退出。
    3. Looper 退出后,通过 Handler 发送的消息会失败,此时 Handler 的 send 方法会返回 false。子线程中,如果手动创建了 Looper,那么所有事情做完后,应当调用 quit 方法来终止消息循环,否则这个子线程会一直处于等待的状态,而如果退出 Looper 以后,这个县城就会立刻终止,因此 建议不需要的时候终止 Looper
  5. Looper.loop 方法
    1. 调用了 loop 方法后,消息循环系统才会 真正的起作用。
    2. 工作过程:
      1. loop 方法是一个死循环,当且仅当 MessageQueue.next 方法返回了 null 时,跳出循环。当 Looper 的 quit 方法被调用时,Looper 就会调用 MessageQueue 的 quit 或者 quitSafely 方法来通知消息队列退出,此时,next 的方法就会返回 null。
      2. loop 方法会调用 MessageQueue 的 next 方法来获取新消息,当没有信息时,next 会阻塞,这也导致 loop 方法一直阻塞。
      3. 当 next 方法返回新消息时,Looper 就会处理这条新消息:msg.target.dispatchMessage(msg),这里的 msg.target 是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage 方法来处理了。
      4. Handler 的 dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就将代码逻辑切换到制定的线程中了。

Handler 的工作原理

1.原理

Handler 的工作包含消息的发送和接收过程。

  1. 消息的发送:

    1. 通过 post 的一系列方法和 send 的一系列方法来实现,post 相关方法最终是通过 send 相关方法来实现的。
    2. Handler 发送消息只是向消息队列中插入一条消息,MessageQueue 的 next 方法就会返回这条信息给 Looper,Looper 收到消息后就会开始处理这条消息,最终消息由 Looper 交由 Looper 处理,即 Handler 的 dispatchMessage 方法会被调用,此时进入处理消息的阶段。
  2. 消息的处理

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

    流程如下:

    1. 首先,检查 Message 的 callback 是否为 null,不为null 就通过 handlerCallback 来处理消息。Message 的 callback 是一个 Runnable 对象,实际上就是 Handler 的 post 方法所传递的 Runnable 参数。

      private static void handleCallback(Message message) {
            message.callback.run();
        }
    2. 其次,检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handleMessage 方法来处理消息。

      public interface Callback {
            /**
             * @param msg A {@link android.os.Message Message} object
             * @return True if no further handling is desired
             */
            public boolean handleMessage(Message msg);
        }

      通过 Callback 可以如下方式创建 Handler 对象:Handler handler = new Handler(callback)。这样做的意义在于提供另外一种无需派生 Handler 子类并重写其 handlerMessage 方法处理具体消息的 Handler 的使用方式。

    3. 最后,调用 Handler 的 handlerMessage 方法来处理消息。只是一个空方法,需要我们派生 Handler 子类时,重写该方法。

      public void handleMessage(Message msg) {
        }
  3. 补充:

    1. Handler 还有一个特殊的构造方法,通过一个特定的 Looper 来构造 Handler

      public Handler(Looper looper) {
            this(looper, null, false);
        }
      
      public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

      原文中说“通过这个构造方法可以实现一些特殊的功能”,然而因为我接触事件尚短,不清楚具体能做些什么。。。

    2. 默认构造方法中对 Looper 对象进行判断,如果为空的话,那么就会报这个常见的异常”Can’t create handler inside thread that has not called Looper.prepare()”。

      public Handler() {
            this(null, false);
        }
      
      
      public Handler(Callback callback, boolean async) {
      
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

主线程的消息循环

消息循环模型

  1. Android 的主线程 ActivityThread 的入口方法 main 中,会通过 Looper.prepareMainLooper 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop 方法来开启主线程的消息循环。
  2. 主线程的消息循环开始之后,ActivityThread 需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H,内部定义了一组消息模型,主要包含四大组件的启动和停止等过程。
  3. ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中执行。
  4. 以上即为主线程的消息循环模型。

猜你喜欢

转载自blog.csdn.net/whdalive/article/details/81174218