面试面试

带着问题学习 Android Handler 消息机制

2018-02-06  本文已影响0人  Marker_Sky
学习 Android Handler 消息机制

一、提出问题

面试时常被问到的问题:

这俩问题其实是一个问题,其实只要搞清楚了 Handler,Looper,MessageQueue,Message 的作用和联系,就理解了 Android 的 Handler 消息机制。那么再具体一点:

  1. 为什么在主线程可以直接使用 Handler?
  2. Looper 对象是如何绑定 MessageQueue 的?
  3. MessageQueue 里的消息从哪里来?Handler是如何往MessageQueue中插入消息的?
  4. Message 是如何绑定 Handler 的?
  5. Handler 如何绑定 MessageQueue?
  6. 关于 handler,在任何地方 new handler 都是什么线程下?
  7. Looper 循环拿到消息后怎么处理?

二、解决问题

那么,我们从主线程的消息机制开始分析:

2.1 主线程 Looper 的创建和循环

Android 应用程序的入口是 main 函数,主线程 Looper 的创建也是在这里完成的。

ActivityThread --> main() 函数

public static void main(){
        // step1: 创建主线程Looper对象
        Looper.prepareMainLooper();
        
        ActivityThread thread = new ActivityThread();
        // 绑定应用进程,布尔标记是否为系统进程
        thread.attach(false);
        // 实例化主线程 Handler
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 开始循环
        Loop.loop();

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

Looper.prepareMainLooper()用来创建主线程的 Looper 对象,接下来先看这个方法的实现。

2.1.1 创建主线程 Looper

Looper --> prepareMainLooper()

private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper(){
        // step1: 调用本类 prepare 方法
        prepare(false);
        // 线程同步,如果变量 sMainLooper 不为空抛出主线程 Looper 已经创建
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // step2: 调用本类 myLooper 方法
            sMainLooper = myLooper();
        }
}

prepareMainLooper() 方法主要是使用 prepare(false) 创建当前线程的 Looper 对象,再使用 myLooper() 方法来获取当前线程的 Looper 对象。

step1: Looper --> prepare()

// ThreadLocal 为每个线程保存单独的变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// Looper 类的 MessageQueue 变量
final MessageQueue mQueue;
// quitAllowed 是否允许退出,这里是主线程的 Looper 不可退出
private static void prepare(boolean quitAllowed) {
        // 首先判定 Looper 是否存在
        if(sThreadLocal.get() != null){
                throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 保存线程的副本变量
        sThreadLoacal.set(new Looper(quitAllowed));
}

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

更多关于 ThreadLocal 的原理:

深入剖析ThreadLocal实现原理以及内存泄漏问题

问题1:为什么在主线程可以直接使用 Handler?
因为主线程已经创建了 Looper 对象并开启了消息循环,通过上文的代码就可以看出来。

问题2:Looper 对象是如何绑定 MessageQueue 的?或者说 Looper 对象创建 MessageQueue 过程。
很简单,Looper 有个一成员变量 mQueue,它就是 Looper 对象默认保存的 MessageQueue。上面代码中 Looper 有一个构造器,新建 Looper 对象时会直接创建 MessageQueue 并赋值给 mQueue。
问题2解决:在 new Looper 时就创建了 MessageQueue 对象并赋值给 Looper 的成员变量 mQueue。

step2: Looper --> myLooper()

// 也就是使用本类的ThreadLocal对象获取之前创建保存的Looper对象
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

这个方法就是通过 sThreadLocal 变量获取当前线程的 Looper 对象,比较常用的一个方法。上文主线程 Looper 对象创建后使用该方法获取了 Looper 对象。

2.1.2 开始循环处理消息

回到最开始的 main() 函数,在创建了 Looper 对象以后就调用了 Looper.loop() 来循环处理消息,贴一下大致代码:

public static void main(){
        // step1: 创建主线程Looper对象
        Looper.prepareMainLooper();
        ...
        // step2: 开始循环
        Loop.loop();
}

Looper --> loop()

public static void loop() {
    // step1: 获取当前线程的 Looper 对象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // step2: 获取 Looper 保存的 MessageQueue 对象
    final MessageQueue queue = me.mQueue;

    ...
    // step3: 循环读取消息,如果有则调用消息对象中储存的 handler 进行发送
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        try {
            // step4: 使用 Message 对象保存的 handler 对象处理消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}

Android消息机制1-Handler(Java层)

获取到下一条消息,如果 MessageQueue 中没有消息,就会进行阻塞。那么如果存在消息,它又是怎么放入 MessageQueue 的呢?或者说MessageQueue 里的消息从哪里来?Handler是如何往MessageQueue中插入消息的?先不说这个,把这个问题叫作问题3后面分析。

Handler --> dispatchMessage

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

private static void handleCallback(Message message) {
   message.callback.run();
}

public void handleMessage(Message msg) {
}

可以看到该方法最后执行了 handleMessage() 方法,这是一个空方法也就是需要我们覆写并实现的。另外 dispatchMessage() 也体现出一个问题:

消息分发的优先级:

到这里 Looper 循环并通过 Handler 发送消息有一个整体的流程了,接下来分析 Handler 在消息机制中的主要作用以及和 Looper、Message 的关系。

2.2 Handler 的创建和作用

上面说到 loop() 方法在不断从消息队列 MessageQueue 中取出消息(queue.next() 方法),如果没有消息则阻塞,反之交给 Message 绑定的 Handler 处理。回顾一下没解决的两个问题:

既然要解决 Handler 插入消息的问题,就要看 Handler 发送消息的过程。

2.2.1 Handler 发送消息

Handler --> sendMessage(Message msg);

final MessageQueue mQueue;

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);
}
// 处理消息,赋值 Message 对象的 target,消息队列插入消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到调用 sendMessage(Message msg) 方法最终会调用到 enqueueMessage() 方法,这个方法主要有两个作用:赋值 Message 对象的 target、消息队列插入消息。

Android消息机制1-Handler(Java层)

要了解 Handler 的 MessageQueue 对象是怎么赋值的就要看 Handler 的构造函数了,Handler 创建的时候作了一些列操作比如获取当前线程的 Looper,绑定 MessageQueue 对象等。

2.2.2 Handler 的创建

下面是 Handler 无参构造器和主要的构造器,另外几个重载的构造器有些是通过传递不同参数调用包含两个参数的构造器。两个参数构造函数第一个参数为 callback 回调,第二个函数用来标记消息是否异步。

// 无参构造器
public Handler() {
     this(null, false);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
        }
    }
    // step1:获取当前线程 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // step2:获取 Looper 对象绑定的 MessageQueue 对象并赋值给 Handler 的 mQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

如果获取的 Looper 对象为 null,说明没有执行 Looper.prepare() 为当前线程保存 Looper 变量,就会抛出 RuntimeException。这里又说明了Handler 必须在有 Looper 的线程中使用,报错不说,没有 Looper 就无法绑定 MessageQueue 对象也就无法进行更多有关消息的操作。

既然说到了 Handler 的构造器,就想到一个问题:问题 6:关于 handler,在任何地方 new handler 都是什么线程下?这个问题要分是否传递 Looper 对象来看。

  1. 不传递 Looper 创建 Handler:Handler handler = new Handler();上文就是 Handler 无参创建的源码,可以看到是通过 Looper.myLooper() 来获取 Looper 对象,也就是说对于不传递 Looper 对象的情况下,在哪个线程创建 Handler 默认获取的就是该线程的 Looper 对象,那么 Handler 的一系列操作都是在该线程进行的。
  2. 传递 Looper 对象创建 Handler:Handler handler = new Handler(looper);那么看看传入 Looper 的构造函数:
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
// 第一个参数是 looper 对象,第二个 callback 对象,第三个消息处理方式(是否异步)
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看出来传递 Looper 对象 Handler 就直接使用了。所以对于传递 Looper 对象创建 Handler 的情况下,传递的 Looper 是哪个线程的,Handler 绑定的就是该线程。

到这里 Looper 和 Handler 就有一个大概的流程了,接下来看一个简单的子线程 Handler 使用例子:

new Thread() {
    @Override
    public void run() {
        // step1
        Looper.prepare();
         // step2
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == 1){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
                        }
                    });
                     // step5
                    Looper.myLooper().quit();
                }
            }
        };
         // step3
        handler.sendEmptyMessage(1);
         // step4
        Looper.loop();
    }
}.start();

三、总结和其它

3.1 Handler、Looper、MessageQueue、Message

  1. Handler 用来发送消息,创建时先获取默认或传递来的 Looper 对象,并持有 Looper 对象包含的 MessageQueue,发送消息时使用该 MessageQueue 对象来插入消息并把自己封装到具体的 Message 中;
  2. Looper 用来为某个线程作消息循环。Looper 持有一个 MessageQueue 对象 mQueue,这样就可以通过循环来获取 MessageQueue 所维护的 Message。如果获取的 MessageQueue 没有消息时,便阻塞在 loop 的queue.next() 中的 nativePollOnce() 方法里,反之则唤醒主线程继续工作,之后便使用 Message 封装的 handler 对象进行处理。
  3. MessageQueue 是一个消息队列,它不直接添加消息,而是通过与 Looper 关联的 Handler 对象来添加消息。
  4. Message 包含了要传递的数据和信息。

3.2 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

这是知乎上的问题,感觉问的挺有意思。平时可能不太会太深究这些问题,正好有大神回答那就记录一下吧。

  1. 为什么不会因为死循环卡死?
    线程可以看作是一段可执行代码,当代码执行完毕线程的生命周期就该终止了。对于主线程来说我们不希望它执行一段时间后退出,所以简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。既然是死循环那么怎么去处理消息呢,通过创建新线程的方式。
  2. 为这个死循环准备了一个新线程
    在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:
public static void main(){
        ...
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread();

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 开始循环
        Loop.loop();

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

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。

  1. 主线程的死循环一直运行是不是特别消耗CPU资源呢?

其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资。

  1. Activity的生命周期是怎么实现在死循环体外能够执行起来的?
    上文 main 函数有一部分获取 sMainThreadHandler 的代码:
final H mH = new H();

public static void main(){
        ...
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        ...
}

final Handler getHandler() {
    return mH;
}

类 H 继承了 Handler,在主线程创建时就创建了这个 Handler 用于处理 Binder 线程发送来的消息。

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:

在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。
比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。

3.3 Handler 使用造成内存泄露

  1. 有延时消息,要在Activity销毁的时候移除Messages
  2. 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。

具体操作可以参考文章:

Handler内存泄露原理及解决方法

参考资料:

《Android 开发艺术探索》
Android消息机制1-Handler(Java层)
Android Handler消息机制实现原理

上一篇 下一篇

猜你喜欢

热点阅读