Android 消息机制

在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("");

  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 消息机制)