浅谈Android消息处理机制 —— Looper | 青训营笔记

89 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第 10 天。昨天完成了本次青训营实践二的内容,对于Android消息处理机制和性能监控和维护有了一定的了解。

本篇笔记主要系统整理下本次实践过程中学习到的知识与的相关思考。下面开始内容:


一、内容点

  1. Handler、Looper和MessageQueue三者的关系
  2. 异步消息处理线程的概念

二、详细解释

  1. Handler、Looper和MessageQueue三者的关系

    Handler:消息的真正处理者,具备获取消息,发送消息,处理消息,移除消息等功能;

    Looper:Looper负责从消息队列中循环的取出消息然后给Handler处理;每个线程只允许有一个Looper,且主线程中系统已经初始化好一个Looper对象。通过不断执行器loop()方法,从MessageQueue中读取消息,分发消息。

    MessageQueue:已队列的形式对外提供插入和删除工作,其内部结构是以双向链表的形式存储消息的,采用先进先出的方式处理Message。程序在创建Looper时会在其构造器中创建MessageQueue对象;

    此外,还有两个与之相关的概念需要提及:

    Message:代表一个行为what或者一串动作Runnable,每一个消息在加入消息队列时,都有明确的目标Handler;

    ThreadLocal:线程本地存储区(Thread Local Storage,简称:TLS),每个线程都有自己的私有本地存储区域,不同线程之间彼此不能方位对方的TLS区域。主要作用是提供线程内局部变量的TLS,这种变量在线程的声明周期内起作用。

image.png

Android消息处理机制

image.png (引用一位大佬的总结图,若侵权删除)


2. Looper

Looper主要是prepare()和loop()方法需要注意

prepare()方法

public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true));
}

主要为ThreadLocal生成Looper实例,而且会判断原ThreadLoacal是否包含Looper对象,来确保一个线程只有一个Looper对象。

looper()方法

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();
 
        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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
 
            msg.target.dispatchMessage(msg);
 
            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.recycle();
        }
}

分别在Looper的开始分发消息和结束时会在日志输出带有指定内容的log,可以以此作为标志,监听消息分发的耗时

可能还有同学有疑问,这是一个死循环为什么不会导致内存泄露或者应用卡死

由于Linux的处理机制,在消息队列无消息的时候会调用Looper.quitSafely()方法阻塞线程

首先: 卡死是ANR,产生的原因有2个:

1、在5s内没有响应输入的事件(例如按键,触摸等),

2、BroadcastReceiver在10s内没有执行完毕。

事实上我们所有的Activity和Service都是运行在loop()函数中,以消息的方式存在,所以在没有消息产生的时候,looper会被block(阻塞),主线程会进入休眠,一旦有输入事件或者Looper添加消息的操作后主线程就会被唤醒,从而对事件进行响应,所以不会导致ANR

简单来说looper的阻塞表明没有事件输入,而ANR是由于有事件没响应导致,所以looper的死循环并不会导致应用卡死。

三、个人小结

  1. Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工明确。

    Looper :负责关联线程以及消息的分发在该线程下从 MessageQueue 获取 Message,分发给Handler ;

    MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的Message;

    Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

2.一个线程只有一个Looper,通过prepare()方法构造,并确保只有一个Looper,但一个线程不止一个Handler