基于Android9.0,了解Android消息机制

基于Android9.0,了解Android消息机制

​ 还是那句话:点成线,线成面,切勿贪心,否则一脸懵逼

​ 由于Android的主线程(UI线程)是非安全的,而且Android开发规范的限制,不能在UI子线程中访问UI控件,否则就会出发程序异常。这个时候,就可以通过Handler来将更新UI的操作切换到UI线程中执行。因此,Handler被大家经常用来更新UI,但是从本质上说,Handler并不是专门用于更新UI的。

​ Android的消息机制主要是指Handler的运行机制,然后得到MessageQueue、Looper的支撑。

接下来,我们来了解这三个类

  • Handler
  • ThreadLocal
  • MessageQueue
  • Looper

还是从源码出发。

Handler有两种发送消息的方式

第一种

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {//重写Handler的handlerMessage方法,因为Handler的handlerMessage是一个空实现
        super.handleMessage(msg);
    }
};
Message msg = Message.obtain();//或者new Message();
msg.what = 1;
msg.obj  = "fat";
handler.sendMessage(msg);

Handler的handleMessage方法
/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {//空实现
    }

先看Handler源码

public Handler() {
        this(null, false);
    }
public Handler(Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper();//注意这里,获取当前线程的Looper。至于什么时候创建的,这条路走通后,会来说明
    if (mLooper == null) {//如果没有获取到Looper,会异常。这里证明:线程没有Looper对象,无法创建Handler
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//把线程的MessageQueue与Handler绑定。MessageQueue原理,下面说介绍
    mCallback = callback;
    mAsynchronous = async;
}

看源码发现,该构造方法,就是获取当前线程的Looper,然后与Handler绑定。

下面看Message msg = Message.obtain();//或者new Message();

public static Message obtain() {//从Message池里获取
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;//Message池是链表结构,获取效率高与数组。
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();//池里没有,还是会new Message()
}

handler.sendMessage(msg);

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        ...
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;//得到构造方法时的消息队列
        if (queue == null) {//为空抛异常
            ...
            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);
    }

接下来,看MessageQueue的enqueueMessage

boolean enqueueMessage(Message msg, long when) {
    ...

    synchronized (this) {
        ...

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
            //这里链式插入头部
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
            //这里链式插入。msg插入到p的后面
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

这里,就完成了Handler的sendMessage,最后enqueueMessage到MessageQueue中。

第二种

Handler handler = new Handler();
handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});
或
Handler handler = new Handler();//Lambda表达式
        handler.post(()->{

        });
        

创建Handler和第一种方式一样,直接看post

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

sendMessageDelayed后的流程,和第一方式一样,我们来看不同点,看getPostMessage

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;//这里把我们创建的Runnable,赋值给callback
    return m;// 返回带有callback的Message
}

到这里,Handler基本结束。

接下来,我们先来看看ThreadLocal,为什么需要先看ThreadLocal呢?

因为涉及到如何保证一个线程对应一个Looper,并且各个线程之间的Looper互不干扰,所以我们先来了解ThreadLocal。

ThreadLocal

ThreadLocal是一个泛型类

public class ThreadLocal {

然后,看看类结构(基于9.0),发现,有get、set、remove等方法,应该能感觉到,这个类大方向的作用。

image-20200716192303006.png

我们先来看看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();
}

我们先看看getMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

继续看threadLocals

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

到这里,发现getMap返回的就是当前线程的ThreadLocal.ThreadLocalMap。然后,我们去看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);//注意这里
}

threadLocalHashCode是一个哈希值,防止相同的线程获取同一个对象。

/**
 * ThreadLocals rely on per-thread linear-probe hash maps attached
 * to each thread (Thread.threadLocals and
 * inheritableThreadLocals).  The ThreadLocal objects act as keys,
 * searched via threadLocalHashCode.  This is a custom hash code
 * (useful only within ThreadLocalMaps) that eliminates collisions
 * in the common case where consecutively constructed ThreadLocals
 * are used by the same threads, while remaining well-behaved in
 * less common cases.
 */
private final int threadLocalHashCode = nextHashCode();

这里的table是一个Entry数组

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;

Entry是ThreadLocalMap一个内部类

static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

到这里,我们可以得出结论,ThreadLocal的get方法,获取的是:当前线程下的ThreadLocalMap里面的table中的item,至于这个item里面具体是什么,我们去看看set()方法,继续

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//这里上面分析过了,就是获取当前线程下的threadLocals
    if (map != null)
        map.set(this, value);//注意这里
    else
        createMap(t, value);//注意这里
}

继续createMap

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

这里我们发现,当前线程下threadLocals为null的话,会构造一个ThreadLocalMap,且初始化table。我们再看看map的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主要是维护当前线程下的ThreadLocal.ThreadLocalMap,每次get或set时,都会先通过当前线程的哈希&内部table数组长度拿到table的index值,而当前线程就为这个table item的key。

至于table如何确定位置、如何防止重复、如何扩容,有时间再慢慢学习,还是那句话:先点成线,再线成面,切勿贪心,否则一脸懵逼。

好了,现在来看Looper

Looper

在Handler构造中,有这么一句话

mLooper = Looper.myLooper();//注意这里
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread " + Thread.currentThread()
                + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//这里关联handler和looper的MessageQueue

看myLooper

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
//Looper类中申明sThreadLocal
static final ThreadLocal sThreadLocal = new ThreadLocal();

这里发现一个问题,在整个过程中,没发现sThreadLocal调用set代码,why?

这里2个点,注意:

1.看胖子之前的文章Android启动流程,可知。在调用ActivityThread的main方法时,会调用Looper.prepareMainLooper(),这里就创建了主线程的Looper。
2.子线程,需要手动调用Looper的prepare方法来创建Looper,否则会抛出异常,看Handler源码可知。

这里,我们以主线程为主,来看看ActivityThread的main方法

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();//注意这里
...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
...
    Looper.loop();//注意这里,开启消息循环

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

来看看Looper.prepareMainLooper

public static void prepareMainLooper() {
    prepare(false);//注意这里
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();//上面已经介绍过了,会调用sThreadLocal.get()
    }
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

到这里,真相大白了。现在剩下最后二个问题:MessageQueue以及三者如何贯穿起来的,分析源码虽然很枯燥,不过貌似看完受益匪浅,内功会提升很多。

MessageQueue

在new Looper时,会创建MessageQueue,我们来看看Looper

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//创建MessageQueue
    mThread = Thread.currentThread();//关联当前线程
}

看看构造方法MessageQueue

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;//用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭
    mPtr = nativeInit();//native方法
}

这里很简单,就两行代码,这里还是先看这条线,至于MessageQueue的内部实现,有时间,再来和大家一起研究、学习,这里提一句,MessageQueue内部是以链表的形式。

接下来,来看看几个类,如何贯穿、运作起来的。

全面贯穿

记得ActivityThread,main方法吗?里面调用了Looper.loop(),来看看。

public static void loop() {
    final Looper me = myLooper();//获取当前线程的Looper
    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);//注意这里,msg.target为对应的Handler
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            ...
        }
        ...

        msg.recycleUnchecked();
    }
}

先看queue.next()

Message next() {
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
     // nextPollTimeoutMillis该参数用于确定消息队列中是否还有消息,从而决定消息队列应处于出队消息状态 or 等待状态。
     //nextPollTimeoutMillis为-1,消息队列处于等待状态
    for (;;) {//死循环获取message
         ...
        //阻塞操作,当等待超时或者消息队列被唤醒,才会继续
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
           ...
            Message prevMsg = null;
            Message msg = mMessages;
            ...
            if (msg != null) {
                if (now < msg.when) {
                    ...
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
...
    }
}

记住:点成线,线成面,切勿贪心,否则一脸懵逼。先不要去管为什么死循环不会造成线程阻塞,后续再走这条线。

接着再来看dispatchMessage

//如果你记得handler的两种方式(忘记了,看上面),就知道callback代表什么了
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//使用post方式
        handleCallback(msg);
    } else {//sengMessage方式
        if (mCallback != null) {//我们在构成handler时,mCallback传入的null
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//注意这里
    }
}

先看msg.callback == null的时候,调用的handleMessage

/**
 * Subclasses must implement this to receive messages.子类必须实现它来接收消息。
 */
public void handleMessage(Message msg) {
}

这里是一个空实现,子类需要覆盖,这里其实就是我们构造Handler时的

image-20200717182455681.png

再来看看msg.callback != null的时候,handleCallback方法

private static void handleCallback(Message message) {
    message.callback.run();
}

message.callback为我们传入的Runnable,所以这里最后调用传入的Runnable的run方法。

至此,handler机制差不多完结了。


关于总结:还是不借鉴各路大神的blog总结了。胖子觉得这样会造成部分朋友只看总结,不看内容,最后变成知其然不知其所以然(胖子就吃过很多这样的亏),还是交给朋友们自行总结吧。

胖子总结

  • 先搞清楚Handler、Looper、ThreadLocal、MessageQueue的基本实现
  • 自己用工具画一画它们之间的关系
  • 温馨提示:点成线,线成面,切勿贪心,否则一脸懵逼
  • 胖子有什么理解错误的,欢迎大家指出来,一起讨论、学习、进步
  • 期待胖子的第三篇 《Android事件分发(一)》

参考文献

Android Handler:手把手带你深入分析 Handler机制源码

你可能感兴趣的:(基于Android9.0,了解Android消息机制)