美文网首页
Android学习感悟之消息机制

Android学习感悟之消息机制

作者: arvinljw | 来源:发表于2017-05-16 16:28 被阅读0次

    本篇Android消息机制的原理,包括四个方面ThreadLocal、MessageQueue、Looper和Handler,会通过消息机制流程来分析理解。

    简介

    由于所有UI操作都必须在主线程中完成,所以我们有时候在子线程得到的数据后需要转化到主线程去更改UI,而这就是Android提供消息机制的原因。这里有个细节,为什么UI操作必须要在主线程完成呢?其中最主要的原因是我们的View并不是线程安全的。这又有一个问题了,为什么不给View加锁呢?因为它会让View的访问变得复杂,而且会降低UI的访问效率,锁是会阻塞某些线程的执行,而使用单线程就可以避免这两个问题,然后使用Handler转换线程也不麻烦。

    下面就进入正题,其实消息机制最主要的就是Handler、Looper以及MessageQueue,我简单的理了个消息机制的流程图,如下:

    下面我们会根据这个流程去解读源码。

    消息机制流程源码分析

    Looper.prepare()

    看了上边的流程图,发现我们自己在主线程中使用handler好像没有直接操作过Looper呀?其实在界面创建之前系统已经给我们初始化了Looper,源码在ActivityThread中的main方法中,如下:

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

    其中去掉了些这里不涉及的代码,可以看到其实主线程还是走了这个流程的,其中有一点不一样的地方,是主线程的looper是单独存起来的,来看源码:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
        sMainLooper = myLooper();
    }
    
    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));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
    

    第一步prepare主要就做了两个事,在threadLocal中设置Looper,Looper中初始化MessageQueue;这里有个知识点,ThreadLocal是如何让不同线程的Looper都不一样的,如果不看系统的实现方式,我们也能大体想到,使用一个Map去存,key就是线程,value就是Looper,这样我们就能保证不同线程Looper不一样,且同一线程只有一个Looper了,这里就深入一步查看一下ThreadLocal的源码:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    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);
    }
    
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
    
    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();
    }
    

    这段代码是刚才ThreadLocal保存Looper所调用的代码,大体含义还是比较简单,就是存入一个Entry数组中,每一项都有自己的key和value,其中索引是用一个hashCode和这个数组的长度进行&运算的来的。这样就会让查询变得便利,其实HashMap就是类似的储存方式。

    然后再看看ThreadLocal的get方法:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T) e.value;
        }
        return setInitialValue();
    }
    
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    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);
    }
    
    private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
    
        while (e != null) {
            ThreadLocal k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    

    这样看来,其实ThreadLocal的功能就是类似HashMap一样,存取都是通过key,数据结构使用数组,所以是通过key的hashCode与数组长度的到。

    这样对ThreadLocal应该就有了一定的了解了。

    下一步。

    创建Handler

    创建Handler的方式有很多,但是最终不外乎两种,传入Looper和不传入Looper;如下:

    不传Looper

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
            }
        }
    
        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;
    }
    

    传入Looper

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

    其作用在于在handler中的得到Looper对象以及其MessageQueue,然后另外还有回调等数据。便于用户直接操作这一个类就可以了。

    Looper.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) {
                return;
            }
            
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
    
            msg.recycleUnchecked();
        }
    }
    
    Message next() {
    
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
    
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
    
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } 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;
                }
    
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
               //其他代码...
            }
        }
    }
    
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    public void handleMessage(Message msg) {
    }
    

    去掉了一些其他代码,可以看到loop这个方法是有一个死循环,只有在MeesageQueue的next返回null时,才会结束Looper的循环;而MessageQueue的next方法也是一个死循环,只有当mQuitting为true时返回null,其他时候都在循环中,当有要处理的消息时,就返回该Message,然后交给相应的Handler去处理,里边会判断callback是否为空,来判断是post的消息还是send的消息,然后再分别处理,而send的消息需要我们自己重写handleMessage方法去实现消息处理,而具体发送消息会在下文说到。

    这里边涉及到的MessageQueue的数据结构其实是使用链式存储,具体的方式下文再分解,这里先知道大体流程。

    Handler发送消息

    Handler发送消息有两种方式:

    • sendMessage(Message)
    • post(Runnable)

    其实他们两个方法最后都是转化成Mesasge去实现,来直接看源码。

    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);
    }
    
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
    
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
    
            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;
            }
    
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    

    下面是post的源码,也就在第一步在构造Message对象,然后后边就一样了。

    public final boolean post(Runnable r) {
        return sendMessageDelayed(getPostMessage(r), 0);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    

    这里的post和send消息都还有其他的一些方法,比如延时发送,比如发送一个空消息,等等但是归根结底都是调用了enqueueMessage方法,来加入到MessageQueue中。

    然后Looper的loop方法在需要处理的时候就能得到该消息,并处理。而这其中最主要的是,需要理解怎么加入到MessageQueue的。

    所以我们需要先理解其存储结构,可以看到,Message.next属性,也是Message类型的,所以可以猜到应该是使用了链式存储,所以它并不是一个队列。

    再来看一下enqueueMessage方法,这里跟一下代码:

    当是第一条Message传入时,可以知道mMessages为空,所以进入if,然后让传入的msg.next = p, mMessages = msg,即当前消息的后一条时空,mMessages为第一条消息;

    假设第一条的when是5,这时候第二条消息进入,when是10;所以会进入else,然后再进入循环遍历后加入到链表的最后;

    这时候第三条消息进入,when是8;这时候还是会第三条消息的when比第一条消息的when大,所以还是会进入else,循环之后,发现不用加到最后,因为第三条的when小于第二条的when,就break了,这时候就插入到了第二条。

    这时候链表的顺序就为:消息1->消息3->消息3。

    最后再提供一个WeakHandler的源码,它能避免内存泄漏,原理是采用了弱引用。

    import android.os.Handler;
    import android.os.Message;
    
    import net.arvin.afbaselibrary.listeners.IWeakHandler;
    
    import java.lang.ref.WeakReference;
    
    public class WeakHandler extends Handler {
        private WeakReference<IWeakHandler> mActivity;
    
        public WeakHandler(IWeakHandler activity) {
            mActivity = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            if (mActivity != null) {
                IWeakHandler weakHandleInterface = mActivity.get();
                if (weakHandleInterface != null) {
                    weakHandleInterface.handleMessage(msg);
                }
            }
        }
    
    }
    
    //回调接口IWeakHandler
    
    import android.os.Message;
    
    public interface IWeakHandler {
        void handleMessage(Message msg);
    }
    

    至此,整个消息的流程就基本分析完了。

    相关文章

      网友评论

          本文标题:Android学习感悟之消息机制

          本文链接:https://www.haomeiwen.com/subject/tavlxxtx.html