返回博客首页

《Android开发艺术探索》第10章-Android的消息机制

XiaoNiu · 2026/5/6 07:20:36

在Android的多线程开发中,Handler的运用是很重要的一环。本章主要介绍Handler的运行机制。

10.1 Android的消息机制概述

如果在子线程中更新UI,有可能会收到android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.报错。这个报错是在ViewRootImpl的checkThread方法中抛出的:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

如果要在子线程中更新UI,则需要借助Handler了。之所以不能在子线程对UI进行操作,是因为如果多个线程同时操作UI,会导致不能预期的结果。加锁可以解决这个问题,但是又会牵扯出其他问题,比如效率的降低以及可能导致的阻塞。所以在包括Android的许多操作系统中,操作UI都是单线程进行的。

上面说到,checkThread是在ViewRootImpl中进行的。在第8章中说到,ViewRootImpl对应的是一个Window。并且在抛出的异常中说的是“Only the original thread...”而不是“Only the main thread...”那么这个“original thread”指的是哪个线程呢?从代码中可以看出,它指的是ViewRootImpl的mThread,而这个线程其实就是创建Window的这个线程。

事实上,通过以下代码创建一个Window:

        final TextView tv = new TextView(MainActivity.this);
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
                tv.setText("hello");
                tv.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        tv.setText("world");
                    }
                });
                mHandlerInThread = new Handler();

                WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        WindowManager.LayoutParams.TYPE_APPLICATION,
                        0, PixelFormat.TRANSLUCENT
                );
                lp.gravity = Gravity.LEFT | Gravity.TOP;
                wm.addView(tv, lp);
                Looper.loop();
            }
        });
        mThread.start();

在子线程中创建一个Window,这个Window只包含一个TextView,而这个TextView的内容只能在这个线程修改。如果在主线程中修改TextView的内容,也会抛出android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.异常。

Handler是如何工作的呢?首先,创建Handler必须要在当前线程中调用Looper.prepare(),如果没有的话,那么就会抛出Can't create handler inside thread that has not called Looper.prepare()异常。

    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

看一下Looper的prepare方法:

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

sThreadLocal是一个ThreadLocal类型的对象,它在Loop类中是一个静态对象。sThreadLocal中维护了一个map,map的key是当前线程,value是当前线程的looper。

    // ThreadLocal.set()
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

在每个线程中,只能调用一次Looper.prepare(),否则会抛出异常,也就是说每个线程只对应一个Looper。 而每个Looper中维护了一个消息队列mQueue,在Looper.loop()中有一个死循环,在循环中会调用mQueue的next方法取出消息队列中的下一个消息。而next方法是阻塞的,直到取到下一个消息才会返回。

    // 略去部分
    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            msg.target.dispatchMessage(msg);
        }
    }
    // 略去部分
    Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }

当有消息之后,会调用消息对应Handler的dispatchMessage方法,进行消息的分发,最后调用到Handler的handleMessage方法或者Callback回调。 当在外部调用了Handler的post方法或者send方法之后,最终都会传递到enqueueMessage方法中

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在这里对Message对象的target成员赋值,并且调用了MessageQueue的enqueueMessage方法将消息添加到MessageQueue之中。在MessageQueue的enqueueMessage方法中,有以下一段:

                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;

可以看出,在MessageQueue中,消息队列其实就是一个链表,而MessageQueue中的mMessage成员即是这个链表的头部。链表是以消息的触发时间来排序的,即是enqueueMessage中的uptimeMillis参数。当时间到达Message设定的时间,MessageQueue阻塞的next()方法就会将其返回,然后Handler将会收到消息。

10.2 Android的消息机制分析