Android进阶知识(二十二):Android的消息机制

Android进阶知识(二十二):Android的消息机制

一、Android的消息机制概述

  Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MeeageQueue、Looper以及ThreadLocal的描述如下表。

描述
MessageQueue消息队列,采用单链表的数据结构存储,内部存储了一组消息,以队列形式对外提供插入和删除工作。
Looper消息循环,Looper以无限循环的形式去查找是否有新消息,有则处理,无则等待
ThreadLocal非线程,可以在不同的线程中互不干扰地存储并提供数据

  Handler的主要作用是将一个任务切换到某个指定的线程中执行。Android提供这个功能的原因在于Android规定访问UI只能在主线程中进行,如果在子线程访问UI,那么程序就会抛出异常,在主线程中进行耗时操作则会导致ANR。ViewRootImpl对UI操作进行了验证,由checkThread方法完成,如下所示。

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

  系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾
在这里插入图片描述
  那么问题来了,系统为什么不允许在子线程访问UI呢?
在这里插入图片描述
  Android的UI控件并不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。那么为什么系统不对UI控件的访问加上锁机制呢?
在这里插入图片描述
  缺点有两个:

  1. 加上锁机制会让UI访问的逻辑变得复杂。

  2. 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

  对于Handler的工作原理,Handler创建时会采用当前线程的Looper来构建内部的消息循环系统(没有Looper会报错),Handler创建完毕后通过post或者send方法将Runnable投递到Handler内部的Looper处理。send方法工作过程如下。
在这里插入图片描述
  当Handler的send方法被调用,它会调用MessageQueue的enqueueMessage方法将消息放入消息队列,然后Looper发现新消息到来,就会处理该消息,最终消息的Runnable或Handler的handleMessage方法被调用。
  笔者曾经写过关于Handler消息机制的总结以及使用方法,读者可以参看笔记:Android基础知识(九):Handler机制的原理分析Android基础知识(十):Handler的使用方式与注意事项

二、Android的消息机制分析

  Android的消息机制实际上就是Handler的运行机制,为了分析Handler的运行过程,需要了解Handler、MessageQueue、Looper和ThreadLocal的工作原理。

  1. ThreadLocal的工作原理

  ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储之后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。常见的应用场景为,某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候
在这里插入图片描述
  ThreadLocal的演示例子如下。

private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();

mBooleanThreadLocal.set(true);
Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

new Thread("Thread#1") {
    @Override
    public void run() {
        mBooleanThreadLocal.set(false);
       Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
    }
}.start();

new Thread("Thread#2") {
    @Override
    public void run() {
       Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
    }
}.start();

  运行程序得到的结果如下,这更能理解ThreadLocal所表达的意思。ThreadLocal的奇妙之处就是不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,根据当前ThreadLocal的索引查找对应的value值

D/TestActivity(7689): [Thread#main]mBooleanThreadLocal=true
D/TestActivity(7689): [Thread#1]mBooleanThreadLocal=false
D/TestActivity(7689): [Thread#2]mBooleanThreadLocal=null

  不同线程中的数组是不同的,这就是通过ThreadLocal可以在不同的线程中维护一套数据的副本并彼此互不干扰的原因。
  为了了解其工作原理,我们来看ThreadLocal的set方法。

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

  在ThreadLocal内部有一个成员专门用于存储线程的ThreadLocal的数据:ThreadLocal.Values localValues,values方法则用于获取当前线程的ThreadLocal数据。
  在localValues内部有一个数组:private Object[] table,ThreadLocal的值就存储在该数组中。具体的算法在put方法中实现,其存储规则为ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置:table[index+1]=value(其中reference对象在table数组索引为index)。
在这里插入图片描述
  至于ThreadLocal的get方法就不多解释了。从set方法可以看出其所操作的对象是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读/写操作仅限于各自的线程内部。

  1. MessageQueue的工作原理

  MessageQueue的主要操作为插入和读取,读取操作本身伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next。MessageQueue实际上是一个单链表的数据结构,因此这两个操作也就是对单链表的操作,源码就不看了,下表为两个方法的作用描述。

方法作用
equeueMessage往消息队列中插入一条消息
next从消息队列中取出一条消息并将其从消息队列中删除,是一个无限循环的方法,如果消息队列中没有消息,阻塞;新消息到来,返回消息并移除
  1. Looper的工作原理

  Looper在Android的消息机制中扮演着消息循环的角色,其会不停地从MessageQueue中查看是否有新消息,如果有则立即处理,否则一直阻塞。其构造方法会创建一个MessageQueue,这就是Looper和MessageQueue在同一个线程的原因

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

  Handler的工作需要Looper,没有Looper的线程就会报错,关于如何创建Looper可以参照博客:Android基础知识(十):Handler的使用方式与注意事项。下表为Looper几个方法的描述。

方法作用
Looper.prepare()为当前线程创建一个Looper
Looper.loop()开启消息循环
Looper.prepareMainLooper()给主线程即ActivityThread创建Looper使用,本质也是通过prepare方法实现
Looper.getMainLooper()可以在任何地方获取到主线程的Looper
Looper.quit()直接退出Looper,Looper退出后,通过Handler发送消息会失败,send方法返回false
Looper.quitSafely()设定一个退出标记,把消息队列中的已有消息处理完毕后安全退出

  值得一提的是,在子线程中,如果手动创建了Looper,那么在所有事情处理完以后需要调用quit方法终止消息循环,否则子线程将无限等待,一旦Looper退出,线程立即终止。
  Looper最主要的方法就是loop方法,只有调用loop方法后,消息循环系统才会真正地起作用,其部分源码如下。

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the 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.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // other code

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

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        // other code
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        }
        // other code
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        msg.recycleUnchecked();
    }
}

  Looper的loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper的quit()被调用,Looper会调用MessageQueue的quit或者quitSafely来通知消息队列退出,当消息队列被标记退出状态,next方法返回null。
  loop会调用MessageQueue的next方法获取新消息,当没有新消息,next阻塞,loop方法阻塞;next返回新消息,Looper处理该消息:msg.target.dispatchMessage(msg)。其中msg.target为发送这条消息的Handler对象,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这就是切换到指定线程的逻辑。
在这里插入图片描述

  1. Handler的工作原理

  Handler的工作主要包含消息的发送和接收过程,消息的发送可以通过post和send一系列方法实现,post系列方法最终通过send方法实现
  Handler发送消息仅仅是向消息队列中插入一条消息,MessageQueue的next方法返回消息给Looper,Looper收到后开始处理,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用。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);
    }
}

  用流程图归纳如下。
在这里插入图片描述
  从中也可以知道Handler处理消息的回调方法的优先级:
  ① 优先级最高的是Message自己的callback,这是一个Runnable对象,用Handler post一个Runnable的时候,其实就是把这个Runnable赋值给Message对象的callback,然后把Message对象发送给MessageQueue。
  ② 优先级第二的是Handler自己的callback,在创建一个Handler对象时可以传一个Handler.Callback对象给它,并实现Callback里面的handleMessage(Message)方法,作为消息处理方法。
  ③ 优先级最低的是Handler自己的handlerMessage(Massage)方法,也就是常用的消息处理方法。
  另外,前面提到了Handler构建时,当前线程没有Looper会报错,这可以从源码中得到答案。

public Handler(@Nullable Callback callback, boolean async) {
   // other code

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

三、主线程的消息循环

  Android的主线程为ActivityThread,主线程的入口方法为main,在main方法中会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()开启主线程的消息循环。

public static void main(String[] args) {
    // other code

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();
    // other code
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

  主线程的消息循环开始后,ActivityThread需要一个Handler来和消息队列交互——ActivityThread.H,其内部定义了一组消息类型,包括四大组件的启动和停止等。

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    @UnsupportedAppUsage
    public static final int EXIT_APPLICATION        = 111;
    @UnsupportedAppUsage
    public static final int RECEIVER                = 113;
    @UnsupportedAppUsage
    public static final int CREATE_SERVICE          = 114;
    @UnsupportedAppUsage
    public static final int SERVICE_ARGS            = 115;
    @UnsupportedAppUsage
    public static final int STOP_SERVICE            = 116;
    
    // ......
}

  主线程的消息循环模型如下图所示。ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread的逻辑切换到ActivityThread中执行,即切换到主线程中执行
在这里插入图片描述

参考资料:《Android开发艺术探索》

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值