Android进程框架:线程通讯的桥梁Handler

关于做者java

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工做,欢迎交流技术方面的问题,能够去个人Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。android

文章目录git

  • 一 消息队列的建立
    • 1.1 创建消息队列
    • 1.2 开启消息循环
  • 二 消息的添加
  • 三 消息的分发和处理
    • 3.1 消息分发
    • 3.2 消息处理

第一次阅览本系列文章,请参见导读,更多文章请参见文章目录程序员

Android是一个消息驱动型的系统,消息机制在Android系统中扮演者重要的角色,与之相关的Handler也是我平常中经常使用的工具。今天咱们就来聊一聊这个。github

Android消息循环流程图以下所示:数组

主要涉及的角色以下所示:缓存

  • Message:消息,分为硬件产生的消息(例如:按钮、触摸)和软件产生的消息。
  • MessageQueue:消息队列,主要用来向消息池添加消息和取走消息。
  • Looper:消息循环器,主要用来把消息分发给相应的处理者。
  • Handler:消息处理器,主要向消息队列发送各类消息以及处理各类消息。

整个消息的循环流程仍是比较清晰的,具体说来:架构

  1. Handler经过sendMessage()发送消息Message到消息队列MessageQueue。
  2. Looper经过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
  3. target handler调用自身的handleMessage()方法来处理Message。

事实上,在整个消息循环的流程中,并不仅有Java层参与,不少重要的工做都是在C++层来完成的。咱们来看下这些类的调用关系。框架

注:虚线表示关联关系,实线表示调用关系。less

在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都经过MessageQueue的Native方法来完成,而其余虚线链接的类只有关联关系,并无 直接调用的关系,它们发生关联的桥梁是MessageQueue。

有了上面这些分析,相信咱们对Android的消息机制有了大体的理解,对于这套机制,咱们很天然会去思考三个方面的问题:

  • 消息队列是如何建立的,它们如何实现消息循环的,消息循环为何不会致使线程卡死?🤔
  • 消息是如何添加到队列中的,它们在队列里是如何排序的?🤔
  • 消息是如何被分发的,分发之后又是如何被处理的?🤔

咱们一一来看一下。

一 消息队列的建立

1.1 创建消息队列

消息队列是由MessageQueue类来描述的,MessageQueue是Android消息机制Java层和C++层的纽带,其中不少核心方法都交由native方法实现。

既然提到对象构建,咱们先来看看它的构造函数。

public final class MessageQueue {
    
    private long mPtr; // used by native code
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
}
复制代码

能够看到它调用的是native方法来完成初始化,这个方法定义在了一个android_os_MessageQueue的C++类类。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //构建NativeMessageQueue对象
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    //将nativeMessageQueue对象的地址值转成long型返回该Java层
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
复制代码

能够看到该方法构建了一个NativeMessageQueue对象,并将NativeMessageQueue对象的地址值转成long型返回给Java层,这里咱们知道其实是mPtr持有了这个 地址值。

NativeMessageQueue继承域MessageQueue.cpp类,咱们来看看NativeMessageQueue的构造方法。

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    
    //先检查是否已经为当前线程建立过一个Looper对象
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        //建立Looper对象
        mLooper = new Looper(false);
        //为当前线程设置Looper对象
        Looper::setForThread(mLooper);
    }
}
复制代码

能够看到NativeMessageQueue构造方法先检查是否已经为当前线程建立过一个Looper对象,若是没有,则建立Looper对象并为当前线程设置Looper对象。

咱们再来看看Looper的构造方法。

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    //建立管道
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
    //读端文件描述符
    mWakeReadPipeFd = wakeFds[0];
    //写端文件描述符
    mWakeWritePipeFd = wakeFds[1];
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
            errno);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
            errno);
    //建立一个epoll实例,并将它的文件描述符保存在变量mEpollFd中
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    //将前面建立的管道读端描述符添加到这个epoll实例中,以便它能够对管道的写操做进行监听
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
            errno);
}
复制代码

这里面提到两个概念:管道与epoll机制。

关于管道

管道在本质上也是文件,但它不是普通的文件,它不属于任何文件类型,并且它只存在与内存之中且有固定大小的缓存区,通常为1页即4kb。它分为读端和写端,读端负责从 管道读取数据,当数据为空时则阻塞,写端负责向管道写数据,当管道缓存区满时则阻塞。那管道在线程通讯中主要用来通知另外一个线程。例如:线程A准备好了Message放入 了消息队列,这个时候须要通知线程B去处理,这个时候线程A就像管道的写端写入数据,管道有了数据以后就回去唤醒线程B区处理消息。也正是基于管道来进行线程的休眠与 唤醒,才保住了线程中的loop循环不会让线程卡死。

关于epoll机制

epoll机制用来监听多个文件描述符的IO读写事件,在Android的消息机制用来监听管道的读写IO事件。

关于epool机制,这里有个简单易懂的解释

epoll一共有三个操做方法:

  • epoll_create():建立一个epoll的句柄,size是指监听的描述符个数
  • epoll_ctl():对须要监听的文件描述符(fd)执行操做,好比将fd加入到epoll句柄。
  • epoll_wait():返回须要处理的事件数目,如返回0表示已超时。

上面Looper的构造方法里,咱们已经看到了利用epoll_create()建立一个epoll的实例,并利用epoll_ctl()将管道的读端描述符操做符添加到epoll实例中,以即可以对管道的 写操做进行监听,下面咱们还能够看到epoll_wait()的用法。

讲到这里整个消息队列便建立完成了,下面咱们接着来看看消息循环和如何开启的。

1.2 开启消息循环

消息循环是创建在Looper之上的,Looper能够为线程添加一个消息循环的功能,具体说来,为了给线程添加一个消息循环,咱们一般会这么作:

public class LooperThread extends Thread {

    public Handler mHandler;

    @Override
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}
复制代码

能够看到先调用Looper.prepare()初始化一个Looper,而后调用Looper.loop()开启循环。

关于Looper,有两个方法来初始化prepare()/prepareMainLooper(),它们建立的Looper对象都是同样,只是prepareMainLooper() 建立的Looper是给Android主线程用的,它仍是个静态对象,以便其余线程均可以获取到它,从而能够向主线程发送消息。

public final class Looper {
    
   // sThreadLocal.get() will return null unless you've called prepare().
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   private static Looper sMainLooper;  // guarded by Looper.class

    public static void prepare() {
          prepare(true);
      }
  
     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));
     }
  
      //建立主线程的Looper,应用启动的时候会右系统调用,咱们通常不须要调用这个方法。
      public static void prepareMainLooper() {
          prepare(false);
          synchronized (Looper.class) {
              if (sMainLooper != null) {
                  throw new IllegalStateException("The main Looper has already been prepared.");
              }
              sMainLooper = myLooper();
          }
      }
      
      //返回和当前线程相关的Looper
      public static @Nullable Looper myLooper() {
          return sThreadLocal.get();
      }
}

复制代码

指的一提的是这里使用的是ThreadLocal来存储新建立的Looper对象。

ThreadLocal描述的是线程本地存储区,不一样的线程不能访问对方的线程本地存储区,当前线程能够对本身的线程本地存储区进行独立的修改和读取。

之因此会采用ThreadLocal来存储Looper,是由于每一个具有消息循环能力的线程都有本身独立的Looper,它们彼此独立,因此须要用线程本地存储区来存储Looper。

咱们在接着来看看Looper的构造函数,以下所示:

public final class Looper {
    
  private Looper(boolean quitAllowed) {
      //建立消息队列
      mQueue = new MessageQueue(quitAllowed);
      //指向当前线程
      mThread = Thread.currentThread();
   }  
}
复制代码

Looper的构造函数也很简单,构造了一个消息队列MessageQueue,并将成员变量mThread指向当前线程,这里构建了一个MessageQueue对象,在MessageQueue构建 的过程当中会在C++层构建Looper对象,这个咱们上面已经说过。

Looper对象建立完成后就能够开启消息循环了,这是由loop()方法来完成的。

public final class Looper {
    
     public static void loop() {
        //获取当前线程的Looper
        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;

        //确保当前线程处于本地进程中,Handler仅限于处于同一进程间的不一样线程的通讯。
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //进入loop主循环方法
        for (;;) {
            //不断的获取下一条消息,这个方法可能会被阻塞
            Message msg = queue.next();
            if (msg == null) {
                //若是没有消息须要处理,则退出当前循环。
                return;
            }

            // 默认为null,可经过setMessageLogging来指定输出,用于debug
            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);
            }

            //把message回收到消息池,以便重复利用。
            msg.recycleUnchecked();
        }
     }
}
复制代码

能够看到,这个方法不断重复着如下三件事:

  1. 调用MessageQueue的next()方法读取MessageQueue的下一条Message。
  2. 把Message分发给相应的target。
  3. 再把分发的Message回收到消息池,以便重复利用。

如此消息循环便创建起来了。

二 消息的添加

在如此开始中,咱们一般会调用handler的sendXXX()或者postXXX()将一个Message或者Runnable,这些方法实际上调用的MessageQueue的enqueueMessage()方法,该方法 会给目标线程的消息队列插入一条消息。

注:如何理解这个"目标线程的消息队列",首先要明白Handler、Looper与MessageQueue这三兄弟是全家桶,绑在一块儿的,你用哪一个Handler,消息就被插入到了这个Handler所在线程 的消息队列里。

public final class MessageQueue {
    
      boolean enqueueMessage(Message msg, long when) {
            //每一个消息都必须有个target handler
            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) {
                //正在退出时,回收Message,加入消息池。
                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;
                //mMessages表示当前须要处理的消息,也就是消息队列头的消息
                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 {
                    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;
                }
    
                // 唤醒消息队列
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
}
复制代码

enqueueMessage()以时间为序将消息插入到消息队列中去,如下三种状况下须要插入到队列头部:

  • 消息队列为空
  • 要插入的消息的执行时间为0
  • 要插入的消息的执行时间小于消息队列头的消息的执行时间

上面三种状况很容易想到,其余状况以时间为序插入到队列中间。当有新的消息插入到消息队列头时,当前线程就须要去唤醒目标线程(若是它已经睡眠(mBlocked = true)就执行唤醒操做,不然不须要),以便 它能够来处理新插入消息头的消息。

经过这里的分析,你能够发现,消息队列事实上是基于单向链表来实现的,虽然咱们总称呼它为"队列",但它并非一个队列(不知足先进先出)。

一样利用单向链表这种思路的还有对象池,读者应该有印象,不少文档都提倡经过Message.obtain()方法获取一个Message对象,这是由于Message对象会被缓存在消息池中,它主要利用 Message的recycle()/obtain()方法进行缓存和获取。

具体说来:

recycle()

public final class Message implements Parcelable {
    
        private static final Object sPoolSync = new Object();
        private static Message sPool;
        
        public void recycle() {
            //判断消息是否正在使用
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            //对于再也不使用的消息加入消息池
            recycleUnchecked();
        }
    
        void recycleUnchecked() {
            //将消息标记为FLAG_IN_USE并清空关于它的其余信息
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                //当消息池没有满时,将消息加入消息池
                if (sPoolSize < MAX_POOL_SIZE) {
                    //将sPool存放在next变量中
                    next = sPool;
                    //sPool引用当前对象
                    sPool = this;
                    //消息池数量自增1
                    sPoolSize++;
                }
            }
        }
}
复制代码

obtain()

public final class Message implements Parcelable {
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                
                //sPool当前持有的消息对象将做为结果返回
                Message m = sPool;
                //将m的后继从新赋值给sPool,这其实一个链表的删除操做
                sPool = m.next;
                //m的后继置为空
                m.next = null;
                //清除 in-use 标志位
                m.flags = 0;
                //消息池大小自减1
                sPoolSize--;
                return m;
            }
        }
        //当对象池为空时,直接建立新的Message()对象。
        return new Message();
    }
}
复制代码

这里面有个巧妙的设计,这也给咱们如何设计一个对象池提供了一个很好的思路,它是以单向链表具体说来:

  1. 在类中定义一个该类的静态对象sPool以及它的后继对象next。
  2. 当对象加入对象池时,将该对象加入到链表中。
  3. 当对象从对象池中取出时,返回sPool当前持有的对象便可,并将sPool从当前链表中移除。

好了。消息池就聊这么多,咱们接着来看消息的分发和处理。

三 消息的分发与处理

3.1 消息分发

消息的分发是创建在消息循环之上的,在不断的循环中拉取队列里的消息,消息循环的创建流程咱们上面已经分析过,经过分析得知,loop()方法 不断调用MessageQueue的next()读取消息队列里的消息,从而进行消息的分发。

咱们来看看next()方法的实现。

public final class MessageQueue {
    
    Message next() {
           final long ptr = mPtr;
           //当前消息循环已退出,直接返回。
           if (ptr == 0) {
               return null;
           }
   
           //pendingIdleHandlerCount保存的是注册到消息队列中空闲Handler个个数
           int pendingIdleHandlerCount = -1; 
           //nextPollTimeoutMillisb表示当前无消息到来时,当前线程须要进入睡眠状态的
           //时间,0表示不进入睡眠状态,-1表示进入无限等待的睡眠状态,直到有人将它唤醒
           int nextPollTimeoutMillis = 0;
           for (;;) {
               if (nextPollTimeoutMillis != 0) {
                   Binder.flushPendingCommands();
               }
   
               //nativePollOnce是阻塞操做,用来检测当前线程的消息队列中是否有消息须要处理
               nativePollOnce(ptr, nextPollTimeoutMillis);
   
               //查询下一条须要执行的消息
               synchronized (this) {
                   final long now = SystemClock.uptimeMillis();
                   Message prevMsg = null;
                   //mMessages表明了当前线程须要处理的消息
                   Message msg = mMessages;
                    
                   //查询第一个能够处理的消息(msg.target == null表示没有处理Handler,没法进行处理,忽略掉)
                   if (msg != null && msg.target == null) {
                       do {
                           prevMsg = msg;
                           msg = msg.next;
                       } while (msg != null && !msg.isAsynchronous());
                   }
                   if (msg != null) {
                       //若是消息的执行时间大于当前时间,则计算线程须要睡眠等待的时间
                       if (now < msg.when) {
                           //当异步消息的触发时间大于当前时间,则使者下一次轮询的超时时长
                           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                       } 
                       //若是消息的执行时间小于当前时间,则说明该消息须要当即执行,则将该消息返回,并从消息队列中
                       //将该消息移除
                       else {
                           mBlocked = false;
                           if (prevMsg != null) {
                               prevMsg.next = msg.next;
                           } else {
                               mMessages = msg.next;
                           }
                           msg.next = null;
                           if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                           //设置消息的使用状态,即flags |= FLAG_IN_USE。
                           msg.markInUse();
                           return msg;
                       }
                   } else {
                       // 若是没有更多消息须要处理,则将nextPollTimeoutMillis置为-1,让当前线程进入无限睡眠状态,直到
                       //被其余线程唤醒。
                       nextPollTimeoutMillis = -1;
                   }
   
                   //全部消息都已经被处理,准备退出。
                   if (mQuitting) {
                       dispose();
                       return null;
                   }
                   
                   //pendingIdleHandlerCount指的是等待执行的Handler的数量,mIdleHandlers是一个空闲Handler列表
                   if (pendingIdleHandlerCount < 0
                           && (mMessages == null || now < mMessages.when)) {
                       pendingIdleHandlerCount = mIdleHandlers.size();
                   }
                   if (pendingIdleHandlerCount <= 0) {
                       //当没有空闲的Handler须要执行时进入阻塞状态,mBlocked设置为true
                       mBlocked = true;
                       continue;
                   }
   
                   //mPendingIdleHandler是一个IdleHandler数组
                   if (mPendingIdleHandlers == null) {
                       mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                   }
                   mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
               }
   
               //只有在第一次循环时,才会去执行idle handlers,执行完成后重置pendingIdleHandlerCount为0
               for (int i = 0; i < pendingIdleHandlerCount; i++) {
                   final IdleHandler idler = mPendingIdleHandlers[i];
                   //释放Handler的引用
                   mPendingIdleHandlers[i] = null;
   
                   boolean keep = false;
                   try {
                       keep = idler.queueIdle();
                   } catch (Throwable t) {
                       Log.wtf(TAG, "IdleHandler threw exception", t);
                   }
   
                   if (!keep) {
                       synchronized (this) {
                           mIdleHandlers.remove(idler);
                       }
                   }
               }
   
               //执行完成后,重置pendingIdleHandlerCount为0,以保证不会再次重复运行。
               pendingIdleHandlerCount = 0;
               
               //当调用一个空闲Handler时,新的消息能够当即被分发,所以无需再设置超时时间。
               nextPollTimeoutMillis = 0;
           }
       } 
}
复制代码

首先要明确一个概念,MessageQueue是利用对象间的后继关联(每一个对象都知道本身的直接后继)实现的链表,其中它的成员变量mMessages变量指的是当前须要被处理消息。

next()方法主要用来从消息队列里循环获取消息,这分为两步:

  1. 调用nativePollOnce(ptr, nextPollTimeoutMillis)方法检测当前线程的消息队列中是否有消息须要处理(注意不是在这里取消息)。它是一个阻塞操做,可能会引发 线程睡眠,下面咱们会说。
  2. 查询当前须要处理的消息,返回并将其从消息队列中移除。

这个查询当前须要处理的消息能够分为三步:

  1. 找到当前的消息队列头mMessages,若是它为空就说明整个消息队列为空,就将nextPollTimeoutMillis置为-1,当前线程进入无限睡眠等待,知作别的线程将其唤醒。若是 它不为空,则进入步骤2.
  2. 若是消息队列头的执行之间大于当前时间,则说明线程须要等待该消息的执行,线程进入睡眠。不然进入步骤3.
  3. 查找到了当前须要被处理的消息,将该消息从消息队列里移除,并返回该消息。

能够看到这里调用的是native方法nativePollOnce()来检查当前线程是否有消息须要处理,调用该方法时,线程有可能进入睡眠状态,具体由nextPollTimeoutMillis参数决定。0表示不进入睡眠状态,-1表示 进入无限等待的睡眠状态,直到有人将它唤醒。

咱们接着来看看nativePollOnce()方法的实现。

nativePollOnce()方法是个native方法,它按照调用链:android_os_MessageQueue#nativePollOnce() -> NativeMessageQueue#pollOnce() -> Looper#pollOnce() -> Looper#pollInner() 最终完成了消息的拉取。可见实现功能的仍是在Looper.cpp里。

咱们来看一下实现。

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ...
        //内部不断调用pollInner()方法检查是否有新消息须要处理
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    ...
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //调用epoll_wait()函数监听前面注册在mEpollFd实例里的管道文件描述符中的读写事件。若是这些管道
    //文件描述符没有发生读写事件,则当前线程会在epoll_wait()方法里进入睡眠,睡眠事件由timeoutMillis决定
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
    
    //epoll_wait返回后,检查哪个管道文件描述符发生了读写事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        //若是fd是当前线程关联的管道读端描述符且读写事件类型是EPOLLIN
        //就说明当前线程关联的一个管道写入了新的数据,这个时候就会调用
        //awoken()去唤醒线程
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                //此时已经唤醒线程,读取清空管道数据
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } 
        ...
     }
     ...
    return result;
}
复制代码

能够看到这个方法作了两件事情:

  1. 调用epoll_wait()函数监听前面注册在mEpollFd实例里的管道文件描述符中的读写事件。若是这些管道文件描述符没有发生读写事件,则当前线程 会在epoll_wait()方法里进入睡眠,睡眠事件由timeoutMillis决定。
  2. 若是fd是当前线程关联的管道读端描述符且读写事件类型是EPOLLIN就说明当前线程关联的一个管道写入了新的数据,这个时候就会调用awoken()去唤醒线程。

至此,消息完成了分发。从上面的loop()方法咱们能够知道,消息完成分发后会接着调用Handler的dispatchMessage()方法来处理消息。

咱们接着来聊一聊Handler。

3.1 消息处理

Handler主要用来发送和处理消息,它会和本身的Thread以及MessageQueue相关联,当建立一个Hanlder时,它就会被绑定到建立它的线程上,它会向 这个线程的消息队列分发Message和Runnable。

通常说来,Handler主要有两个用途:

  • 调度Message和Runnable,延时执行任务。
  • 进行线程的切换,请求别的线程完成相关操做。

咱们先来看看Handler的构造函数。

public class Handler {
    
    //无参构造方法,最经常使用。
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    
    public Handler(Callback callback, boolean async) {
        //匿名类、本地类都必须声明为static,不然会警告可能出现内存泄漏,这个提示咱们应该很熟悉了。
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    
        //获取当前线程的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //获取当前线程的消息队列
        mQueue = mLooper.mQueue;
        //回调方法,这个Callback里面其实只有个handleMessage()方法,咱们实现这个
        //接口,就不用去用匿名内部类那样的方式来建立Handler了。
        mCallback = callback;
        //设置消息是否为异步处理方式
        mAsynchronous = async;
    }   
}
复制代码

对于构造方法而言,咱们最经常使用的是无参构造方法,它没有Callback回调,且消息处理方式为同步处理,从这里咱们也能够看出你在哪一个线程里建立了Handler,就默认使用当前线程的Looper。

从上面的loop()方法中,咱们知道Looper会调用MessageQueue的dispatchMessage()方法进行消息的处理,咱们来看看这个方法的实现。

public class Handler {
        public void dispatchMessage(Message msg) {
            //当Message存在回调方法时,优先调用Message的回调方法message.callback.run()
            if (msg.callback != null) {
                //实际调用的就是message.callback.run();
                handleCallback(msg);
            } else {
                //若是咱们设置了Callback回调,优先调用Callback回调。
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                //若是咱们没有设置了Callback回调,则回调自身的Callback方法。
                handleMessage(msg);
            }
        }
}
复制代码

能够看到整个消息分发流程以下所示:

  1. 当Message存在回调方法时,优先调用Message的回调方法message.callback.run()。
  2. 果咱们设置了Callback回调,优先调用Callback回调。
  3. 若是咱们没有设置了Callback回调,则回调自身的Callback方法。

由此咱们也能够得知方法调用的优先级,从高到低依次为:

  • message.callback.run()
  • Handler.mCallback.handleMessage()
  • Handler.handleMessage()

大部分代码都是以匿名内部类的形式实现了Handler,因此通常会走到第三个流程。

能够看到因此发送消息的方法最终都是调用MessageQueue的enqueueMessage()方法来实现,这个咱们上面在分析MessageQueue的时候已经说过,这里就再也不赘述。

理解了上面的内容,相信读者已经对Android的消息机制有了大体的了解,咱们趁热打铁来聊一聊实际业务开发中遇到的一些问题。

在平常的开发中,咱们一般在子线程中执行耗时任务,主线程更新UI,更新的手段也多种多样,如Activity#runOnUIThread()、View#post()等等,它们之间有何区别呢?若是个人代码了 既没有Activity也没有View,我该如何将代码切换回主线程呢?🤔

咱们一一来分析。

首先,Activity里的Handler直接调用的就是默认的无参构造方法。能够看到在上面的构造方法里调用Looper.myLooper()去获取当前线程的Looper,对于Activity而言当前线程就是主线程(UI线程),那主线程 的Looper是何时建立的呢?🤔

03Android组件框架:Android视图容器Activity一文 里咱们就分析过,ActivityThread的main()函数做为应用的入口,会去初始化Looper,并开启消息循环。

public final class ActivityThread {
      public static void main(String[] args) {
          ...
          Looper.prepareMainLooper();
          ...
          if (sMainThreadHandler == null) {
              sMainThreadHandler = thread.getHandler();
          }
          ...
          Looper.loop();
          throw new RuntimeException("Main thread loop unexpectedly exited");
      }  
}
复制代码

主线程的Looper已经准备就绪了,咱们再调用Handler的构造函数去构建Handler对象时就会默认使用这个Handler,以下所示:

public class Activity {
    
   final Handler mHandler = new Handler();

   public final void runOnUiThread(Runnable action) {
          if (Thread.currentThread() != mUiThread) {
              mHandler.post(action);
          } else {
              action.run();
          }
      }  
}
复制代码
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    
    public boolean post(Runnable action) {
        
        //当View被添加到window时会添加一些附加信息,这里面就包括Handler
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        //Handler等相关信息尚未被关联到Activity,先创建一个排队队列。
        //这其实就至关于你去银行办事,银行没开门,大家在门口排队等着同样。
        getRunQueue().post(action);
        return true;
    }
}
复制代码

这里面也是利用attachInfo.mHandler来处理消息,它事实上是一个Handler的子类ViewRootHandler,一样的它也是使用Looper.prepareMainLooper()构建出来的Looper。

因此你能够看出Activity#runOnUIThread()、View#post()这两种方式并无本质上的区别,最终还都是经过Handler来发送消息。那么对于那些既不在Activity里、也不在View里的代码 当咱们想向主线程发送消息或者将某段代码(一般都是接口的回调方法,在这些方法里须要更新UI)post到主线程中执行,就能够按照如下方式进行:

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        //TODO refresh ui
    }
})
复制代码

好了,到这里整个Android的消息机制咱们都已经分析完了,若是对底层的管道这些东西感受比较模糊,能够先理解Java层的实现。

相关文章
相关标签/搜索