huazi

huazi

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

Android 的消息机制主要是指 Handler 的运行机制,Handler 底层需要 MessageQueue 和 Looper 的支撑。

主线程,也叫 UI 线程,即 ActivityThread,主线程被创建时就会初始化 Looper。

Handler 主要作用是将一个任务切换到指定的线程去执行,主要是为了在主线程中更新 UI,解决子线程无法访问 UI 的问题。ViewRootImpl 对 UI 操作做了验证,是由 ViewRootImpl 的 checkThread 方法来完成的。

系统为什么不允许在子线程中访问 UI?

因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问有可能导致 UI 控件处于不可预期的状态。

为什么不对 UI 控件加上锁机制?

首先,加上锁机制会让 UI 访问的逻辑变得复杂;其次,锁机制会降低 UI 的访问效率,因为锁机制会阻塞某些线程的执行,基于这两个缺点,最简单高效的做法就是采用单线程来处理 UI 操作。

Handler 会采用当前线程的 Looper 来构建内部的消息循环系统,如果当前线程没有 Looper,那么就会报错。解决办法就是,为当前线程创建 Looper 即可,子线程的 Looper 不会自动结束,需要手动结束。

Handler 创建完毕后,其内部的 Looper 以及 MessageQueue 就可以和 Handler 一起工作了,然后通过 Handler 的 post/send 方法将一个 Runnable 投递到 Handler 的内部的 Looper 中处理,post 方法最终通过 send 方法完成,send 方法会调用 MessageQueue 的 enqueueMessage 方法将消息放入消息队列,然后 Looper 会发现有新消息,就会处理这个消息,然后这个消息中的 Runnable 或者 Handler 的 handleMessage 方法就会被调用。

ThreadLocal 的工作原理#

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只能在指定的线程中可以获取到存储的数据,对于其他线程无法获取数据。
当某些数据是以线程为作用域,且不同的线程具有不同的数据副本时,可以考虑使用 ThreadLocal。

不同的线程中可以访问同一个 ThreadLocal 对象,但是他们的值是不一样的。原因是,不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。

ThreadLocal 的 set 方法如下 (Api 版本 28):

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

会先获取当前线程的 ThreadLocal.ThreadLocalMap 如果 map 为空就创建并且把 value 存入,不为空直接存入。

再看下 get 方法:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

通过 get 和 set 方法可以看出,他们所操作的都是当前线程的 threadLocals 的 Entry 数组,因此不同线程访问同一个 ThreadLocal 的 set ,get 方法的操作权限仅限于各自线程的内部。

消息队列的工作原理#

MessageQueue 主要包含两个操作,插入和读取,读取本身会带着删除操作,插入和读取对应的方法为 enqueueMessage 和 next。其中 enqueueMessage 往消息队列中插入消息,next 取消息并且从消息队列中移除。
MessageQueue 内部是通过单链表来维护消息列表,单链表在插入和删除上比较有优势。

next 方法是一个无线循环的方法,如果消息队列中没有消息,那么 next 方法就一直阻塞在这里,当有新消息到来时,next 方法会返回这条消息并将其从单链表中移除。

Looper 工作原理#

Looper 在 Android 的消息机制中扮演着消息循环的角色,它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就立刻处理,否则就会阻塞在那里。
构造方法如下:

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

通过构造方法,会创建消息队列,并把当前线程保存起来。
通过 Looper.prepare () 可以为当前线程创建 Looper,通过 Looper.loop () 来开启消息循环。

Looper.prepare();
Handler handler = Handler()
Looper.loop();

Looper 可以退出,Looper 退出后,Handler 发消息会失败, send 方法会返回 false。子线程,创建了 looper,所有事情处理完成后,就应该调用 quit/quitSafely 方法来终止循环,否则子线程会一直处于等待状态,如果退出 Looper 后,这个线程就会立刻终止,因此建议不需要的时候终止 Looper。

Looper 的 loop 方法是个死循环,唯一跳出循环的方式是 MessageQueue.next 返回了 null。当 Looper quit 后,会通知消息队列退出,当消息队列标记为退出桩体,它的 next 方法就会返回 null。也就是说 Looper 必须退出,否则 loop 方法就会一致循环下去。next 是个阻塞操作,没有消息时会阻塞,导致 loop 一致阻塞,如果返回了消息,Looper 就会处理消息msg.target.dispatchMessage, 这里的 msg.target 是发送消息的 handler 对象,这样就保证了不会把消息发送给其他的 handler,最终交给了 dispatchMessage 方法处理,dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就成功将代码逻辑切换到了指定的线程中执行。

Handler 工作原理#

Handler 主要工作包括消息的发送和接收过程,可以通过 post/send 的一系列方法来实现,post 最终是通过 send 系列方法来实现的。看代码一目了然:

    public final boolean sendMessage(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;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }    

Handler 发送消息的过程仅仅是向消息队列中插入了一条消息,会根据 delayMillis 来判断插入位置。然后 MessageQueue.next () 会放回这条消息给 looper,Looper 收到消息后就开始处理,最终交给 handler 处理。再看下处理代码:

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

首先,检查 Message 的 callback 是否为空,不为空就通过 handleCallback 处理,message 的 callback 是个 Runnable 对象,实际上就是 post 方法所传递的 Runnable 参数。

其次,检查 mCallback 是否为空,不为空就调用 mCallback 的 handleMessage 方法处理消息,Callback 是个接口,定义如下:

    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

Callback 可以通过如下方式创建:Handler handler = new Handler(callback)。他的意义,注释已说明:可以用来创建一个 Handler 实例,但不需要派生出 Handler 子类。日常开发中,创建 Handler 常见的方式就是派生出 Handler 的子类并重写其 handleMessage 方法,而 Callback 给我们提供了另一种方式。

最后,调用 Handler 的 handlerMessage 方法来处理消息。
Handler 还有一个构造方法,可以指定 Looper,方便切换线程。

主线程的消息循环#

主线程是指 ActivitThread,严格来说它不是线程,但可以把它当做主线程。

ActivityThread 并没有真正继承 Thread 类,只是往往运行在主线程,给人以线程的感觉,其实承载 ActivityThread 的主线程就是由 Zygote fork 而创建的进程。

ActivitThread 入口方法为 main,在 main 中通过 Looper.prepareMainLooper(); 创建主线程的 Looper 和 MessageQueue,并通过 Looper.loop () 开启主线程的消息循环。
ActivitThread 中的 Handler 为 ActivitThread.H,它内部定义了一组消息类型,主要包含了组件的启动和停止过程。

ActivitThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivitThread 的请求后会回调,ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息会将 ApplicationThread 的逻辑切换到 ActivitThread 中执行,即切换到主线程中执行。

总结#

Android 的消息机制很重要,要充分理解其工作机制,以及 Handler, MessageQueue, Message, Looper 是如何共同工作的。

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.