关于 Android 中 Handler 线程转换的思考

Roy 发布在Android 1

读完 Handler 相关的源码,了解了 Handler 在多个线程间进行消息分发的大概流程,以及和 MessageQueue,Looper 之间的关系,但是心中一直还存在一点疑惑:假设在主线程定义的 Handler,在子线程中调用发送消息,是怎么让主线程收到这个消息的呢?粗看好像是 Handler 进行了一次线程切换,但是仔细查看源码便可以发现,Handler 似乎并没有这种能力。

首先我们从子线程中发送消息开始理一下代码大概是怎么走的。首先看 Handler 的初始化代码:

    public Handler(@Nullable Callback callback, boolean async) {
        ......
        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 初始化的时候,会定义一个 Looper 以及 MessageQueue 类型的成员变量,而且 Looper 为空的话就会报异常,因此在 Handler 初始化前必须先进行 Looper 的初始化。这就是为什么如果在子线程中使用 Handler 必须有如下类似代码:

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler =new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        return false;
                    }
                });
                Looper.loop();
            }
        }).start();

prepare()方法中是将新建一个 Looper 对象,并将其放进该线程的 ThreadLocal 对象中。Looper 的初始化代码中有会新建一个 MessageQueue 对象:

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

这样 Handler、MessageQueue、Looper 三者的关系就明了了,我们开始进入正题,当子线程中调用 Handler 发送一个消息时,具体代码参考如下:

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    handler.sendEmptyMessage(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

经过一系列类似方法跳转,最后会进入如下方法中:

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

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

上面代码 Handler 中要传递的消息最后被成员变量 MessageQueue 插入了自身队列中(由单例表实现,方便插入和删除),而后会通过nativeWake()native 方法唤起此时正在等待 MessageQueue 消息的 Looper。

public static void loop() {
        final Looper me = myLooper();
        ......
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

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); //here
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
     ......
    }

主要看msg.target.dispatchMessage(msg);这一行代码,target 是在当初调用enqueueMessage()方法时将自身传递到 Message 中的,最后还是通过调用 Handler 的 dispatchMessage 来处理消息。

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

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

为什么不直接在子线程中调用 Handler 的 dispatchMessage() ,而要绕这么一大圈呢?

我们用代码做一下验证:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message message) {
                Log.d(MainActivity.class.getSimpleName(), "current thread-->" + Thread.currentThread().getName());
                Log.d(MainActivity.class.getSimpleName(), "handleMessage: " + message.what);
                return false;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    handler.sendEmptyMessage(1);

                    Thread.sleep(2000);
                    Message message = Message.obtain();
                    message.what = 2;
                    handler.dispatchMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

输出结果为:

D/MainActivity: current thread-->main
D/MainActivity: handleMessage: 1
D/MainActivity: current thread-->Thread-2
D/MainActivity: handleMessage: 2

可以看到直接调用dispatchMessage()的话代码还是在子线程中运行,因此可以知道,消息在哪个线程接收,并不是由 Handler 来决定的,而是由初始化的时候 Handler 所绑定的 Looper 对象所在的线程来决定。Looper 对象是在某个线程的 ThreadLocal 中创建的,而 MessageQueue 对象是在 Looper 中创建的,就像一开始 Handler 在子线程中调用一样,后面的 Handler 是在 Looper 所在线程进行dispatchMessage()方法的调用的,因此那时候该方法已经转到该线程中运行了。

再看一个HandlerThread使用的例子:

        HandlerThread thread = new HandlerThread("test");
        thread.start();

        Handler handler = new Handler(thread.getLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                Log.d(MainActivity.class.getSimpleName(), "current thread-->" + Thread.currentThread().getName());
            }
        });

可以看到输出结果为:

D/MainActivity: current thread-->test

使用HandlerThread有一点需要注意的是线程操作处理完成后要记得主动调用thread.quit();进行释放,不然其中的 Looper 一直阻塞,线程无法结束。

TOP
前往 GitHub Discussion 评论