美文网首页
Android线程之消息机制(Handler、MessageQu

Android线程之消息机制(Handler、MessageQu

作者: Amy_LuLu__ | 来源:发表于2018-02-11 16:00 被阅读0次

    注意:本篇文章是本人阅读相关文章所写下的总结,方便以后查阅,所有内容非原创,侵权删。

    本篇文章内容来自于:

    1. Android开发艺术探索 任玉刚
    2. 深入源码解析Android中的Handler,Message,MessageQueue,Looper

    目录

    1. 什么是Android消息机制
    2. Android消息机制概述
    3. Android消息机制分析
      --3.1 ThreadLocal的工作原理
      --3.2 MessageQueue的工作原理
      --3.3 Looper的工作原理
      --3.4 Handler的工作原理

    1. Android消息机制成员介绍

    Android的消息机制主要是指Handler的运行机制。
    Handler的运行需要底层的MessageQueue和Looper的支撑。

    (1) Handler
    Handler是Android消息机制的上层接口,开发时只需和Handler交互即可。
    Handler使用很简单,可以轻松将一个任务切换到Handler所在的线程中去执行。

    为什么Androd要提供这个功能:
    因为Android规定必须在主线程中访问UI,又不建议在主线程中进行耗时操作,否则会导致程序无法响应ANR。如果没有Handler无法将子线程中访问UI的工作切换到主线程中去进行。

    为什么不允许在子线程中访问UI?
    因为Android的UI并不是线程安全的,如果在多线程并发访问可能会导致UI控件处于不可预期的状态。如果加上锁机制会让UI访问的逻辑变复杂影响效率。最简单高效的方法是采用单线程模型来处理UI操作,开发者只需要通过Handler切换线程。

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

    (3) Looper消息循环:
    它的出现是因为MessageQueue只是一个消息的存储单元,不能处理消息。Looper填补了这个功能。
    Looper会以无限循环的形式去查找是否有新消息,如果有的话处理消息,否则就一直等待。
    线程是默认没有Looper的,如果需要使用Handler则必须为线程创建Looper。(主线程ActivityThread创建时会初始化Looper,所以在主线程中默认可以使用Handler)

    ThreadLocal
    Looper中还有一个ThreadLocal。ThreadLocal不是线程,它的作用是可以在每个线程中存储数据。
    Handler创建的时候会采用当前线程的Looper来构造消息循环系统。

    那么Handler内部如何获取当前线程的Looper呢?
    这就要使用ThreadLocal。Looper类中使用ThreadLocal来存储Looper对象。

    那么为什么需要ThreadLocal来存储Looper呢?
    因为Looper的构造函数是私有的,无法创建Looper进行赋值。只能将Looper的引用存在变量中,而且每个线程都有自己对应的Looper,则需要用到ThreadLocal来存储。因为ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。

    2. Android消息机制概述

    Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。

    Handler创建时会采用当前线程的Looper来构建内部的消息循环系统。
    如果当前线程没有Looper,则会报错。因为线程是默认没有Looper的,而主线程ActivityThread创建时会初始化Looper,所以在主线程中默认可以使用Handler。
    那么如何解决?只需要为当前线程创建Looper即可,或者在一个有Looper的线程中。
    Handler创建完毕后,这个时候其内部的Looper以及MessageQueue就可以和Handler一起切同工作了。
    发送消息时,可通过Handler的post方法发送一个Runnable,或者通过Handler的send方法发送一个Message。两个方法都是通过send方法来完成
    当Handler的send方法被调用时,它会调用MessagQueue的enqueueMessage方法将这个消息放入消息队列,然后Looper发现有新消息到来时,就会处理这个消息
    最终消息中的Runnable或者Handler的HandlerMessage方法会被调用

    当使用Handler来post/send消息时,仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper接受到消息后就开始处理,最终消息由Looper交由给Handler处理,即Handler的dispatchMessage方法会被调用,而Handler的dispatchMessage方法会进行判断,如果发送过来的是一个runnbale,则运行这个runnable任务;如果mCallback不为null,则运行mCallback的handlerMessage方法;最后运行自身的handleMessage方法

    3. Android消息机制分析

    3.1 ThreadLocal的工作原理

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

    应用的场景:当某些数据是以线程为作用域并且不同线程具有不通过的数据副本的时候,就可以采用ThreadLocal。
    应用:AcivityThread、Looper、AMS
    由于不同的线程拥有不同的Looper,则可以通过ThreadLocal来轻松实现Looper在线程中的读取。

    ThreadLocal的使用

    public class MainActivity extends BaseActivity {
        private ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            threadLocal.set(true);
            Log.d("xl", "mainthread=" + threadLocal.get());
    
            new Thread("thread1") {
                @Override
                public void run() {
                    threadLocal.set(false);
                    Log.d("xl", "thread1=" + threadLocal.get());
                }
            }.start();
    
            new Thread("thread2") {
                @Override
                public void run() {
                    Log.d("xl", "thread2=" + threadLocal.get());
                }
            }.start();
        }
    
    }
    
    D/xl: mainthread=true
    D/xl: thread1=false
    D/xl: thread2=null
    

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

    ThreadLocal的内部实现
    ThreadLocal是一个泛型类,定义为public class ThreadLocal<T>,只要弄清楚ThreadLocal的get和set方法就可以明白他的工作原理。
    (1)ThreadLocal的set方法

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t); //首先获得当前线程的ThreadLocalMap数据。
            //Thread中有ThreadLocal.ThreadLocalMap threadLocals = null;用于存储ThreadLocal对象,以Entry的形式
            if (map != null)
                map.set(this, value); //有的话设置
            else
                createMap(t, value); //没有的话创建
        }
    

    (2)ThreadLocal的get方法

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);//首先获得当前线程的ThreadLocalMap数据。
            if (map != null) { //有的话则取
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue(); //没有的话创建
        }
    

    3.2 MessageQueue的工作原理

    MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作。
    插入的操作对应enqueueMessage方法,往消息队列中插入一条消息。
    读取的操作对应next方法,从消息队列中取出一条消息并将其从消息队列中移除。

    虽然MessageQueue叫消息队列,但是内部实现是用了一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。

    MessageQueue的内部实现
    (1)enqueueMessage
    主要是单链表的插入操作。

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

    (2)next
    next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法就会阻塞,当有消息到来的时候,next方法会返回这条消息并将其从单链表中删除。

    Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            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;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    3.3 Looper的工作原理

    Looper在消息机制中扮演消息循环的角色。它会不停从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。

    Looper的构造方法(private)
    构造方法中会创建一个MessageQueue消息队列,然后将当前线程的对象保存起来。

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

    Handler的工作需要Looper,没有Looper的线程就会报错。
    那么如何为一个线程创建Looper?
    通过Looper.prepare()可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。

            new Thread("thread1") {
                @Override
                public void run() {
                    Looper.prepare();  
                    //Looper. prepareMainLooper()这个方法是给主线程创建Looper使用的。
                    Handler handler = new Handler();
                    Looper.loop();
                }
            }.start();
    

    如何得到当前线程的Looper对象?
    getMainLooper()方法,可以在任何地方获取到主线程的Looper。
    Looper.myLooper()拿到当前线程的Looper的引用
    如何退出Looper
    Looper提供了quit和quitSafely来退出一个Looper。
    quit会直接退出Looper。
    quitSafely只是设定一个退出标记,会把消息队列中的已有消息处理完毕后才安全的退出。
    Looper退出后,通过Handler发送的消息会失效,此时Handler的send方法会返回false。
    在子线程中,如果手动创建了Looper,那么在所有的事情完成后应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态。

    Looper的内部实现
    (1)Looper.prepare()方法
    会创建一个Looper对象,且用ThreadLocal保存这个对象,ThreadLocal内部是使用当前线程中的一个数组来保存这个Looper的。

        public static void prepare() {
            prepare(true);
        }
    
        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));
        }
    

    (2)Looper.loop()方法
    loop方法是一个死循环,
    唯一跳出循环的方式是MessageQueue的next方法返回了null。
    当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,此时next方法会返回null。
    所以,除非Looper退出,否则loop方法会无限循环下去。

    loop方法会调用MessageQueue的next方法获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。
    当MessageQueue的next方法返回了新消息后,Looper就会处理这条消息:msg.target.dispatchMessage(msg);
    msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给该Handler的dispatchMessage方法来处理了。
    而Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,则成功切换线程了。

        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;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                final long end;
                try {
                    msg.target.dispatchMessage(msg);
                    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (slowDispatchThresholdMs > 0) {
                    final long time = end - start;
                    if (time > slowDispatchThresholdMs) {
                        Slog.w(TAG, "Dispatch took " + time + "ms on "
                                + Thread.currentThread().getName() + ", h=" +
                                msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                    }
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }
    

    3.4 Handler的工作原理

    Handler的工作主要包括消息的发送和接受过程。
    消息的发送可以通过post的一系列方法以及send的一系列方法来实现,其实post也就是用了send方法

        public final boolean dsendMessage(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);
        }
    

    Handler发送消息仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper接受到消息后就开始处理,最终消息由Looper交由给Handler处理,即Handler的dispatchMessage方法会被调用,这是Handler进入处理消息的阶段。

        public void dispatchMessage(Message msg) {
           //1. 首先检查Message的callback是否为null,不为null则调用handleCallback
          //Message的callback是一个runnable对象,实际上就是Handler的post方法所传递的runnable对象,handleCallback就是message.callback.run(),运行runnable。
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    //2.检查mCallback是否为null,不为null则调用mCallback的handleMessage方法。即new Handler(callback)
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                //3.最后调用Handler的handleMessage方法来处理消息
                handleMessage(msg);
            }
        }
    

    为什么在没有Looper的子线程创建Handler会抛异常
    因为public Handler()实际就是调用了Handler(Looper looper)

    相关文章

      网友评论

          本文标题:Android线程之消息机制(Handler、MessageQu

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