美文网首页
Android 消息机制

Android 消息机制

作者: wind_sky | 来源:发表于2019-04-16 17:06 被阅读0次

    在Android开发过程中,我们经常需要在不同线程中传递消息,其中会经常用到Handler,而Android的消息机制,就是Handler和MessageQueue、Looper一起协同工作的过程。

    一. 概述

    1. Handler的主要作用是将某个任务切换到Handler所在的线程中执行,一般比较常用的场景是我们在子线程进行网络请求,之所以在子线程进行网络请求是因为在UI线程执行耗时操作会导致ANR,获取数据之后在UI线程进行更新界面,为什么只能在UI线程更新页面,因为Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。

    2. 系统为什么不允许在子线程中去访问UI呢? 因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。

    3. Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper就会报错。Handler可以通过post方法发送一个Runnable到消息队列中,也可以通过send方法发送一个消息到消息队列中,其实post方法最终也是通过send方法来完成。

    4. MessageQueue的enqueueMessage方法最终将这个消息放到消息队列中,当Looper发现有新消息到来时,处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意Looper是运行Handler所在的线程中的,这样一来业务逻辑就切换到了Handler所在的线程中去执行了。

    二. 消息机制原理

    为了更好的理解Looper的工作原理,这里先介绍一下ThreadLocal类。
    更详细的可见:https://www.jianshu.com/p/7968fe20d62c

    ThreadLocal的工作原理:

    ThreadLocal 是线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只有在指定的线程中才可以获取到存储的数据,对于其他线程不能获得数据。对于Handler来说,它需要获取当前线程的Looper,而Looper的作用域就是线程,而且每个线程拥有不同的Looper,通过ThreadLocal可以实现线程中的存取。通过ThreadLocal可以让监听器作为线程内的全局对象存在,在线程内部只要通过get方法获得监听器,ThreadLocal原理:不同线程访问同一个get方法,get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。

    (1)ThreadLocal的set方法:

    public void set(T value) {
         Thread currentThread = Thread.currentThread();
         //通过values方法获取当前线程中的ThreadLoacl数据——localValues
         Values values = values(currentThread);
         if (values == null) {
             values = initializeValues(currentThread);
         }
         values.put(this, value);
     }
    

    从上面代码可以看出,在set时,首先要获得当前线程,然后根据当前线程找到对应的values对象,如果没有values对象则创建一个,然后把要保存的数据存储进去。在Thread类内部有一个protected的属性ThreadLocal.Values的成员用于存储线程的ThreadLocal数据,所以values(currentThread)方法就是直接返回currentThread的这个属性。如果这个属性值为空,则可以new一个Values对象并赋值给currentThread的成员。而Values的put方法就是用来保存数据的,Values类是ThreadLocal的静态内部类,而Values里有一个数组:private Object[] table,ThreadLocal的值就存在这个数组中。

    /**
     * Sets entry for given ThreadLocal to given value, creating an
     * entry if necessary.
     */
    void put(ThreadLocal<?> key, Object value) {
        cleanUp();
    
        // Keep track of first tombstone. That's where we want to go back
        // and add an entry if necessary.
        int firstTombstone = -1;
    
        for (int index = key.hash & mask;; index = next(index)) {
            Object k = table[index];
    
            if (k == key.reference) {
                // Replace existing entry.
                table[index + 1] = value;
                return;
            }
    
            if (k == null) {
                if (firstTombstone == -1) {
                    // Fill in null slot.
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
                    return;
                }
    
                // Go back and replace first tombstone.
                table[firstTombstone] = key.reference;
                table[firstTombstone + 1] = value;
                tombstones--;
                size++;
                return;
            }
    
            // Remember first tombstone.
            if (firstTombstone == -1 && k == TOMBSTONE) {
                firstTombstone = index;
            }
        }
    }
    

    从这个put方法的源码可以看出,ThreadLocal的值在table数组中的存储位置总是ThreadLocal的reference字段所标识的对象的下一个位置。

    (2)ThreadLocal的get方法:

    public T get() {
         // Optimized for the fast path.
         Thread currentThread = Thread.currentThread();
         Values values = values(currentThread);     //找到localValues对象
         if (values != null) {
             Object[] table = values.table;
             int index = hash & values.mask;
             if (this.reference == table[index]) {  //找到ThreadLocal的reference对象在table数组中的位置
                 return (T) table[index + 1];       //reference字段所标识的对象的下一个位置就是ThreadL的值
             }
         } else {
             values = initializeValues(currentThread);
         }
         return (T) values.getAfterMiss(this);
     }
    

    从上面的源码可以看出来,也是通过currentThread来获取到Values对象,然后判断reference属性是否等于Object数组index位置的对象,等于的时候则获取index下一个位置的对象,这个对象就是之前保存的数据。如果没有Values则使用默认的值,默认情况下为null。

    从ThreadLocal的set/get方法来看,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set/get方法,它们对ThreadLocal的读/写行为仅限于各自线程的内部。

    MessageQueue工作原理:

    MessageQueue是消息队列,主要包含两个操作,插入和读取,读取操作本身也包含着删除操作,MessageQueue内部通过一个单链表数据结构来存储消息,插入读取操作对应的方法是enqueueMessage和next,

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

    首先先判断Message是否绑定了一个Handler对象,即msg.target对象,再判断message是否正在使用,当都满足条件的时候,将锁定message对象,然后判断消息是否退出,如果是就将消息回收并返回false,否则将消息标记为正在使用,并设置一些信息。Message p = mMessage,当第一次执行到这时,p是空,所以把当前message作为消息队列中链表的头结点,注:when == 0 || when < p.when说明上一个消息已经执行完毕或者是当前消息比上一个消息先执行,所以相当于是第一次执行到这里。如果已经有头结点,那么就遍历到链表尾部,将当前消息插入到尾部。

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

    从最外面的for循环我们可以看出来这是一个无限循环的过程,当消息队列里有消息时,则会取出这个message,即mMessages。当满足条件时,则取出这个mMessages(通过Message msg = mMessages赋值后返回msg),然后把mMessages赋值为msg.next,即把下一个消息赋值成mMessages。通过这个过程就把当前消息处理完了,并且把处理过的消息删除掉了。
    当消息队列里没有消息时,会查看是否存在IdleHandler,如果存在便执行,如果不存在便阻塞等待消息的到来。

    Looper工作原理:

    Looper在消息机制中负责不断地从消息队列中查看是否有新消息,如果有则立即处理,如果没有则阻塞。

    通过Looper.prepare()方法即可为当前线程创建一个Looper,再通过Looper.loop()开启消息循环。prepareMainLooper()方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。

    prepare方法

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

    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
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
    
                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();
            }
    }
    

    首先是获取到当前线程的Looper对象和绑定到Looper对象的这个消息队列,接着就是一个死循环了。loop方法是调用MessageQueue的next方法获得消息的,从上面的next方法源码可以看出,next是一个阻塞的操作,当没有新消息时会一直阻塞在那里,这导致loop也会阻塞,如果next返回了新消息,Looper就会处理这个消息,把这个消息通过Handler(msg.target是一个Handler对象)的dispatchMessage方法来处理这个消息,这也就到了Handler里面去执行了,这样就成功地将代码逻辑切换到指定的线程中去了。

    如果当这个loop执行到一半的时候要退出怎么办呢?Looper提供quit和quitSafely来退出一个Looper,并标识为退出状态,当消息队列被标记为退出状态时,next方法就会返回null,这样loop里面的死循环就跳出去了。

    上述通过prepare方法和loop方法只是对普通线程来说的,对于主线程来说,由于主线程情况比较复杂,所以提供了prepareMainLooper来给ActivityThread创建Looper对象,但是其本质也是通过prepare来实现的,这个可以去看源码。同时,也可以通过getMainLooper方法在其他任何地方获取到主线程的Looper对象。

    注: 1.当Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false; 2. 在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。

    Handler的工作原理:

    Handler主要包括消息的发送和接收,也就是相当于Handler给自己发送了一条消息,只是消息经过了消息队列以及Looper,最后才到Handler的handleMessage方法里。Handler的消息的发送主要由post一系列方法以及send的一系列方法来实现,post的一系列方法最终都是通过send的一系列方法来实现的。而send一系列方法最后都是通过sendMessageAtTime方法来实现的。

    sendMessageAtTime 方法

    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则通过loop方法调用Handler的dispatchMessage方法(msg.target.dispatchMessage(msg);)。下面是dispatchMessage的代码

    dispatchMessage 方法

      public void dispatchMessage(Message msg) {
         if (msg.callback != null) {
             //Message的callback是一个Runnable,       
            //也就是Handler的 post方法所传递的Runnable参数
             handleCallback(msg);
         } else {
             //如果给Handler设置了Callback的实现,
             //则调用Callback的handleMessage(msg)
             if (mCallback != null) {
                 if (mCallback.handleMessage(msg)) {
                     return;
                 }
             }
             //调用Handler的handleMessage方法来处理消息,
             //该Handler子类需重写handlerMessage(msg)方法
             handleMessage(msg);
         }
     }
     private static void handleCallback(Message message) {
         //直接执行Runnable中的run()方法
         message.callback.run();
     }
     public interface Callback {
         //不想继承Handler子类,可以通过Callback来实现handleMessage
         public boolean handleMessage(Message msg);
     }
     //默认空实现
     public void handleMessage(Message msg) {
     }
    

    Thread、Looper、Handler、MessageQueue之间的数量对应关系:

    Thread :Handler = 1 :n
    Thread :Looper = 1 :1
    Looper :MessageQueue = 1 :1

    主线程消息循环:

    Android的主线程就是ActivityThread,主线程的入口方法为main(String[] args),在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

    main方法

    public static void main(String[] args) {
      ... 
      Process.setArgV0("<pre-initialized>");
    
      Looper.prepareMainLooper();//创建主线程的Looper
    
      ActivityThread thread = new ActivityThread();
      thread.attach(false);
    
      if (sMainThreadHandler == null) {
          sMainThreadHandler = thread.getHandler();
      }
    
      if (false) {
          Looper.myLooper().setMessageLogging(new
                  LogPrinter(Log.DEBUG, "ActivityThread"));
      }
    
      Looper.loop();//开启looper
    
      throw new RuntimeException("Main thread loop unexpectedly exited");
    } 
    

    从源码中可以看到,通过Looper.prepareMainLooper来给主线程创建了一个Looper对象以及MessageQueue对象,然后通过Looper.loop();来开启消息循环。其中,主线程的Handler是mH这个变量,mH是ActivityThread.H的一个实例,它内部定义了一组消息,主要包含了四大组建的启动和停止。

    Android主线程的消息循环模型:

    ActivityThread 通过ApplicationThread 和AMS 进行进程间通信,AMS 以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread 会向H 发送消息,H 收到消息后会将ApplicationThread 中的逻辑切换到ActivityTread 中去执行,即切换到主线程中去执行。四大组件的启动过程基本上都是这个流程。

    思考:
    Looper.loop(),这里是一个死循环,如果主线程的Looper终止,则应用程序会抛出异常。所以,既然主线程 “卡” 在这里了,有两个问题
    (1)那Activity为什么还能启动;(2)点击一个按钮仍然可以响应?

    问题1:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。所以,主线程的Handler把消息添加到了MessageQueue,Looper.loop会拿到该消息,并在主线程中执行。这就解释了为什么主线程的Looper是个死循环,而Activity还能启动,因为四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行分发的。

    问题2:和问题1原理一样,点击一个按钮最终都是由系统发消息来进行的,都经过了Looper.loop()处理。

    相关文章

      网友评论

          本文标题:Android 消息机制

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