美文网首页
Android的消息机制结合代码示例详解

Android的消息机制结合代码示例详解

作者: BlueSocks | 来源:发表于2023-07-11 19:54 被阅读0次

    1.Android消息机制概述

    1.1. Android消息机制是什么?

    Android消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作工程,这三者实际上是一个整体,只不过我们在开发过程中比较多的接触到Handler而已。

    1.2. Handler的主要作用

    Handler的主要作用是将一个任务切换到Handler所在的线程中执行。比如我们经常碰到的一个场景:Android建议不要在主线程中进行耗时操作,否则会导致程序无法响应(ANR),那么我们会在子线程中进行耗时的网络请求和I/O操作。当耗时操作完成后需要更新UI,由于Android开发规范的限制,我们不能在子线程中访问UI,否则会程序异常,这个时候就需要通过Handler将任务切换到主线程进行UI更新操作。

    对于访问UI只能在主线程中进行,否则程序会抛出异常这个验证工作是由ViewRootImpl的checkThread方法来完成的,如下所示:

        void checkThread(){
            if (mThread != Thread.currentThread()){
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views."
                );
            }
        }
    
    
    • 这里延伸一个问题,系统为什么不允许在子线程中访问UI?

    系统为什么不允许在子线程中访问UI呢?这是因为Android饿UI控件不是线程安全的,如果在多线程中并非访问可能会导致UI控件处于不可预期的状态,那为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:首先加上锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是需要通过Handler切换一下UI访问的执行线程即可。

    2. MeesageQueue的工作原理

    MessageQueue主要包含两个操作:插入和读取,分别对应MessageQueue的enqueueMessage和next方法。Handler通过send方法发送一条消息后,最终会调用MessageQueue的enqueueMessage方法,enqueueMessage方法会将这条消息插入到消息队列中。而next方法的作用是从消息队列中取出一条消息并将其从消息队列中移除。

    2.1. enqueueMessage方法分析

    我们先来分析下插入操作的实现,也就是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;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    
    

    从enqueueMessage的实现来看,它的主要操作是单链表的插入操作。链表的插入操作是按照when的大小进行排序,when值小的message排在前面:

    if (p == null || when == 0 || when < p.when):

    • p == null:表示当前链表没有消息

    • when == 0:表示新Message的when为0

    • when < p.when:表示新Message的when小于链表首结点Message的when

    • 如果是上述三种情况,则直接将新的Message插入到链表首部。

    • 如果p == null || when == 0 || when < p.when为false,则会开始遍历链表,获取链表中的下一个Message与新Message的when值比较,如果满足p == null || when < p.when则结束循环,将新Message插入。

    2.2. next方法分析

    接着我们看下如何从消息队列中取出消息,也就是next方法的实现,源码如下:

        Message next() {
            //...省略无关代码
    
            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;
                    }
                    //...
                }
                //...
            }
        }
    
    

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

    3. Looper的工作原理

    Looper在Android消息机制中扮演着消息循环的角色, 它会不停的从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。对于Looper我们需要分析下它的创建过程以及它是如何轮询消息的。

    3.1. Looper的创建

    Handler的工作需要Looper,如果当前线程没有Looper对象线程就会报错,那么如何为当前线程创建Looper呢?其实很简单,通过Looper.prepare()即可为当前线程创建一个Looper对象,接着通过Looper.loop()来开启消息循环,如下所示:

        new Thread("Thread#1"){
            @Override
            public void run() {
                super.run();
                //创建Looper对象
                Looper.prepare();
                Handler handler = new Handler();
                //开启消息轮询
                Looper.loop();
            }
        }.start();
    
    

    我们先来分析Looper的prepare方法,看看Looper对象是怎么创建以及存取的,代码如下:

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        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));
        }
    
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
    

    分析上面的prepare方法可以知道,Looper对象是通过ThreadLocal实现在线程中存取的,换句话说就是Looper的作用域是线程,每个线程都有各自的Looper对象。并且当前线程创建Looper后,如果再次调用perpare方法,会抛出RuntimeException异常,所以每个线程都只有一个Looper对象。

    下面我们再看下Looper的构造方法,在构造方法中它会创建一个MessageQueue(消息队列),然后将当前线程的对象保存起来,如下所示:

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

    3.2. loop方法分析

    Looper最重要的一个方法是loop方法,只有调用了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.");
            }
            if (me.mInLoop) {
                Slog.w(TAG, "Loop again would have the queued messages be executed"
                        + " before this one completed.");
            }
    
            me.mInLoop = true;
    
            // 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 = me.mQueue.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);
                }
    
                msg.target.getTraceName(msg);
    
                // 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();
            }
        }
    
    

    Looper的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发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就功地将代码逻辑切换到指定线程中去执行了。

    4. Handler的工作原理

    Handler的主要工作是发送和接收消息。 Handler可以通过post的一系列方法或send的一系列方法来发送消息,当然最终都是通过sendMessageAtTime方法来发送消息的,在sendMessageAtTime方法中,通过调用MessaageQueue的enqueueMessage将消息插入到消息队列中。Handler接收消息是在handleMessage方法中,Looper查询到新消息后,最终会调用Handler的handleMessage方法,这样消息最终又回调到了handleMessage放中。

    4.1. Handler发送消息

    Handler提供了post的一系列方法或send的一系列方法来发送消息,如下所示:

        public final boolean post(@NonNull Runnable r) {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    
        public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
            return sendMessageAtTime(getPostMessage(r), uptimeMillis);
        }
    
        public final boolean postDelayed(Runnable r, int what, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
        }
    
        public final boolean sendMessage(@NonNull Message msg) {
            return sendMessageDelayed(msg, 0);
        }
    
        public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    
        public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    

    可以看到,Handler提供的post系列方法和send系列方法最终都是调用了sendMessageAtTime方法,而sendMessageAtTime方法最终调用了MessageQueue的enqueueMessage方法向消息队列中插入了一条消息。

    至此,Handler发送消息过程就完成了。

    4.2. Handler处理消息

    在分析Looper的时候,我们说到Looper的loop方法中,如果有新消息会交给Handler处理,即Handler的dispatchMessage方法会被调用,dispatchMessage源码如下:

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

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

        //callback就是post方法所传递的Runnable对象
        //public final boolean post(@NonNull Runnable r) {
        //   return  sendMessageDelayed(getPostMessage(r), 0);
        //}
    
        private static void handleCallback(Message message) {
            message.callback.run();
        }
        ```
    -     其次,检查mCallback是否为null,不为null就调用mCall的handleMessage方法来处理消息,Callback是个接口,它的定义如下:
    -   ```
    
        /**
         * Callback interface you can use when instantiating a Handler to avoid
         * having to implement your own subclass of Handler.
         */
        public interface Callback {
            /**
             * @param msg A {@link android.os.Message Message} object
             * @return True if no further handling is desired
             */
            boolean handleMessage(@NonNull Message msg);
        }
    
    

    通过Callback可以采用如下方式来创建Handler对象,Handler handler = new Handler(callback)。那么Callback的意义是什么呢?源码里面的注释已经做了说明:可以用来创建一个Handler的实例但并不需要派生Handler的子类。

    最后,调用Handler的handleMessage方法来处理消息。

    相关文章

      网友评论

          本文标题:Android的消息机制结合代码示例详解

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