Android Handler消息机制

Android的消息机制概述

Android的消息机制主要是指Handler的运行机制,主要包括以下

Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;

MessageQueue:它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作;内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表

Handler:消息辅助类,Handler的主要作用是将一个任务切换到Handler所在的线程中去执行

Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

ThreadLocal: 可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper。

Android为什么要提供Handler消息机制(Android为什么需要提供在某个具
体的线程中执行任务这种功能呢)?

因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常

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

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

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

缺点有两个:
1.首先加上锁机制会让UI访问的逻辑变得复杂;
2.其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

Handler工作过程

Android Handler消息机制

ThreadLocal的工作原理

ThreadLocal:是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,
对于其他线程来说则无法获取到数据。

什么时候使用ThreadLocal?

1.当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
2.复杂逻辑下的对象传递,比如监听器的传递

ThreadLocal使用:

private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
mBooleanThreadLocal.set(true);
Log.d(TAG,"[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
new Thread("Thread#1") {
    @Override
    public void run() {
        mBooleanThreadLocal.set(false);
            Log.d(TAG,"[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
    };
}.start();
new Thread("Thread#2") {
    @Override
    public void run() {
        Log.d(TAG,"[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
    };
}.start();

结果:
D/TestActivity(8676): [Thread#main]mBooleanThreadLocal=true
D/TestActivity(8676): [Thread#1]mBooleanThreadLocal=false
D/TestActivity(8676): [Thread#2]mBooleanThreadLocal=null

说明每个线程访问的对应ThreadLocal中保存的值,UI线程中值为true,所以ThreadLocal中得到值为true,线程1中ThreadLocal的值设置false,所以打印的值为false,线程2中ThreadLocal没有赋值,所以为null。

为什么会这样?

不同线程访问同一个ThreadLocal的get方法
ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。
不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

ThreadLocal的set、get方法

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的数据:ThreadLocal.ThreadLocalMap Entry,因此获取当前线程的ThreadLocal数据就变得异常简单了。
如果map的值为null,创建ThreadLocalMap对象,初始化后再将ThreadLocal的值进行存储。

下面看一下ThreadLocal的值到底是如何在ThreadLocalMap中进行存储的

set()方法

private void set(ThreadLocal<?> key, Object value) {

        // We don't use a fast path as with get() because it is at
        // least as common to use set() to create new entries as
        // it is to replace existing ones, in which case, a fast
        // path would fail more often than not.

        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();
    }

ThreadLocalMap中set方法实际上是将ThreadLocal存储在类型为Entry的数组中,具体看上面代码。我们再看Entry对象

static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

是一个扩展自WeakReference的类,说明Entry对象使用时随时就能获取到,又不影响此对象的垃圾收集。

get()方法

/**返回此thread-local变量的当前线程副本中的值。
*  如果变量没有*当前线程的值,则首先通过调用{@link #initialValue}方法将其初始化为返回值。
*/
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();
}

得到ThreadLocalMap的map对象,如果不为空,则取出ThreadLocalMap.Entry对象,上面我们看到ThreadLocalMap.Entry对象实际上
存储着ThreadLocal<?>的弱引用类,如果该ThreadLocalMap.Entry对象不为空得则取出ThreadLocal的值,这样就得到了ThreadLocal的值。

MessageQueue的工作原理

MessageQueue主要包含两个操作:插入和读取。

读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,

1.enqueueMessage的作用是往消息队列中插入一条消息,
2.next的作用是从消息队列中取出一条消息并将其从消息队列中移除。

从enqueueMessage的实现来看,它的主要操作其实就是单链表的插入操作;

next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法
会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除

Looper的工作原理

不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。

Looper的构造方法

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

Looper提供了quit和quitSafely来退出一个Looper,二者的区别是:

quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。

Looper的loop()方法从MessageQueue中取数据,然后Handler下发消息处理

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
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;
        ......
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ......
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       ......
    }
}

Handler的工作原理

Handler的工作主要包含消息的发送和接收过程。
消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过send的一系列方法来实现的。所有方法最后都执行
queue.enqueueMessage(msg,uptimeMillis),所以Handler发送消息的过程仅仅是向消息队列中插入了一条消息。
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环,代码如下。

public static void main(String[] args) {
    ......
    Looper.prepareMainLooper();
    ......
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    ......
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

《Android开发艺术探索》的笔记