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

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方法来处理消息。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

猜你喜欢

转载自blog.csdn.net/weixin_43440181/article/details/131689223
今日推荐