Handler

前言

之所以要写Handler,是因为最近面试Handler被虐,所以回来看了一下,并且发现知识链很多都是断裂的,所以打算从今天开始,有时间就整理博客。这应该算是我干Android这几年的第一篇博客,可能有不对的地方,望小伙伴们能指点一二!

 

Handler是什么

Handler就是线程间通讯的一个工具,可以让你在不同线程间更加简单的传递数据。

 

Handler是怎么工作的

它主要涉及到了四个类:Handler,Message,Looper,MessageQueue

分别都是做什么的呢

Message作为消息主体,由Handler把这个消息Message传送到用于存放消息的消息队列MessageQueue中,然后再由Looper这个循环器不断的轮训查看消息队列MessageQueue中是否有新的消息,如果有在发送回Handler,由Handler分发这个消息Message。大概的工作流程就是这样的。

 

Handler通讯机制的简单使用

发消息

/**
 * 延时执行该Runnable
 * 此处Runnable是在主线程中
 */
Runnable run = new Runnable() {
    @Override
    public void run() {
        int a = 1 + 2 + 3;
        Log.v("myHandler"," a = "+a);
    }
};
handler.postDelayed(run,2000);//延迟两秒,注意:该方法即使Activity销毁仍然执行
handler.postAtTime(run,2000);//时间参数计算方法是从手机开机开始计算,之后的毫秒值执行接口。比如从开机到现在是10000毫秒,你想在3000毫秒后执行run,就填入10000 + 3000。当前开机时间获得方法是SystemClock.uptimeMillis()。
 
/**
 * 消息组装
 */
Message msg = Message.obtain();
msg.what = 12;
msg.arg1 = 13;
msg.arg2 = 14;
handler.dispatchMessage(msg);//是在当前线程发送消息(如果当前是在子线程中,Handler接收消息不要更新ui否则异常,因为Android不能再子线程中更新UI)
handler.sendMessage(msg);//进入UI线程发送消息
 
handler.sendEmptyMessage(0);//进入UI线程发送标记
 
/**
 * 定时任务
 * 其实sendMessageDelayed调用的也是sendMessageAtTime
 */
handler.sendMessageAtTime(msg, SystemClock.uptimeMillis()+3000);//计时,SystemClock.uptimeMillis()获取当前时间
handler.sendMessageDelayed(msg,2000);//上面的是绝对时间,下面的是相对时间,即消息送走时间

收消息(主线程)

 

Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 12:
                Log.v("myHandler"," msg.arg1 = "+msg.arg1);
                Log.v("myHandler"," msg.arg2 = "+msg.arg2);
                break;
            default:
        }
    }
};

收消息(子线程)

 

new Thread() {
    @Override
    public void run() {
            Looper.prepare();
            handler = new Handler(Looper.myLooper(), new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    Log.v("myHandler", "isMainThread = " + isMainThread());
                    Looper.myLooper().quitSafely();//退出looper
                    return true;
                }
            });
            Looper.loop();//死循环,此处之后的代码在Looper退出之前不会执行(并非永远不执行)
            Log.v("myHandler", "isMainThread = " + isMainThread());//这一行代码只有当第9行执行完之后才会执行
    }
}.start();

这里需要做一个说明:在子线程中创建Handler时,必须使用带有Looper参数的Handler构造器,Handler有三个带有Looper构造器的重载方法,子线程默认Looper是没有初始化的,所以需要调用Looper.prepare();初始化,并且还要调用Looper.loop()开始循环,否则这里的Handler是无法接收到消息的!而在接收消息后又调用了Looper.myLooper().quitSafely();退出循环,这个时候第14行的日志才会被打印,因为在循环结束之前子线程是被阻塞的,所以没有继续往下执行,因此上方代码片段中,在没有接收到消息之前不会打印任何日志,接收到消息之后第8行日志被打印,接着loop循环结束,然后子线程继续向下执行,打印第14行日志,然后子线程生命结束

 

----------------------------------------------------------------------------------------------------------------

下面开始进入正题,分析Handler!既然Handler消息通讯主要牵扯到四个类,那接下来就一个一个来说一下!

 

1:Handler

首先说一下Handler的构造器,一共是有七个重载的构造方法

 

final Looper mLooper;
final MessageQueue mQueue;
public Handler() {
    this(null, false);
}
public Handler(@Nullable Callback callback) {
    this(callback, false);
}
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}
public Handler(boolean async) {
    this(null, async);
}
public Handler(@Nullable 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(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

通过源码可以看到,前5个构造方法最终调用的是第6个和第7个构造方法,那也就是说只有第6个和第7个在实际构造Handler, 给成员变量mLooper和mQueue做了显式初始化初始化,也就是说一个Handler只对应一个Looper,looper中有消息队列MessageQueue,这里从Looper中获取了消息队列MessageQueue赋值到本类的成员变量上,此处是Handler与Looper和MessageQueue产生关系的地方。

前面说Handler是怎么工作的地方说过,Handler把消息Message传送到用于存放消息的消息队列MessageQueue中,上方源码中的构造器就让Handler与MessageQueue产生关系,然后能对MessageQueue做一些操作,而这个操作就是单纯的往其内部放一些消息Message。

前面说过的使用例子中我们能看到Handler有好多方法能发送消息,例子中也并不是全部的发送消息的方法,有兴趣的可以翻看下源码或api。Handler发送消息无论你调用的是哪个发送消息的方法,最终都是由enqueueMessage一个方法来处理的,我们来看一下这个方法是怎么处理的

 

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
    //给msg做了一个标记,标记这个消息的收取方
    msg.target = this;
    //告诉msg我们是在哪个线程上工作的
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    //设置消息是否异步,让msg不受制于同步屏障所影响
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //开始往消息队列中塞入消息
    return queue.enqueueMessage(msg, uptimeMillis);
}

上方源码中每一行都加了注释,那我们来说一下这个方法的参数,第一个参数就是在本类Handler的构造时所获取到的消息队列,第二个参数是一个时间,这个时间是给MessageQueue使用的,如果我们发送消息时调用的方法是有时间参数的,那这里就是那个时间,如果调用的是没有时间参数的方法发送消息,这里会默认一个0,有什么用呢,主要就是消息队列排序用的,为什么要排序呢?在Handler的头注释中有这么一段话

There are two main uses for a Handler: 
to schedule messages and runnables to be executed at some point in the future; 
to enqueue an action to be performed on a different thread than your own.

说Handler有两个主要用途:

  1. 计划在未来某个时间点执行消息和可运行文件;
  2. 将要在不同线程上执行的操作排队

也就是说Handler可以让我们在不同线程中更加方便(简单)的即时或延时的发送消息,后边的消息队列中会讲到这个时间的具体使用方法

上方是Handler发送消息,那是怎么接受消息的呢?我们来看下源码

 

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(@NonNull Message msg) {
}

注释中有写道,我们可以实现接口Callback或者重写Handler的handleMessage方法来接收消息,那这两个地方的消息又是哪里来的呢?是由下方源码中的方法来分发的,如果这是一个系统消息,那调用的就是下方的第3行,否则就进入else中,上方使用例子中在主线程中接收消息就是下方源码中第10行的handleMessage方法的重写,使用例子中在子线程中构建Handler,我们实现了Callback接口,那里实现的方法handleMessage就是下方第6行代码执行的回调

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

上面就是Handler发送消息和接收消息的讲解,既然都是围绕着消息运转的,那消息很重要,接下来我们来看一下是什么样的消息

2:Message

Message:消息的载体

我们先来看一下Message的头注释说了什么东西

/**
 *
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
public final class Message implements Parcelable {

大致的意思是:Message是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message中包含了两个额外的 int字段和一个object字段,这样在大部分情况下,使用者就不需要再做内存分配工作了。虽然Message的构造函数是public的,但是最好是使用Message.obtain( )或Handler.obtainMessage( )函数来获取Message对象在message.get()方法。关键的部分来了,它最后提到了一个池的概念!什么意思呢?主要是说Message它可以回收再利用,在他的成员变量上有这样两个字段

    //池的当前大小
    private static int sPoolSize = 0;
    //池的最大容量
    private static final int MAX_POOL_SIZE = 50;

这两个字段是对池的一个最大容量限定所用,也就是说这个Message不光是一个消息,我们也可以把它当做一个消息池来看待,但是它的最大容量是50个,但其实我们在正常使用时并不是很容易就达到这个最大容量,分析源码的时候我们就能大致明白为什么不那么容易达到了! 这个Message还实现了一个Parcelable的接口,为的是进程间通信,Handler消息机制不是说好的线程间通信么?在后边我们讲消息队列的时候会在来说一下这里提到的问题。下面我们继续来分析Message

首先来看一下这个消息的载体他能够承载哪些消息

    //用于区别消息的类型
    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;

上方是能够承载的消息,在每一个上面都加了注释,有兴趣的朋友可以瞄一下,接下来我们看构造器:

/** 
* Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}

从源码上来看,构造器啥也没干,Java中如果不写构造方法的时候,不是会有一个空的默认构造方法么?但是那个方法没有注释啊,这里主要是为了咱们来看这个注释:“想要获取一个Message的首选方法应该是Message.obtain()”,还记得头注释么,也这么说过,那我们来看一下这个方法干了什么

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

从这个源码上看,这个方法首先使用synchronized关键字开启了一个锁,加锁就是为了防止脏数据的产生所产生的一个产物,Handler通信机制中,我们程序员在使用时,都是子线程对UI线程的消息传递,怎么可能会出现脏数据呢?有兴趣的朋友可以在源码上搜索一个下这个方法都在哪里调用过,就知道了,并不是单单只有Handler在使用Message这个对象。然后在这个方法里我们看到如果sPool是空的它会给你new一个Message对象,否则的话它把是否在被使用的标记m.flags改成了0,m.next对象给设置成了null,然后把sPool赋值的m的这个Message对象给你用了,在这里采用了一个回收再利用的方式,避免有能用的而你在去创建一个所导致的内存浪费。

消息销毁回收的源码在下面,只是不需要我们主动调用,但为了了解Message我们还是来看一下源码

void recycleUnchecked() {
    // 当消息保留在回收的对象池中时,将其标记为正在使用.
    // 清除所有其他细节.
    flags = FLAG_IN_USE;//标记该Message是否在被使用 
    what = 0; //用于区别消息的类型
    arg1 = 0;//携带数据,空消息所携带的数据
    arg2 = 0;//携带数据,空消息所携带的数据
    obj = null;//进程间通信时只能携带系统定义的parcelable对象
    replyTo = null;//Messenger进行进程间通讯时,用于实现双向通讯
    sendingUid = UID_NONE;//发送消息用的uid
    workSourceUid = UID_NONE;//此消息排队的uid
    when = 0;//存放时间,用于在MessageQueue中实现排序
    target = null;//消息的处理目标对象,用于处理消息
    callback = null;//消息所携带的代码语句数据
    data = null;//携带数据,可存放多条
    synchronized (sPoolSync) {
        //sPoolSize:池的当前大小
        //MAX_POOL_SIZE:池的最大容量
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

我就把上方源码所有的字段全都加上了注释以便理解。recycleUnchecked方法回收之前先重置Message的状态,包括设置为未使用状态和清空所写携带的数据。可以看到,在将Message放回对象池的时候会首先判对象池的容量是否已经满了,只有未满的时候才会回收进对象池,对这个当前池的大小在这个方法中做了一次+1的操作,否则将会丢弃等待GC的回收。那-1的呢,还记得上面的那个Message.obtain()方法吗?那里的源码第8行做了池的-1操作,

因此,虽然Message的构造方法是public的,但是系统建议我们使用obtain方法来获取对象,因为这样可以从对象池中获取Message,避免了多次分配对象。

message类中有一个方法要特意讲一下,这个方法在Handler中和在MessageQueue中有用到

public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}

如果是异步消息,这里则会返回true。因为在消息的入列和出列的时候都有使用这个方法,所以这里说明一下

3:Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。

还是先看构造器

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

这里可以看到,构造中就干了俩事,一个是给成员变量mQueue做了一个显式初始化,而真假值的参数是来控制消息队列是否可退出的,true就是可退出的。另一个就是获取了一个当前工作线程的引用,因为Looper对外提供了几个方法中使用到了线程,所以这里获取了一下当前工作线程的引用,但是问题来了,这个构造器是私有的,我们外部不能使用,那来看一下他的头注释是咋说的,其中有这么一段话

Class used to run a message loop for a thread.  Threads by default do not have a message loop associated with them; to create one, call
 
{@link #prepare} in the thread that is to run the loop, and then
{@link #loop} to have it process messages until the loop is stopped.

说你想使用Looper必须要调用prepare和loop两个方法才行,这两个方法是干什么的呢,prepare就是初始化Looper用的,loop方法就是真正的开启一个循环的方法,之所以Looper是循环器,就是因为loop这个方法,在Looper中最重要的方法有如下三个

prepare:初始化

loop:开始循环

quit(不安全-API1)、quitSafely(安全-推荐使用-API18):结束循环

我们先来说下第一个prepare

上源码

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}

这个静态的初始化方法prepare也是一个重载,只不过方法的访问权限不同而已。源码中我们可以看到,成员变量sThreadLocal中只让你有一个Looper,在第6行判断的,你给多了它就给你抛异常,但你要是没有Looper,它就给你创建一个。其实在Looper的类中还有另外一处可以初始化的地方在头注释中没有提到

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

源码中我们可以看到,这里第8行调用了一次prepare(false);方法,但是这里给的是一个false,意味着消息队列不能退出,源码中我们能看到,Google并不建议我们使用该方法,这个方法虽然是public的,但是你在应用开发过程中要是使用了,人家就抛一个异常给你,因为主线程的Looper已经初始化过了!

loop循环

这个方法有点长,所以我就挑点咱们平时发消息使用的那点主要的讲一下哈

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;
        ...
        for (;;) {
            Message msg = queue.next(); // 可能会阻塞
            if (msg == null) {
                // 没有消息表明消息队列正在退出.
                return;
            }
            ...
            msg.recycleUnchecked();
        }
        ...

首先来看注释,“在此线程中运行消息队列,但是别忘了调用quit()结束loop循环。”接下来我们看源码,在源码的第6行是使用了方法myLooper()来获取一个当前对象,为什么不用this呢?从抛出的异常中可以看出来,是为了防止你没调用Looper.prepare()方法去初始化looper就直接调用了loop()方法。在然后是获取成员变量中的消息队列mQueue赋值到局部变量中queue上,局部变量和成员变量的不同呢?看这里!在然后就是进入无限循环中了,为什么这里使用for (;;)而不是while(true)呢?因为for (;;)指令少,不占用寄存器(整理中,现在跳转的是百度?),而且没有判断跳转,所以同为死循环,但是for (;;) 更优一点。我记忆当中早期Android这里使用的就是while(true)。for循环中就是处理消息队列中的内容Message了。最后消息使用完毕,销毁!但是否真的销毁了这个对象,前面说Message时有讲过

但是这里是一个死循环,如果使用空参数Handler那就是默认使用主线程(UI线程)的Looper,那这里明明是死循环,为什么却不会anr异常呢?并且主线程的死循环一直运行是不是特别消耗CPU资源呢? 导致功耗很高,其实不然,上方代码片段的13行的注释中有写道,这里可能是阻塞的,要不然没有消息了msg为null在往下执行就是return返回跳出循环了,意味着你的Activity线程执行完了,但是Android的所有的UI操作都是通过Handler来发送消息的,那这里loop中的循环都结束了还怎么做UI操作呢!阻塞这个方法是调用的消息队列中的方法,下面我们讲MessageQueue的时候在来说说这个事!

阻塞这里就涉及到了Linux pipe/epoll机制,简单说就是在主线程的消息队列MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,所以不会出现前面说的问题。

 

quit和quitSafely退出循环

如果你是在一个子线程中开启了一个looper,并调用了loop()方法,那当前子线程相当于阻塞于此了,你不停掉loop的话,那后面的具体代码将不会被执行,举个例子

new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        Looper.loop();
        Log.v("tag", "msg");
}}.start();

上方代码中日志将永远不会被打印,因为Looper中的loop()方法中的循环没有被停掉,所以线程执行到第5行就被阻塞住了,如果想要日志打印那就必须调用Looper.myLooper().quit();停掉循环,线程才会继续往下面执行,打印日志。

 

那quit是怎么停止的loop()呢?按照惯例,上源码

    /**
     * Quits the looper.
     * <p>
     * Causes the {@link #loop} method to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @see #quitSafely
     */
    public void quit() {
        mQueue.quit(false);
    }

注释中说“调用loop()方法终止,将不再处理消息队列中的消息。在请求looper退出后,向队列发送消息的任何尝试都将失败。使用此方法可能不安全,因为某些消息可能无法传递在循环终止之前,这里指的是延时消息。建议使用quitsaffe()来确保一切未了结的工作都要有条不紊地完成。"那我们来看一下quitsaffe()方法都干了什么?

 public void quitSafely() {
     mQueue.quit(true);
 }

单从源码上看,就一个真假值不同,别的啥也没干,那里面这个真假值到底起了什么作用呢?接下来我们来分析下MessageQueue

4:MessageQueue

从前面讲过的东西,我自己挖了几个坑

  1. 消息队列是怎么退出的,构造器的参数的真假值有什么具体作用
  2. 线程的阻塞也是在消息队列中,那它是怎么阻塞的
  3. 消息队列为什么要排序
  4. 还有一个进程间通信

接下来按照前面讲解的流程来讲解,在其中夹杂刚刚的这几个问题

上构造器

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

先来看一下这个真假值得参数,前面讲过,这个真假值是控制此消息队列是否可退出的,通过代码搜索,在本类中的成员变量mQuitAllowed只有在一个地方有使用过

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }...
}

从上方的源码片段中,我们可以看到,如果是主线程在使用此消息队列的情况下是不让你退出的。还记得最前面说的重要性么?那里我就说过,一个APP的生命从生到死,都跟Handel有关,而Handler对应一个looper,一个Looper对应一个MessageQueue,所以主线程在使用Handler的时候是不让你退出的,否则你的ui线程讲无法在继续使用!

接下来说一下构造器中的第3行,nativeInit是一个Native方法,从名字来看,是一个初始化的方法

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层创建了一个NativeMessageQueue对象,我们可把它理解成NativeMessageQueue是Java层MessageQueue在Native层的代表。这里把这个代表对象地址返回到java层保存了起来。那NativeMessageQueue的构造函数又干了什么呢?

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

这里我们看到,NativeMessageQueue在它的构造函数中获取了一个Looper,Native的Looper是Native世界中参与消息循环的一位重要角色。虽然它的类名和Java层的Looper类一样,但两者其实并无任何关系!源码就不贴了,有兴趣的朋友看这里:http://androidxref.com/9.0.0_r3/xref/system/core/include/utils/Looper.h

private native static long nativeInit();//初始化Native层代表
private native static void nativeDestroy(long ptr);//销毁Native层代表
private native void nativePollOnce(long ptr, int timeoutMillis); //线程阻塞
private native static void nativeWake(long ptr);//唤醒线程
private native static boolean nativeIsPolling(long ptr);//工作线程是否休眠
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);//原始文件描述

主要我们讲的是Java层面的,并且在Java层的MessageQueue中还并不是只有这么一个Native方法,所以Native我们就不在继续深刨了,主要在Java层我们来聊一聊

MessageQueue消息队列,既然是消息队列,那我们先来说说主要的入列和出列这两个方法。

入列方法是在Handler的enqueueMessage方法中调用的MessageQueue中的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;
        // p指向队列的头部
        Message p = mMessages;
        boolean needWake;
        // 如果p为空,表明消息队列中没有消息,那么msg将是第一个消息
        // 如果when = 0或者when < p.when,说明是即时消息,把该消息放入队首,队首是最先出队的
        // needWake需要根据mBlocked的情况考虑是否触发
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 判断唤醒条件,当前消息队列头部消息是屏障消息,并且当前插入的消息为异步消息,
            // 并且当前消息队列处于无消息可处理的状态
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            // 如果p不为空,表明消息队列中还有剩余消息,需要将新的msg按照从小到大的时间顺序放入到对应的位置
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    // 因为消息队列之前还有剩余消息,所以这里不用调用nativeWake去唤醒线程
                    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实现的单链表,而单链表只能按照从表头至表尾的顺序访问。那为什么要按照时间排序呢?因为我们还有延时消息。接下来我们看一下出列的方法大致就能明白一二了

出列方法是在Looper的死循环中调用的next()方法,源码如下

Message next() {
    // Native层代表NativeMessageQueue的对象指针
    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();
        }
        // 调用Native方法,参数1为NativeMessageQueue的指针,参数2为0则不阻塞,也就是第一次循环不阻塞,否则线程等待,开始阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // 尝试检索下一个message。如果找到则返回
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            // mMessages用来存储消息,这里从其中取一个消息进行处理
            Message msg = mMessages;
            // 如果msg不为空但是target为空,我们的target肯定不会为null
            // 还记得Handler中的enqueueMessage()方法么,那里有设置
            // 所以这里的msg不是我们设置而是系统在初始化的时候设置的障碍
            if (msg != null && msg.target == null) {
                // 被障碍物挡住了。在队列中查找下一个异步消息
                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;// 设置一个非0的值,准备阻塞线程
            }
            // 处理完所有挂起的消息后,立即处理退出消息
            if (mQuitting) {
                dispose();
                return null;
            }
            // 获取空闲时处理任务的handler 用于发现线程何时阻塞等待更多消息的回调接口。
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 没有要运行的空闲处理程序。循环等待更多。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        // 运行空闲处理程序.
        // 我们只在第一次迭代时到达这个代码块
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            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);
                }
            }
        }
        // 重置空闲的handler个数,因为不需要重复执行
        pendingIdleHandlerCount = 0;
        //当执行完空闲的handler的时候,新的native消息可能会进入,所以唤醒Native消息机制层.
        nextPollTimeoutMillis = 0;
    }
}

上方就是消息出列的方法,大部分我都加了注释,跟message相关的,就是根据时间来拿消息,然后丢出去。因为在入列的时候我们做了时间的排序,所以这里就直接根据最近的那个时间来拿消息即可。

这个入列和出列的两个方法中,直接涉及到了两个Native方法,一个是在入列方法第57行nativeWake(mPtr),另一个是在出列方法中的16行nativePollOnce(ptr, nextPollTimeoutMillis),他们俩一个是唤醒线程的,一个是阻塞线程的。

这里涉及到了一个Native的方法nativeWake唤醒线程,我们来看一下源码!

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    // 取出NativeMessageQueue对象
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    // 调用它的wake函数
    nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
    // 调用了Looper的wake()函数
    mLooper->wake();
}
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    // 向管道的写端写入一个8字节的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));
        }
    }
}

因此,管道的读端就会因为有数据可读而从等待状态中醒来

在上方消息出列的方法中出现了一个IdleHandler。看着跟我们说的消息的出列入列没啥太大关系的东西,这是啥?从注释里面看,跟它相关的好像都有写“空闲”。它是当线程正在等待更多消息时,回调的一个接口,MessageQueue对外提供addIdleHandler(@NonNull IdleHandler handler) 方法添加空闲时任务,它到底干了啥呢?通过代码搜索看到好几处都调用过这个方法,其中有一个咱么都熟悉的ActivityThread,反正我就只有这里熟悉一些,那我就只说这了,是怎么掉的这个方法呢?

final GcIdler mGcIdler = new GcIdler();
void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

而上面这个方法会在什么时候被调用呢?在搜索,是在ActivityThread的内部类H中,在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中,那GvIdler是啥?也是一个内部类,不长,我贴出来

//GC任务
   final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            //执行后,就直接删除
            return false;
        }
    }
    // 判断是否需要执行垃圾回收。
    void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //获取上次GC的时间
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }

就是获取上次GC的时间,判断是否需要GC操作。如果需要则进行GC操作。

剩下那几个我没细看,有兴趣呢可以自己瞅瞅,这都已经偏离主话题了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值