Handler作用和原理讲解

在Android开发中,无论是刚入门的新人还是工作多年的老司机,都经常用到Handler,用的最多的场景就是用子线程来做耗时请求(如:网络、数据库等),然后切换到主线程来处理请求结果。没错,Handler的主要作用就是实现线程间通信的。

Handler的一些基础用法这里就不赘述了。接下来我们一起来探究下Handler是如何实现线程间通信的。

1.Handler中几个重要的类。

首先我们回顾下项目中我们是如何使用Handler的:

Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

Message msg = handler.obtainMessage();
Message.obtain(handler, new Runnable() {
    @Override
    public void run() {

    }
});
msg.what = 1;
msg.obj = "xxx";
handler.sendMessage(handler.obtainMessage());

以上代码中我们得出Handler消息机制中几个重要的类:

(1)Message:需要传递的消息,看源码我们会发现Message中可以携带参数int whatObject objint arg1int arg2Bundle dataRunnable callback

(2)Looper:Looper 是一个消息循环器,它可以帮助 Handler 在一个线程中处理消息。每个线程只能有一个 Looper,Looper 负责创建该线程的消息队列并开启消息循环。当 Looper 的 loop() 方法被调用时,它会一直循环检查消息队列中是否有需要处理的消息,如果有,就将消息发送给对应的 Handler 进行处理。当消息队列中没有消息时,Looper 就会进入休眠状态,直到有新的消息进入队列。

看下Looper中的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.");
    }

    ......

    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}
/**
 * Poll and deliver single message, return true if the outer loop should continue.
 */
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ......
    try {
        msg.target.dispatchMessage(msg);
        ......
    }

    ......

    return true;
}

(3)Handler:可以创建消息并将其投递到消息队列中。每个Handler都关联着一个消息队列,当有消息投递到消息队列时,Looper.loop就会取出消息并交给Handler来处理。同时每个Handler也会关联着一个线程,这个线程通常是Handler创建时所在的线程。

看下Handler中的几个方法的调用关系:

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

当我们调用handler.sendMessage最终会调用MessageQueue的enqueueMessage方法。那接下来我们再看另外一个重要的类MessageQueue。

(4)MessageQueue:消息队列是一个先进先出的队列,用来存储需要处理的消息。Handler 的主要作用就是将消息投递到消息队列中,等待被处理。通过消息队列,我们可以在多个线程之间传递消息,从而实现线程间通信的功能。

看下MessageQueue中的enqueueMessage方法:

public final class MessageQueue {

    @UnsupportedAppUsage
    Message mMessages;
   ......

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

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

先看msg.next = p,Message有个next属性,它指向的也是Message对象,由此我们可以得出这里队列并不是使用List来记录的,而是采用的单链表的方式来记录的。

我们知道java中存储数据是放在堆中的,如果用List来记录数据需要在堆中开辟连续的空间,而内存空间是有限的,如果List越来越大,会导致内存中很难开辟那么多连续的空间从而会导致OOM,而采用单链表的好处就是可以利用内存中不连续的空间。

接下来我就详细分析下enqueueMessage方法的实现。

假设每次enqueue都进入29行的if里面。

那么第一次enqueue后链表结构如下:

第一次enqueueMessage后的链表结构

假设第二次enqueue后进入的是34行的else里面,默认情况下下一个Message的when肯定比上一个的要大,因为看上面Handler中的源码,when参数的值是SystemClock.uptimeMillis() + delayMillis。所以我们暂时不考虑when < p.when的情况。那么执行完后的链表结构如下:

第二次enqueueMessage后的链表结构

第三次enqueueMessage后的链表结构如下:

第三次enqueueMessage后的链表结构

我们会发现每次发送消息,都是将新的消息放到链表的尾部,然后全局的mMessage对象指向链表的头部,这样取消息的时候就可以从头部开始一步步的取下去,关于怎么取消息,具体可以看下MessageQueue中的next方法,比较简单,这里就不赘述了。

上面流程有不懂的地方其实可以在纸上画画就明白了,刚开始prev指向链表头,p指向表头的下一个位置,然后比较when,因为我们刚开始就假设了一个前提,新的Message的when只会比表尾的Message的when大,所以每次都会插入到表尾的位置。

理解了上面的流程,我们再回过头来看看when,接着上面的图来看,假设有个Message4要插入进来,而它的when比Message2大,比Message3要小,那该如何执行呢?

首先会进入else中的for循环,然后当prev指向Message2,p指向Message3的时候,判断这句话:

if (p == null || when < p.when) {
break;
}

会发现满足when<p.when,退出循环,然后再执行这句话:

msg.next = p;
prev.next = msg;

这样Message4就插入到了Message2和Message3之间了,如下图:

第4次enqueueMessage后的链表结构

最后我们再整体回顾下Handler、Looper、Message、MessageQueue是如何协调工作的,下面是我画的流程图,为了加深记忆,我举了个不是特别恰当的例子,大家不要对号入座,仅供学习娱乐:

Handler整体流程图

Handler就是辛勤工作赚钱养家的老王,它有一个吃饭的本领sendMessage,挣到钱(Message)后自己也舍不得花,就往家里的保险柜MessageQueue里面存,保险柜还比较高级,只有老王会操作,需要使用专有的指纹enqueueMessage才能打开往里面存,而老王有个败家娘们Looper它有个技能loop,就是花钱,但是保险柜没指纹打不开,它就让老王给它也录个专有指纹next,这样它就可以愉快的花钱(Message)了。

2.Looper死循环为什么不会导致应用卡死?

我们看ActivityThread源码会发现,main方法有下面两段话:

public static void main(String[] args) {
    ......
    Looper.prepareMainLooper();
    ......
    Looper.loop();
}

我们在子线程经常这样做:

new Thread(new Runnable() {
    @Override
    public void run() {
        
        while (!quit) {
            
        }
        
        Log.d("TAG", "如果quit为false这里执行不到");
        
    }
}).start();

只要quit不为true,while循环会一直执行下去,那while循环体外面的语句都执行不到。其实我们每个应用程序的主线程就是一个死循环,那为什么在主线程开启一个死循环程序不会卡死呢?

要回答这个问题,我们得先了解在 Android 应用程序中,主线程通过回调机制来接收各种事件,这些事件包括用户的输入事件(比如点击、触摸、滑动等)、系统事件(比如屏幕旋转、电量低等)、应用程序事件(比如 Activity 生命周期事件、广播接收器事件等)等。当发生某个事件时,系统会回调主线程中对应的方法,比如 onClick()、onTouchEvent()、onCreate() 等,主线程就会根据事件类型进行相应的操作。

与消息循环不同的是,主线程的事件回调是由系统控制的,它不会一直占用线程,而是会根据事件的发生情况进行回调。例如,在应用程序启动时,系统会回调主线程中的 onCreate() 方法来进行初始化操作;当用户点击某个按钮时,系统会回调主线程中的 onClick() 方法来响应用户的操作;当系统检测到电量低时,系统会回调主线程中的 onLowBattery() 方法来通知应用程序等等。

那有人会问,既然Android中的事件是由回调的方式来处理的,那要Looper.loop是干什么呢?答案是:为了处理UI绘制。我们看看下面这篇文章,了解下UI绘制的整个流程,讲的有点复杂,我们重点关注下面一句话:

onVsync() 是底层会回调的,可以理解成每隔 16.6ms 一个帧信号来的时候,底层就会回调这个方法,当然前提是我们得先注册,这样底层才能找到我们 app 并回调。当这个方法被回调时,内部发起了一个 Message,注意看代码对这个 Message 设置了 callback 为 this,Handler 在处理消息时会先查看 Message 是否有 callback,有则优先交由 Message 的 callback 处理消息,没有的话再去看看Handler 有没有 callback,如果也没有才会交由 handleMessage() 这个方法执行。

https://www.jianshu.com/p/5bd5f6d77add

也就是GPU绘制的时候通过vsync信号告诉上层应用,回调到onVsync()方法,然后通过Handler发送消息最终通过doFrame方法来绘制UI。

就分析到这里吧,其实日常开发中我们会用就行,源码分析是让我们知其然知其所以然,让我们看透本质罢了,至于作用吧,嗯。。。,我感觉就是看透本质,我只能(会)这么解释,哈哈哈。。。。。。

原创不易,转载请注明出处。