handler消息机制包含四个对象_handler消息机制

(4) 2024-07-01 19:23

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
handler消息机制包含四个对象_handler消息机制,希望能够帮助你!!!。

概述

  android中非主线程是不能进行UI操作的,而且在主线程中也不能进行耗时操作。那么当需要进行耗时操作后再更新UI界面又该怎么办呢?
  这里就涉及到了线程间的消息传递机制,也就是Handler机制。通过Handler实现线程间的消息传递,并且我们常用的方法Activity.runOnUiThread(Runnable)方式进行的UI界面的更新实际上也是使用的Handler方式。

  该过程涉及四个对象,Handler,Message,Looper,MessageQueue。那么这四者又是如何配合来达到发送消息的呢?用送信的方式来形容如下:

  • Handler:收信人/发件人,执行具体的操作/发送消息
  • Message:信封,线程发送的消息实体
  • MessageQueue:邮筒,消息队列,存放消息实体
  • Looper:送信人,从消息队列中取消息并进行分发

也就是写信人将信件放到邮筒中,送信人从邮筒中拿出送给收信人。

举例

  先来看一下不采用这套机制而是直接在子线程中操作UI的结果。

btn = findViewById(R.id.btn_act_two); btn.setOnClickListener(v -> { 
    new Thread(() -> { 
    btn.setText("test"); }).start(); }); 

  上面是直接在btn按钮的点击事件中开启一个线程,然后在线程中进行UI更新,不出意料抛出异常信息。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

采用Handler机制:

 btn = findViewById(R.id.btn_act_two); Handler handler = new Handler(); btn.setOnClickListener(v -> { 
    new Thread(() -> { 
    handler.post(() -> { 
    btn.setText("test"); }); }).start(); }); 

  此时再运行就可以成功更新UI界面了。


分析

  上面就是一个使用Handler传递消息的小示例,毕竟下面分析的所有的过程都是为了消息传递的,而根据案例来分析则更容易找到切入点。
  那么就从Handler开始分析。先看一下该过程的使用,首先创建了一个Handler对象,然后在btn的点击事件中调用他的post方法,参数是一个Runnable对象,在其中封装了要更新UI的操作。
  但是要看Handler的话又要对Looper有一定的理解,那么不如先从Looper开始分析吧。

Looper:消息传输中的动力源泉
 private Looper(boolean quitAllowed) { 
    mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 

  Looper将构造方法私有化,并且没有提供其他的构造方法。也就是说,Looper不能通过new的方法创建对象,那么它必定有其他的方法可以获得Looper实体。继续查看源码可以发现,Looper有两个方法可以得到Looper对象,prepareMainLooper和prepare方法。

 //这个方法创建了一个Looper,并且将它作为主线程的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 } } //获取当前进程的Looper public static @Nullable Looper myLooper() { 
    return sThreadLocal.get(); } 

  上面的方法是在创建应用时的主线程中调用的,实际还是通过prepare方法创建的Looper。另外由于该方法将创建的Looper作为了主线程的Looper,所以开发者一般情况下是禁止使用这个方法的。那么创建Looper的prepare又做了些什么呢?

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

  可以看到,prepare首先会判断当前线程是否已经创建过Looper,若是创建过则会抛出异常信息。也就是说,一个线程只能对应一个Looper,也就是只能prepare一次。若是没有创建过,则会创建一个Looper放到ThreadLocal中。而ThreadLocal是按照线程信息储存数据的,获取时也是根据当前线程获取对应的数据。ThreadLocal是Looper的静态常量用于存储进程对应的Looper。

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

  当调用了prepare创建Looper之后,在ThreadLocal中就会有一个当前线程的Looper对象了。而在Looper的构造方法中,还创建了一个消息队列MessageQueue,也就是说,每个Looper都对应的有一个消息队列(每个送信人负责一个邮筒)。
  现在已经有了邮筒了,那么送信人什么时候知道有信封被放在了邮筒中呢?当然是时刻守着邮筒啊,也就是Looper将会一直关注着MessageQueue,这在代码中是通过loop方法的一个死循环实现的。

//删除了部分代码 public static void loop() { 
    //得到当前线程的Looper,是为了获取对应的MessageQueue 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; //这里通过死循环一直从消息队列中取消息 for (;;) { 
    //取消息,队列中没有消息时,会阻塞在这里 Message msg = queue.next(); // might block if (msg == null) { 
    // No message indicates that the message queue is quitting. return; } try { 
    //分发消息 msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { 
    if (traceTag != 0) { 
    Trace.traceEnd(traceTag); } } msg.recycleUnchecked(); } } 

  在loop方法中,for(;;)循环没有设置结束条件,也就是一个死循环,那么为什么不会造成卡顿呢,这就要看MessageQueue的实现了。在MessageQueue的next方法中,若是队列中没有Message时,则会阻塞在这里。因此loop中的这个死循环不会造成系统的卡顿。
  到这里,基本上Looper的主要操作就看完了。那么总结一下,Looper是不能通过new来创建的,而是通过prepare()创建,之后调用loop()来开启循环,阻塞式地从消息队列中取消息。

  送信人从邮筒取信件的过程已经分析完了,那么接下来的问题就是信件什么时候被放在邮筒中的了。
  还记得文章开头吗,在子线程中通过Handler更新UI操作,那么就从Handler来分析一下。按照惯例看一下Handler的构造方法

Handler:消息发送和处理中心
 //其中一个构造方法 public Handler(Callback callback, boolean async) { 
    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 " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 

  从构造方法可以看出,在Handler中的一些属性mHandler,mQueue等都是从Looper中的ThreadLocal中获取的,而关于构造方法的详细解释,会在后文中说明,这里只是为了走一遍发送消息的过程。

  创建Handler后,接下来就是post方法,毕竟post中才是更新UI的操作,那么post方法有做了什么呢?

public final boolean post(Runnable r){ 
    return sendMessageDelayed(getPostMessage(r), 0); } 

  这里直接调用了sendMessageDelayed方法,同时将post的参数runnable进行了封装。

private static Message getPostMessage(Runnable r) { 
    Message m = Message.obtain(); m.callback = r; return m; } 

  可以看到,getPostMessage将runnable操作封装到了Message中,也就是将信纸放在信封中,毕竟信封才是信件的载体。而在Handler中,并不只是post这一种方式发送消息,而是有一系列的方法可以选择,下图总结了Handler中的所有发送消息的方法,并分为了两类,其中红色是将消息放在消息队列的前面(将会优先出队发送出去),黄色是按照常规放在队列后面。
handler消息机制包含四个对象_handler消息机制_https://bianchenghao6.com/blog__第1张
  由图中各种方法的调用可以看出,不论是何种方法,都是将消息(runnable或者空消息int )封装在Message中,然后再调用enqueueMessage方法将消息放入消息队列中。这里将没有具体操作(callback==null)的Message称为空消息。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 
    msg.target = this; if (mAsynchronous) { 
    msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } 

  继续追踪,在enqueueMessage中就做了两件事,将Message的target指向自己,调用MessageQueue的enqueueMessage方法将消息入队。这个target是定义在Message中的类型为Handler的变量,用于处理Message事件。

  那么Handler又是如何处理消息的呢?

public interface Callback { 
    public boolean handleMessage(Message msg); } public void handleMessage(Message msg) { 
   } 

  由上面可以看出,在Handler中,声明了一个接口Callback,里面只有一个方法handleMessage,这个方法就是处理Message消息的,Callback在构造方法中传入。当然,若是在构造方法中没有传入Callback的话还有一种方式,Handler中也定义了一个handleMessage方法,默认是个空实现。可以通过重写这个方法来处理Message消息。
  而消息的处理也是要根据这个来确定的。首先会去判断这个Message是不是个空消息,即Message的callback字段是否为空,若不是空消息,则直接执行它的callback方法(Runnable.run);若是空消息则由Handler来处理。而Handler处理又分为两种情况,若是在构造方法中传入了Callback,则使用Callback的handleMessage来处理,否则使用Handler本身的handleMessage处理消息。

public void dispatchMessage(Message msg) { 
    if (msg.callback != null) { 
    handleCallback(msg);//自己处理 } else { 
    //handler处理 if (mCallback != null) { 
    if (mCallback.handleMessage(msg)) { 
    return; } } handleMessage(msg); } } private static void handleCallback(Message message) { 
    message.callback.run(); } 

  上面这个方法就是用来分发消息的,可以看到处理事件的优先过程就是我们分析的这样。
  这里直接就到分发消息这一步了,而又是谁dispatchMessage的呢?这就要回到Looper中了,在Looper中我们使用prepare方法创建Looper,然后使用loop方法开启循环,在循环中阻塞式地从MessageQueue中取Message。当取到Message后,执行了这句:

 msg.target.dispatchMessage(msg) 

  而在之前发送消息的分析中可以看到,Handler在enqueueMessage的时候,将msg.target指向了自己this,也就是说分发消息的过程是在Looper中调用的,但还是自己(发送消息的Handler)执行的。这样的话,不管在哪个线程中发送的消息,当执行这个消息时,其实就已经切换到了Handler中Looper所在的线程。
  现在的问题就到了Looper线程是哪个线程上了。这时候就又要拿出Handler的构造方法了。前面我们就走过场式地分析了一个Handler的构造方法,而Handler一共有7个构造方法。

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(boolean async) { 
    this(null, async); } public Handler(Callback callback, boolean async) { 
    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()); } } mLooper = Looper.myLooper(); if (mLooper == null) { 
    throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Looper looper, Callback callback, boolean async) { 
    mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } 

  从上面的7个构造方法可以看出,若是没有指定Looper,则使用实例化Handler时所在线程的Looper;若是指定,则使用指定的Looper。

小结

至此,Handler,Message,Looper,MessageQueue四者之间的关系已经清晰了。

  • Message作为消息载体,用于承载消息。
  • MessageQueue作为消息队列,用于存放消息。
  • Looper作为消息传送的动力,用于获取分发消息。
  • Handler作为消息处理中心,用于处理消息和发送消息。

那么再来看下它们之间的包含关系。

  1. 首先,Message作为消息主体,是独立的个体。
  2. 其次,MessageQueue用于存放消息,那么MessageQueue包含Message,对应多个Message。
  3. 而Looper,作为整个消息循环的动力,则包含了MessageQueue,并且一个Looper只有一个MessageQueue;另外在Looper的静态成员ThreadLocal中还存放了所有使用这套消息机制的线程的Looper。
  4. 最后是Handler,作为发送消息的主体,肯定需要知道向哪个线程发送消息,也就是需要知道线程对应的MessageQueue,即需要知道线程的Looper。所以,Handler包含Looper。

剩下的就是如何使用这套机制了。
  首先,在需要使用这套消息机制的线程中使用Looper的静态方法Looper.prepare()来初始化,然后使用Looper.loop()开启消息循环。这样,该线程就已经启用这套机制了,接下来直接使用Handler发送消息就行了。
Handler确定发送消息的目标线程的方式
  系统中可能存在多套消息机制,那么Handler又如何保证发送的消息确实是被发送到了目标线程呢?因为Handler内部包含Looper,所以发送的消息就是Looper所在的线程。而Looper又是在构造方法中获得的或者传递进来的,所以确保向目标线程发送消息有两种方式。

  1. 在目标线程中构造Handler,然后将Handler传递到想要发送消息的线程中。文章开头的小案例就是这种方法。
  2. 获得目标线程的Looper,在构造Handler时将Looper传递进去。这种方式不限定Handler实例化时所在的线程。

  到这里的时候,这套消息机制已经很明确了。那么问题来了,我们既然能够通过这套机制向UI线程发送消息,就说明UI线程肯定也启用了这套机制。而这里又要涉及android下应用程序启动的过程,这里只看一下ActivityThread类中的方法。

// ....\sdk\sources\android-28\android\app\ActivityThread.java public static void main(String[] args) { 
    ... Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 

  可以看到,在主函数中也是调用了这套机制的,但是使用的并不是Looper.prepare()而是Looper.prepareMainLooper(),在上面的Looper分析中我们知到,prepareMainLooper会将当前线程设置为主线程,也就是UI线程。因此作为开发者一般是被禁止使用这个方法开启Looper消息机制的。
  之所以我们可以使用Handler向UI线程发送消息,就是因为UI线程在启动的时候就已经开启这套消息机制了。
  由于同一个进程中的数据,在不同线程之间是可以共享的。这里Handler就可以在不同的线程中给目标线程的消息队列发送消息,然后目标线程处理消息。

注:这里没有分析Message和MessageQueue,因为他俩都是作为服务出现的
以上代码全部取自于api28(Android 9.0)中


补充

  虽然文章是分析四者的关系,但是既然已经从源码方面分析了Handler和Looper,那至少剩下的两者也得提一下吧。

Message:消息载体

  Message从名字就可以看出是消息的载体。

public final class Message implements Parcelable { 
    //用于区别消息的类型 public int what; //携带数据,空消息所携带的数据 public int arg1; public int arg2; //携带数据(进程间通信时只能携带系统定义的parcelable对象,自定义的不行) public Object obj; //Messenger进行进程间通讯时,用于实现双向通讯 public Messenger replyTo; //携带数据,可存放多条 Bundle data; //消息所携带的代码语句数据 Runnable callback; //消息的处理目标对象,用于处理消息 Handler target; //用于标记该Message是否在被使用  int flags; //存放时间,用于在MessageQueue中实现排序 long when; //用于实现单链表,以链表实现Message池 Message next; //链表头指针 private static Message sPool; private static int sPoolSize = 0;//池的当前大小 private static final int MAX_POOL_SIZE = 50;//池的最大容量 ... } 

  上面是Message的一些字段,是用来存储携带消息的。具体的已经在代码中标出了。并且这些字段都是public类型,可以直接通过对象访问。
  从一些字段上就可以看出,Message实际上使用了一个数据池来对Message对象进行回收和再利用。因此,虽然Message的构造方法是public的,但是系统建议我们使用obtain方法来获取对象,因为这样可以从对象池中获取Message,避免了多次分配对象。
  那么来看一下obtain是如何获得Message对象的。

public static Message obtain() { 
    synchronized (sPoolSync) { 
    if (sPool != null) { 
    Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } 

  当开始obtain方法时,首先开启了一个对象锁,这样就避免了可能会造成的混乱情况。然后当mPool不为空的时候,也就是对象池中有数据的时候,会取出单链表的表头Message,然后池内数量减一。唯一需要注意的可能就是在操作对象池的时候需要注意线程的同步。
  当然系统不会只提供一个obtain方法的,而是一系列的方法,而实际上都是调用这个空参数的方法,只是在这个基础上添加了一些参数数据而已。

public static Message obtain() public static Message obtain(Message orig) public static Message obtain(Handler h) public static Message obtain(Handler h, int what) public static Message obtain(Handler h, Runnable callback) public static Message obtain(Handler h, int what, Object obj) public static Message obtain(Handler h, int what, int arg1, int arg2) public static Message obtain(Handler h, int what,int arg1, int arg2, Object obj) 

  上面的一些方法也不用解释了,只是看参数基本上就明白意思了。
  既然Message是由对象池来产生的,那么也需要关注一下它的回收过程。

public void recycle() { 
    if (isInUse()) { 
    if (gCheckRecycle) { 
    throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); } 

  这个方法首先检查一下当前要回收的Message是否是正在使用的。然后调用recycleUnchecked方法进行回收。
  因此,当我们使用完一个Message时,也要通过message.recycle()来将已经不使用的Message回收。

void recycleUnchecked() { 
    // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. 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) { 
    next = sPool; sPool = this; sPoolSize++; } } } 

  recycleUnchecked回收之前先重置Message的状态,包括设置为未使用状态和清空所写携带的数据。可以看到,在将Message放回对象池的时候会首先判对象池的容量是否已经满了,只有未满的时候才会回收进对象池,否则将会丢弃等待GC的回收。
  Message就是一个简单的消息实体,没有什么复杂的地方,也就是其中使用的Message池值得关注一点。

MessageQueue:存放消息的队列

  其实MessageQueue内部还是比较复杂的,但是这里我们只作简单分析一下。由名字可以看出,MessageQueue是一个队列类型的数据结构,那么总体上肯定就是先进先出的访问顺序。而从源码中我们会发现,这个队列的实现方式和Message的对象池一样,也是由Message连接而成的单链表。还记得Message有个属性long when,里面存放的就是放入MessageQueue的时间,而MessageQueue会按照when的大小将Message队列进行排序。
  老规矩,从构造方法开始看起,但是MessageQueue的构造方法什么都没有,就是设置了一下是否允许退出队列的值并执行了一条native函数进行了初始化。

MessageQueue(boolean quitAllowed) { 
    mQuitAllowed = quitAllowed; mPtr = nativeInit(); } 

  作为构造函数,却只有一个native方法,这不禁让我们好奇这条native语句到底做了什么事,返回值mPtr又是什么呢?先从命名可以看出,学过C/C++的都知道,ptr明显的就是指针的常用命名习惯,这里到底是不是就要继续跟进源码来分析了。

  对应的native文件在这里:android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { 
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { 
    jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue); } 

  从这个native方法我们可以看出跟我们分析的差不多,首先该方法在native层创建了一个NativeMessageQueue对象,然后将对象地址指针返回到java层保存起来。但是到这里又会有疑惑了,为什么要在native层也创建一个消息队列呢?这个消息队列与java层的消息队列是同一个东西吗?
  但是我们是分析MessageQueue的,这些都算是具体实现了,我们当然也会分析,但是要先把MessageQueue走一遍,然后在走的过程再分析具体细节,不然跟踪源码那么多很容易最后迷失在源码中,所以这里就现在脑海中留下一个疑问,然后继续看MessageQueue。

  上面说到构造方法主要是在native层创建了一个NativeMessageQueue对象然后将指针保存在了MessageQueue的属性mPtr中。那么接下来,作为队列,重要的方法肯定是入队和出队的实现了,下面是入队方法的源码。

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) { 
    //若是该队列已经退出,则直接回收消息,只有在quit方法中才设置该属性为true 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; //p指向队列(单链表)的头部 Message p = mMessages;//这里的队列也是由Message构成的单链表 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插在对应的位置 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; } //这里在插入方法中可以看出,只要插入的Message不是异步的,那么 //needWake的值就会是mBlocked的值,而mBlocked的值会在出队方 //法next中,当线程阻塞的时候设为True。而这里当有非异步的Message入队时, //就会调用nativeWake方法将线程唤醒来处理消息 if (needWake) { 
    nativeWake(mPtr); } } return true; } 

  从上面的注释可以看出,enqueueMessage会按照时间从小到大的顺序将消息插入在相应的位置。因为MessageQueue中的队列是由Message实现的,也就是Message和它的属性next实现的单链表(在上面的Message的对象池中讲过),而单链表是只能按照从表头至表尾的顺序访问的,因此在MessageQueue中,入队是插在表尾,而出队是从表头取出的。
  接下来就是比较复杂的出队方法next的实现了,这个方法比较复杂代码也比较多,就一点点分析而不是直接全部贴出来了:

Message next() { 
    //ptr是native层对象指针,为0时表示MessageQueue已经结束 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); } ... } 

  可以看到,在next方法中调用了一个没有结束条件的for循环,那么肯定在循环内部通过break、return等结束循环,这里先不管这个结束条件。首先先看nativePollOnce这个方法,因为每次循环的时候都会调用该方法。

  对应的native文件在这里:android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { 
    //将java层传进来的mPtr再转换成对应的指针 NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } 

  可以看到,对应的native方法是直接调用了NativeMessageQueue的pollOnce方法。到这里,我们将java层的分析先放在一边,然后分析完native层的实现后再转回java层。
那么NativeMessageQueue到底是什么呢?刚才在nativeInit方法的时候就提到过这个并保留了一些疑问,这里将具体看一下源码以便将问题解决:

  对应的native文件在这里:android_os_MessageQueue.cpp

class NativeMessageQueue : public MessageQueue, public LooperCallback { 
    public: NativeMessageQueue(); virtual ~NativeMessageQueue(); virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj); void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis); void wake(); void setFileDescriptorEvents(int fd, int events); virtual int handleEvent(int fd, int events, void* data); private: JNIEnv* mPollEnv; jobject mPollObj; jthrowable mExceptionObj; }; 

  上面是NativeMessageQueue的定义,可以看到除了构造函数和析构函数外,还有一个处理异常信息的raiseException,一个处理正常信息的handleEvent,一个设置文件描述事件监听的setFileDescriptorEvents,一个使线程进入阻塞的pollOnce,一个唤醒线程的wake方法。
  那么同样的,从构造方法看一下:

NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { 
    mLooper = Looper::getForThread(); if (mLooper == NULL) { 
    mLooper = new Looper(false); Looper::setForThread(mLooper); } } 

  从构造方法可以看出,NativeMessageQueue的内部实现了一个Looper,并且这个Looper也是跟线程相关的。

  源码在这里:Looper.h

class Looper : public RefBase { 
    protected: virtual ~Looper(); public: //定义wait的返回值 enum { 
    POLL_WAKE = -1, POLL_CALLBACK = -2, POLL_TIMEOUT = -3, POLL_ERROR = -4, }; //定义一些wait事件 enum { 
    EVENT_INPUT = 1 << 0, EVENT_OUTPUT = 1 << 1, EVENT_ERROR = 1 << 2, EVENT_HANGUP = 1 << 3, EVENT_INVALID = 1 << 4, }; enum { 
    PREPARE_ALLOW_NON_CALLBACKS = 1<<0 }; Looper(bool allowNonCallbacks); bool getAllowNonCallbacks() const; //重点关注 int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData); inline int pollOnce(int timeoutMillis) { 
    return pollOnce(timeoutMillis, NULL, NULL, NULL); } //类似于pollOnce int pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData); inline int pollAll(int timeoutMillis) { 
    return pollAll(timeoutMillis, NULL, NULL, NULL); } //唤醒线程 void wake(); //添加文件描述 int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data); int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data); int removeFd(int fd); //下面一系列的发送文件有没有感觉很像Handler void sendMessage(const sp<MessageHandler>& handler, const Message& message); void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler, const Message& message); void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler, const Message& message); void removeMessages(const sp<MessageHandler>& handler); void removeMessages(const sp<MessageHandler>& handler, int what); bool isPolling() const; //在Handler中是通过ThreadLocal来实现的 static sp<Looper> prepare(int opts); static void setForThread(const sp<Looper>& looper); static sp<Looper> getForThread(); private: struct Request { 
    int fd; int ident; int events; int seq; sp<LooperCallback> callback; void* data; void initEventItem(struct epoll_event* eventItem) const; }; struct Response { 
    int events; Request request; }; struct MessageEnvelope { 
    MessageEnvelope() : uptime(0) { 
    } MessageEnvelope(nsecs_t u, const sp<MessageHandler> h, const Message& m) : uptime(u), handler(h), message(m) { 
    } nsecs_t uptime; sp<MessageHandler> handler; Message message; }; const bool mAllowNonCallbacks; // immutable //用来发出唤醒事件的文件描述符 int mWakeEventFd; // immutable ... }; 

  这里只看Looper的定义,具体实现就不看了,需要用到的下面将会再做分析,而其他的只能看自己的兴趣了。不过只是从定义上我们就可以看出,这里的Looper有点像java层的Handler。

  这里是实现Looper的实现的源码:Looper.cpp

  继续看NativeMessageQueue,除了构造方法外最重要的就是pollOnce方法了。

  源码在这里:android_os_MessageQueue.cpp

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { 
    mPollEnv = env; mPollObj = pollObj; mLooper->pollOnce(timeoutMillis); mPollObj = NULL; mPollEnv = NULL; if (mExceptionObj) { 
    env->Throw(mExceptionObj); env->DeleteLocalRef(mExceptionObj); mExceptionObj = NULL; } } 

  可以看到调用了Looper的pollOnce方法。在刚才的Looper的定义Looper.h中我们看到这句。

int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData); inline int pollOnce(int timeoutMillis) { 
    return pollOnce(timeoutMillis, NULL, NULL, NULL); } 

  所以最终调用的四个参数的pollOnce方法,那么继续追踪到Looper.cpp中的实现上:

  源码在这里:Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { 
    int result = 0; for (;;) { 
    while (mResponseIndex < mResponses.size()) { 
    const Response& response = mResponses.itemAt(mResponseIndex++); int ident = response.request.ident; if (ident >= 0) { 
    int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - returning signalled identifier %d: " "fd=%d, events=0x%x, data=%p", this, ident, fd, events, data); #endif if (outFd != NULL) *outFd = fd; if (outEvents != NULL) *outEvents = events; if (outData != NULL) *outData = data; return ident; } } if (result != 0) { 
    #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - returning result %d", this, result); #endif if (outFd != NULL) *outFd = 0; if (outEvents != NULL) *outEvents = 0; if (outData != NULL) *outData = NULL; return result; } result = pollInner(timeoutMillis); } } 

  可以看到最终调用了pollInner方法,接着往下追踪,这个方法比较长,这里在源码中分出了好几个段落点,后面会根据这些来进行分析:

  源码在这里:Looper.cpp

int Looper::pollInner(int timeoutMillis) { 
    //---------------------第一步---------------------------------------------------- //获得native下一个消息的时间,与java层超时时间相比,取最小的时间 // Adjust the timeout based on when the next message is due. if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { 
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime); if (messageTimeoutMillis >= 0 && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) { 
    timeoutMillis = messageTimeoutMillis; } } // Poll. int result = POLL_WAKE; //清空response信息,在线程被唤醒后,会将其他事件放入其中,response是个Vector集合 mResponses.clear(); mResponseIndex = 0; // We are about to idle. mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //-----------------------第二步------------------------------------ //这里是线程进入阻塞的主要方法 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // No longer idling. mPolling = false; // Acquire lock. mLock.lock(); // Rebuild epoll set if needed. if (mEpollRebuildRequired) { 
    mEpollRebuildRequired = false; rebuildEpollLocked(); goto Done; } //------------------------第三步------------------------------------ //线程阻塞出错结果 // Check for poll error. if (eventCount < 0) { 
    if (errno == EINTR) { 
    goto Done; } ALOGW("Poll failed with an unexpected error: %s", strerror(errno)); result = POLL_ERROR; goto Done; } //线程阻塞超时结果 // Check for poll timeout. if (eventCount == 0) { 
    #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - timeout", this); #endif result = POLL_TIMEOUT; goto Done; } // Handle all events. #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount); #endif //------------------------------------第四步------------------------------------- //线程阻塞通过监听事件唤醒,然后处理收到的事件 for (int i = 0; i < eventCount; i++) { 
    int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; //唤醒事件相关的文件描述符发生的事件 if (fd == mWakeEventFd) { 
    if (epollEvents & EPOLLIN) { 
    //清空文件事件 awoken(); } else { 
    ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } //若是其他事件,则将事件封装后放入mResponse中(Vector集合) } else { 
    ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { 
    int events = 0; if (epollEvents & EPOLLIN) events |= EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { 
    ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " "no longer registered.", epollEvents, fd); } } } //Done,什么都没做,在epoll_wait超时或者出错时跳转到这里 Done: ; //----------------------------------第五步--------------------------------------- mNextMessageUptime = LLONG_MAX; //处理Looper中Message事件,由Looper发送的,与java层的Message事件一致,也是按时间排序 while (mMessageEnvelopes.size() != 0) { 
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); //消息时间小于当前时间,也就是说消息已经可以处理了 if (messageEnvelope.uptime <= now) { 
    { 
    // obtain handler sp<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock(); handler->handleMessage(message); } // release handler mLock.lock(); mSendingMessage = false; result = POLL_CALLBACK; //消息还未到可以处理的时候 } else { 
    mNextMessageUptime = messageEnvelope.uptime; break; } } // Release lock. mLock.unlock(); //处理response事件,在上面线程唤醒的时候添加进去的 for (size_t i = 0; i < mResponses.size(); i++) { 
    Response& response = mResponses.editItemAt(i); if (response.request.ident == POLL_CALLBACK) { 
    int fd = response.request.fd; int events = response.events; void* data = response.request.data; int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { 
    removeFd(fd, response.request.seq); } response.request.callback.clear(); result = POLL_CALLBACK; } } return result; } 

  这个pollInner方法稍微有点多,那么这里将一点点分析。
  先看第一点,比较的是timeoutMillis 和 mNextMessageUptime,前者是我们从java层一路传递进来的超时时间,后者是native层的Message队列的下一个事件的时间。mNextMessageUptime在后面设置的,首先若是native层的Message队列中没有了待处理的消息,则设其值为LLONG_MAX。若是有待处理的消息,也就是说消息还未到处理的时间,则设为待处理的消息的时间。
  然后呢,根据这个值与当前时间进行计算,算出是待处理消息需要的时间久还是timeoutMillis需要的时间久,然后取最小的,从这里也可以看出,当native层有消息时也会提前唤醒线程。
  继续跟着源码往下走就到了第二点,首先第二点调用了epoll_wait方法。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

  简单点说或者说根据本文意图来解释的话就是:epoll是linux下的方法,会阻塞线程直到有事件通知。
  那么这里就要简单说明一下epoll了,首先epoll会监听一些文件描述符,然后给这些文件描述添加一些事件,这样当线程访问的时候就会阻塞,然后待这些文件描述符发生添加的事件的时候就会唤醒。epoll主要涉及到如下几个方法:

 int epoll_create ( int size );//创建一个epoll监听 //对事件进行操作 int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event ); epfd:epoll描述符,由create创建的 op:具体操作 EPOLL_CTL_ADD:往事件表中注册fd上的事件 EPOLL_CTL_MOD:修改fd上的注册事件 EPOLL_CTL_DEL:删除fd上的注册事件 fd:要注册观察的文件描述符,可以是具体的文件,socket,管道等 event:具体要检测的事件 EPOLLIN:有数据流入,即文件非空 EPOLLOUT:有数据可以写入,即文件非满 //阻塞等待事件发生 int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout ); epfd:create创建的epoll描述符 events:存放事件的数组 maxevents:最大可存放的事件个数,数组events的大小 timeout:超时时间,为0时立即返回,为-1时一直阻塞,否则等待timeout时间后返回 

  可以看到,在第二步时线程可能会根据timeout的值而进行相应时间的阻塞,这里也是java层的next方法阻塞的实现。当调用epoll_wait方法时,线程就会阻塞在这里,阻塞timeoutMillis时间,除非发生注册监听的事件。
  接着走到第三步,第三步是对eventCount的判断,而eventCount又是epoll_wait的返回值,所以这里要说明一下它的返回值的区别。首先,epoll_wait有三个可唤醒的条件,超时,错误,事件。当发生错误时,返回值为-1,超时返回值为0,发生事件返回值为事件的个数。
  可以看到,当返回值为0或-1的时候,只是设置了result的值POLL_TIMEOUT和POLL_ERROR,然后goto跳转到Done,而Done什么都没做。所以发生超时和错误时是不进行操作只是修改result值而已。
  那么当有事件发生的结果呢?这里要继续看第四步了,第四步循环所有事件,然后判断发生事件的是不是专门用来唤醒线程的mWakeEventFd文件描述符,不是的话则根据对应的事件封装成Message然后通过pushResponse方法放在mResponse(Vector集合)中;若是的话则通过 awoken()方法清空mWakeEventFd中的数据。
这里涉及到两个方法,简单的看一下吧:

  源码在这里:Looper.cpp

void Looper::pushResponse(int events, const Request& request) { 
    Response response; response.events = events; response.request = request; mResponses.push(response); } //该方法从mWakeEventFd中read()读取了一个uint64_t的数据,其实就是一个正整数 //之所以只读一个数字,是因为在唤醒的方法中,只写入了一个数据,因此读取一个数据就会 //使其再次陷入空状态 void Looper::awoken() { 
    #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ awoken", this); #endif uint64_t counter; TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t))); } 

  第五步是处理事件,包括两部分的事件:Looper中的事件和线程wait时返回的事件。另外,若是Looper中有可以处理的事件或者wait中有事件,会设置result为POLL_CALLBACK。
  到这里,pollInner已经分析完了,分为五步:1是计算wait时间,2是进行wait等待,3是对wait返回值的判断,4也是对wait返回值的判断但是是对事件触发返回的判断,5是处理Looper和wait的事件。

  上面我们分析到了java层的next方法,该方法在循环的时候调用了nativePollOnce,然后我们又进入native层分析了一下,总算是nativePollOnce方法弄明白了:陷入阻塞,等待被唤醒。总结起来就是这9个字。接下来继续看next方法,毕竟这才是我们整个分析的主线:

Message next() { 
    ... int nextPollTimeoutMillis = 0; for (;;) { 
    ... //根据我们上面的分析,nextPollTimeMillis为0则不阻塞,也就是第一次循环不阻塞 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { 
    final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //这里判断msg不为空但是target为空,而我们enqueueMessage的时候特意设置了target的 //所以这里的msg不是我们设置而是系统在初始化的时候设置的屏障,这里不再详解 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()); } //这里是正常情况下的msg if (msg != null) { 
    //未达到处理时间的,将会计算需要等待的时间,不超过整形的最大值 if (now < msg.when) { 
    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 { 
    nextPollTimeoutMillis = -1; } //上面分别对消息队列进行判断然后修改nextPollTimeoutMillis,而之前的分析可以看出这个值就是线程 //需要阻塞的时长,有未达到处理时间的消息则阻塞对应时间,没有消息则一直阻塞直到被唤醒 ... } ... } } 

  next也讲完了,还剩下一点的就是在enqueueMessage的时候,调用了唤醒线程的方法nativeWake(mPtr),由于前面还没讲到线程是如何在next中阻塞的,所以一直没分析这个方法,现在我们分析完next后知道了线程是在native层通过epoll_wait阻塞的,那么唤醒的方式肯定是对相应的文件描述符的操作。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { 
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake(); } void NativeMessageQueue::wake() { 
    mLooper->wake(); } void Looper::wake() { 
    #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ wake", this); #endif uint64_t inc = 1; ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))); if (nWrite != sizeof(uint64_t)) { 
    if (errno != EAGAIN) { 
    LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s", mWakeEventFd, strerror(errno)); } } } 

  从上面的nativeWake一路调用到MessageQueue中的wake然后到Looper中的wake。而在Looper中的wake,我们看到,使用write向mWakeEventFd中写入了一个uint64_t 的1,与我们前面分析pollInner中的对应。在pollInner中,若是由于对应的文件描述符发生的事件,调用awoke清空文件,而awoke刚好就是读取一个uint64_t的值。

补充总结

  MessageQueue的主要方法next和enqueueMessage,其中next方法在队列为空或者消息暂未达到处理时间的时候,线程会阻塞,这里的阻塞是通过native层的epoll方式进行的阻塞。enqueueMessage方法在添加Message的同时也会判断是否需要唤醒线程,若是需要则在native层通过对文件的写入数据而触发epoll设置的事件监听,因此唤醒线程。

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复