zoukankan      html  css  js  c++  java
  • Android的消息机制

    一、简介

      Android的消息机制主要是指Handler的运行机制,那么什么是Handler的运行机制那?通俗的来讲就是,使用Handler将子线程的Message放入主线程的Messagequeue中,在主线程使用。

    二、学习内容

      学习Android的消息机制,我们需要先了解如下内容。

    1. 消息的表示:Message
    2. 消息队列:MessageQueue
    3. 消息循环,用于循环取出消息进行处理:Looper
    4. 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler

      平常我们接触的大多是Handler和Message,今天就让我们来深入的了解一下他们。

    三、代码详解

      一般而言我们都是这样使用Handler的

      

     xxHandler.sendEmptyMessage(xxx);
    

      当然还有其他表示方法,但我们深入到源代码中,会发现,他们最终都调用了一个方法

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

      sendMessageAtTime()方法,但这依然不是结束,我们可以看到最后一句enqueueMessage(queue, msg, uptimeMillis);按字面意思来说插入一条消息,那么疑问来了,消息插入了哪里。

      

     1   boolean enqueueMessage(Message msg, long when) {
     2         if (msg.target == null) {
     3             throw new IllegalArgumentException("Message must have a target.");
     4         }
     5         if (msg.isInUse()) {
     6             throw new IllegalStateException(msg + " This message is already in use.");
     7         }
     8 
     9         synchronized (this) {
    10             if (mQuitting) {
    11                 IllegalStateException e = new IllegalStateException(
    12                         msg.target + " sending message to a Handler on a dead thread");
    13                 Log.w(TAG, e.getMessage(), e);
    14                 msg.recycle();
    15                 return false;
    16             }
    17 
    18             msg.markInUse();
    19             msg.when = when;
    20             Message p = mMessages;
    21             boolean needWake;
    22             if (p == null || when == 0 || when < p.when) {
    23                 // New head, wake up the event queue if blocked.
    24                 msg.next = p;
    25                 mMessages = msg;
    26                 needWake = mBlocked;
    27             } else {
    28                 // Inserted within the middle of the queue.  Usually we don't have to wake
    29                 // up the event queue unless there is a barrier at the head of the queue
    30                 // and the message is the earliest asynchronous message in the queue.
    31                 needWake = mBlocked && p.target == null && msg.isAsynchronous();
    32                 Message prev;
    33                 for (;;) {
    34                     prev = p;
    35                     p = p.next;
    36                     if (p == null || when < p.when) {
    37                         break;
    38                     }
    39                     if (needWake && p.isAsynchronous()) {
    40                         needWake = false;
    41                     }
    42                 }
    43                 msg.next = p; // invariant: p == prev.next
    44                 prev.next = msg;
    45             }
    46 
    47             // We can assume mPtr != 0 because mQuitting is false.
    48             if (needWake) {
    49                 nativeWake(mPtr);
    50             }
    51         }
    52         return true;
    53     }

      进入源代码,我们发现,我们需要了解一个新类Messagequeue。

      虽然我们一般把他叫做消息队列,但是通过研究,我们发下,它实际上是一种单链表的数据结构,而我们对它的操作主要是插入和读取。

      看代码33-44,学过数据结构,我们可以轻松的看出,这是一个单链表的插入末尾的操作。

      这样就明白了,我们send方法实质就是向Messagequeue中插入这么一条消息,那么另一个问题随之而来,我们该如何处理这条消息。

      处理消息我们离不开一个重要的,Looper。那么它在消息机制中又有什么样的作用那?

      Looper扮演着消息循环的角色,具体而言它会不停的从MessageQueue中查看是否有新消息如果有新消息就会立刻处理,否则就已知阻塞在那里,现在让我们来看一下他的代码实现。

      首先是构造方法

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

          可以发现,它将当前线程对象保存了起来。我们继续

      Looper在新线程创建过程中有两个重要的方法looper.prepare() looper.loop

      

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

    我们先来看prepare()方法

    1 private static void prepare(boolean quitAllowed) {
    2         if (sThreadLocal.get() != null) {
    3             throw new RuntimeException("Only one Looper may be created per thread");
    4         }
    5         sThreadLocal.set(new Looper(quitAllowed));
    6     }

    咦,我们可以看到这里面又有一个ThreadLocal类,我们在这简单了解一下,他的特性,set(),get()方法。

        首先ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在制定线程中可以获取存储的数据,对于其他线程而言则无法获取到数据。简单的来说。套用一个列子:

    private ThreadLocal<Boolean> mBooleanThreadLocal = new                ThreadLocal<Boolean>();//
    
    
    mBooleanThreadLocal.set(true);
    Log.d(TAH,"Threadmain"+mBooleanThreadLocal.get());
    new Thread("Thread#1"){
        public void run(){
            mBooleanThreadLocal.set(false);
            Log.d(TAH,"Thread#1"+mBooleanThreadLocal.get());
        };      
    }.start();
    
    new Thread("Thread#2"){
        public void run(){
            Log.d(TAH,"Thread#2"+mBooleanThreadLocal.get());
        };      
    }.start();

    上面的代码运行后,我们会发现,每一个线程的值都是不同的,即使他们访问的是同意个ThreadLocal对象。

    那么我们接下来会在之后分析源码,为什么他会不一样。现在我们跳回prepare()方法那一步,loop()方法源码贴上

     1 public static void loop() {
     2         final Looper me = myLooper();
     3         if (me == null) {
     4             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     5         }
     6         final MessageQueue queue = me.mQueue;
     7 
     8         // Make sure the identity of this thread is that of the local process,
     9         // and keep track of what that identity token actually is.
    10         Binder.clearCallingIdentity();
    11         final long ident = Binder.clearCallingIdentity();
    12 
    13         for (;;) {
    14             Message msg = queue.next(); // might block
    15             if (msg == null) {
    16                 // No message indicates that the message queue is quitting.
    17                 return;
    18             }
    19 
    20             // This must be in a local variable, in case a UI event sets the logger
    21             Printer logging = me.mLogging;
    22             if (logging != null) {
    23                 logging.println(">>>>> Dispatching to " + msg.target + " " +
    24                         msg.callback + ": " + msg.what);
    25             }
    26 
    27             msg.target.dispatchMessage(msg);
    28 
    29             if (logging != null) {
    30                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    31             }
    32 
    33             // Make sure that during the course of dispatching the
    34             // identity of the thread wasn't corrupted.
    35             final long newIdent = Binder.clearCallingIdentity();
    36             if (ident != newIdent) {
    37                 Log.wtf(TAG, "Thread identity changed from 0x"
    38                         + Long.toHexString(ident) + " to 0x"
    39                         + Long.toHexString(newIdent) + " while dispatching to "
    40                         + msg.target.getClass().getName() + " "
    41                         + msg.callback + " what=" + msg.what);
    42             }
    43 
    44             msg.recycleUnchecked();
    45         }
    46     }

    首先loop()方法,获得这个线程的Looper,若没有抛出异常。再获得新建的Messagequeue,在这里我们有必要补充一下Messagequeue的next()方法。

      1   Message next() {
      2         // Return here if the message loop has already quit and been disposed.
      3         // This can happen if the application tries to restart a looper after quit
      4         // which is not supported.
      5         final long ptr = mPtr;
      6         if (ptr == 0) {
      7             return null;
      8         }
      9 
     10         int pendingIdleHandlerCount = -1; // -1 only during first iteration
     11         int nextPollTimeoutMillis = 0;
     12         for (;;) {
     13             if (nextPollTimeoutMillis != 0) {
     14                 Binder.flushPendingCommands();
     15             }
     16 
     17             nativePollOnce(ptr, nextPollTimeoutMillis);
     18 
     19             synchronized (this) {
     20                 // Try to retrieve the next message.  Return if found.
     21                 final long now = SystemClock.uptimeMillis();
     22                 Message prevMsg = null;
     23                 Message msg = mMessages;
     24                 if (msg != null && msg.target == null) {
     25                     // Stalled by a barrier.  Find the next asynchronous message in the queue.
     26                     do {
     27                         prevMsg = msg;
     28                         msg = msg.next;
     29                     } while (msg != null && !msg.isAsynchronous());
     30                 }
     31                 if (msg != null) {
     32                     if (now < msg.when) {
     33                         // Next message is not ready.  Set a timeout to wake up when it is ready.
     34                         nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
     35                     } else {
     36                         // Got a message.
     37                         mBlocked = false;
     38                         if (prevMsg != null) {
     39                             prevMsg.next = msg.next;
     40                         } else {
     41                             mMessages = msg.next;
     42                         }
     43                         msg.next = null;
     44                         if (DEBUG) Log.v(TAG, "Returning message: " + msg);
     45                         msg.markInUse();
     46                         return msg;
     47                     }
     48                 } else {
     49                     // No more messages.
     50                     nextPollTimeoutMillis = -1;
     51                 }
     52 
     53                 // Process the quit message now that all pending messages have been handled.
     54                 if (mQuitting) {
     55                     dispose();
     56                     return null;
     57                 }
     58 
     59                 // If first time idle, then get the number of idlers to run.
     60                 // Idle handles only run if the queue is empty or if the first message
     61                 // in the queue (possibly a barrier) is due to be handled in the future.
     62                 if (pendingIdleHandlerCount < 0
     63                         && (mMessages == null || now < mMessages.when)) {
     64                     pendingIdleHandlerCount = mIdleHandlers.size();
     65                 }
     66                 if (pendingIdleHandlerCount <= 0) {
     67                     // No idle handlers to run.  Loop and wait some more.
     68                     mBlocked = true;
     69                     continue;
     70                 }
     71 
     72                 if (mPendingIdleHandlers == null) {
     73                     mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
     74                 }
     75                 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
     76             }
     77 
     78             // Run the idle handlers.
     79             // We only ever reach this code block during the first iteration.
     80             for (int i = 0; i < pendingIdleHandlerCount; i++) {
     81                 final IdleHandler idler = mPendingIdleHandlers[i];
     82                 mPendingIdleHandlers[i] = null; // release the reference to the handler
     83 
     84                 boolean keep = false;
     85                 try {
     86                     keep = idler.queueIdle();
     87                 } catch (Throwable t) {
     88                     Log.wtf(TAG, "IdleHandler threw exception", t);
     89                 }
     90 
     91                 if (!keep) {
     92                     synchronized (this) {
     93                         mIdleHandlers.remove(idler);
     94                     }
     95                 }
     96             }
     97 
     98             // Reset the idle handler count to 0 so we do not run them again.
     99             pendingIdleHandlerCount = 0;
    100 
    101             // While calling an idle handler, a new message could have been delivered
    102             // so go back and look again for a pending message without waiting.
    103             nextPollTimeoutMillis = 0;
    104         }
    105     }

    从24-30我们可以看到,他遍历了整个queue找到msg,若是msg为null,我们可以看到50,他把nextPollTimeoutMillis = -1;实际上是等待enqueueMessage的nativeWake来唤醒。较深的源码涉及了native层代码,有兴趣可以研究一下。简单来说next()方法,在有消息是会返回这条消息,若没有,则阻塞在这里。

    我们回到loop()方法27msg.target.dispatchMessage(msg);我们看代码

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

    msg.target实际上就是发送这条消息的Handler,我们可以看到它将msg交给dispatchMessage(),最后调用了我们熟悉的方法handleMessage(msg);

    三、总结

      到目前为止,我们了解了android的消息机制流程,但它实际上还涉及了深层的native层方法,这里有一篇博客专门讲解这个转载一下

    http://www.cnblogs.com/angeldevil/p/3340644.html。

  • 相关阅读:
    MySQL轻量级监测工具—doDBA
    MySQL构建百万级数据
    MySQL备份与恢复—xtrabackup
    MySQL8.0.15二进制包安装
    「考试」省选51
    「总结」二次剩余
    「考试」省选50
    「总结」$pdf$课:$dp2$
    「考试」省选49
    「考试」省选48
  • 原文地址:https://www.cnblogs.com/yrstudy/p/6358783.html
Copyright © 2011-2022 走看看