浅析Android消息机制 handler之原理分析

1,189 阅读12分钟

前言

在日常的开发过程中我们经常会用到Android消息机制,其中Handler尤为常见。比如当我们执行一些耗时操作,例如读写文件,网络IO时是不建议在主线程也就是UI线程上直接进行,而是重新开一个子线程去完成这些耗时操作,而有一些耗时操作需要更改UI,比如我们需要从网络上下载一些图面显示在界面上,当图片下载完成时,由于Android的机制,子线程是无法更改UI的,所以这时候就需要切换到主线程去更改UI,而这个过程就是由Handler配合完成的。当然更新UI只是Handler的一个功能,它还有更多有趣的功能,对于Handler的实际应用我们在下一篇文章会具体展示。

ThradLocal

在正式介绍Handler之前我们先了解一下ThreadLocal,ThreadLocal是一个线程内部存储类,通过它可以在指定的线程存储数据,数据存储以后,只有在指定的线程才能获取到数据,是不是很有意思,同一个ThreadLocal在不同的线程可以取到不同的数据,它是用于获取我们后面提到的Looper,因为每一个线程都可以有一个Looper。我们以下面这个例子来说明这个过程。


public class MainActivity extends AppCompatActivity {
    private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBooleanThreadLocal.set(true);
        Log.d("HandlerDemo""MainThread: " + mBooleanThreadLocal.get());
        new Thread("Thread1") {
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d("HandlerDemo""Thread1: " + mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread2") {
            @Override
            public void run() {
                Log.d("HandlerDemo""Thread2: " + mBooleanThreadLocal.get());
            }
        }.start();
    }
}

我们可以看到我们声明了一个ThreadLocal,我们在主线程中设为true,获取主线程的ThreadLocal为true,在Thread1中设为false,获取ThreadLocal为false,Thread2没有设置则为空。我们可以把线程联想成一个小区,ThreadLocal是门牌号,虽然门牌号相同,但是小区不同所住的人肯定不一样。

Android消息机制原理

Android消息机制的原理如下图所示。

我们以一个例子来说明这个过程,假如我们需要下载一个文件,这是一个耗时操作,我们需要重新开一个线程去下,下载后Toast完成下载,如果直接在子线程中使用Toast会报错,所以就需要使用Handler来完成这个过程。代码如下

package io.appetizer.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), "Success"Toast.LENGTH_LONG).show();
                    }
                });
            }
        }).start();
    }
}

我们首先可以看到handler调用了post方法。post方法源码如下。

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

我们可以看到post方法调用了sendMessageDelayed方法,从名字可以看出这是延迟发送消息,post是它的一个特殊情况。sendMessageDelayed的源码如下

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

而sendMessageDelayed方法调用了方法sendMessageAtTime方法,从名字我们也可以看出这是一个在固定时间点发送消息,它的源码如下

 public boolean sendMessageAtTime(@NonNull 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);
    }

这里很关键,我们看到了Android 消息机制的第一个幕后英雄MessageQueue,我们现在捋一下思路,当我们在子线程经过耗时操作得到结果,然后通过handler的post来发送消息,post的方法经过一系列的调用来到这里,首先获取一个MessageQueue,如果MessageQueue不存在则报错,如果存在则调用了enqueueMessage方法,源码如下。

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

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

从名字我们看出这个函数主要作用是把Message插入消息队列MessageQueue,MessageQueue虽然名字叫队列,可是有些人表面上说是队列,实际上却是个链表。我们可以看到在这里将msg的target设置为this,也就是这个handler,这是一个伏笔,之后将会了解它的作用。现在我们还是按照这个流程走下去,最后调用了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;
            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;
    }

终于走到了这一步,从源码我们可以看出,我们已经将Message插入了MessageQueue,我们还可以看出发送的时间when决定了在Message的位置。之后可以通过Looper.loop来获取MessageQueue的消息,关键代码如下。这里就可以看到之前的伏笔msg.target,也就是最初的那个handler,调用了dispatchMessage。

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;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
                msg.target.dispatchMessage(msg);           
    }

Handler的dispatchMessage源码如下:

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

我们可以看到Message从Handler到MessageQueue,再到Looper,最后又回到了Handler,而Handler实在主线程,就可以操作UI了。现在再看看之前那张图是不是更加清晰了呢。

最后

点赞就是最大的支持,更多精彩文章和资料可以关注微信公众号QStack。