欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android消息机制(二)之 MessageQueue 与 Looper

程序员文章站 2022-05-14 07:57:18
...

前言

  一张图让你了解消息机制怎么实现 线程间通信
  重温了Android艺术开发探索这本书的消息机制,写这篇文章加入了博主自己的一些理解,换了些角度,希望能帮助对Android消息机制还不太理解的朋友们。若文章有不正确的地方,欢迎讨论,欢迎指出。
  Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层的MessageQueue和Looper的支撑。先简单介绍一下,MessageQueue是消息队列,用于存储一组消息(虽然叫消息队列,其实是用单链表的数据结构来存储消息列表)。Looper中文翻译为循环,这里可以理解为消息循环。它会无限循环访问消息队列中有没有新的消息需要处理。
  那么这两个东西在哪里的呢?实际上又是怎么和Handler配合一起工作的呢?
  

Handler、MessageQueue和Looper大致的工作模型

Android消息机制(二)之 MessageQueue 与 Looper

  这个模型是我自己理解的一个模型,上面画的是我们经常见到的一种情况,就是在子线程中更新UI时,发消息交给UI线程去更新的一种情况。这个实现的实际就是在子线程中拥有一个Handler对象,这个对象持有Looper的对象,那么这个Handler在post消息的时候,就知道它要发到哪个线程中执行了。
  这里有几个需要注意的地方:
  1. Looper不是每个线程中一定都拥有的。主线程例外,当应用程序启动的时候,就已经给主线程创建好一个Looper了。所以这就是为什么我们可以在主线程中直接使用Handler的原因。而其他新开的线程想拥有Looper的话,需要自己创建(这个主要是为了,可以让其他线程发消息给自己处理),创建Looper的方式,Looper.prepare()
  2. Looper是对象是存放在线程对象的一个成员变量ThreadLocalMap中的一个数组中的,具体存放和获取的方式请阅读我的上一篇文章   Android消息机制(一)之 ThreadLocal

例子代码:

某个Activity,即主线程中
private String content=null;

 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textView=(TextView)findViewById(R.id.textView);
        //创建属于主线程的handler,在主线成中创建,默认拥有主线程的Looper
        handler=new Handler();


        // 构建Runnable对象。在runnable中更新界面
        Runnable   runnableUi=new  Runnable(){
            @Override
        public void run() {
            //更新界面
            textView.setText("the Content is:"+content);
        }

        };
        //新创建一个线程,利用主线程的handler发送一个runnable对象,让主线程执行这段程序
        new Thread(){
            public void run(){  
               //注意,这里不用创建一个Looper,因为这个发给主线程的消息。和子线程的Looper没有关系

                content=df.downLoadFiles();     
                handler.post(runnableUi); 
            }                   
        }.start();  
    }

看了上面的示例代码就很好理解了。那么MessageQueue和Looper是怎么实现的呢?
  

MessageQueue的工作原理

  MessageQueue的翻译是消息队列,上面说了,它实际上不是一个队列,它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
  通过上面个工作模型分析,MessageQueue主要的工作就两个,插入和读取,分别对应enqueueMessage 和 next 方法。enqueueMessage 方法的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移出。
  下面看看两个方法的部分源码:

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

从实现上来看,主要操作就是单链表的插入操作,这里算法比较简单,就不做算法分析了,本文着重在对消息机制的理解。

接下来看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方法会返回一条消息并将其从消息队列中移出
  

Looper的工作原理

  从上面的工作模型中我们知道,Looper的主要工作就是不断从MessageQueue那里去消息。有的话会拿出来,并且处理这个消息。否则这个Looper也会阻塞住。
  先看看它的构造方法,可以知道 构造一个Looper对象之后,会创建一个新的MessageQueue,还把它的当前线程保存起来。

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

  在一个线程中创建Handler对象是需要Looper对象的。下面的代码是学习,如何在新线程中创建Looper,并且开启Looper的消息循环

  new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();

另外有必要提一下,因为主线程的Looper比较特殊,比如我们可能会在各种地方更新UI,那么主线程的Looper就变得很重要了。于是Looper提供了一个静态方法,getMainLooper,可以通过它在任何地方获取主线程的Looper对象。下面是getMainLooper方法的代码,以及与之有关的方法。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

   public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

  public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

我们可以通过一系列的方法看出来,其实就是通过ThreadLocal的方式去获取主线程中Looper。如果还不清楚ThreadLocal是什么的朋友。请戳   Android消息机制(一)之 ThreadLocal

接下来看Looper中最重要的一个方法loop方法。开启消息循环

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

        // 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 traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

  从上面的代码我们也可以看出,loop方法也是一个死循环,唯一跳出循环的条件是MessageQueue的next方法返回了null。从上面的MessageQueue的代码我们知道,MessageQueue如果没有消息,next方法并不会返回null,而是阻塞在方法里了,所以会导致Looper也阻塞在loop方法里。只有当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,否则loop方法就会无限循环下去(阻塞)。
  当消息队列有消息时,loop会处理这条消息。msg.target.dispatchMessage(msg); msg.target就是发送这条信息的Handler对象,这样Handler对象发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里要注意!字面是好像没有一个切换线程的过程,其实,Handler对象发送消息这个动作是发生在A线程的。但是Handler持有的Looper对象是可能运行在另一个线程的,在本文的例子代码中,就是在主线程当中的,所以切换到了主线程中去执行了消息了。
  

总结

  弄清了Handler机制之后主要就是其中一种对跨线程处理的处理方式有了很清晰的理解。看似Handler对象自己发的消息自己处理,其实是交给了某个线程的Looper去处理了。关键只要记住上面的工作模型就好了。
  Android中用到消息机制的地方有很多!特别是在主线程中,ActivityThread的内部类H(继承了Handler),就处理了各种事务,包括打开应用程序时创建Application,创建Activity等等等。以后有机会可能博主会一些写相关的文章~
  

  
  
  
—— 参考文献    Android开发艺术探索 - 任玉刚著