0

Handler作用和原理讲解

 1 year ago
source link: https://www.longdw.com/hanlder-message-looper-queue/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在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 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;
......
msg.target.dispatchMessage(msg);
......
......
return true;
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);
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;
......
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后链表结构如下:

11.png
第一次enqueueMessage后的链表结构

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

1-4.png
第二次enqueueMessage后的链表结构

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

2-3-895x1024.png
第三次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之间了,如下图:

3-3-632x1024.png
第4次enqueueMessage后的链表结构

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

5-3-1024x777.png
Handler整体流程图

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

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

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

public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
Looper.loop();
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();
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。

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK