一篇文章搞定《Handler机制》

前言

Handler机制大家一定都不模式吧
那为什么,Handler出现的频率这么高呢?面试中也是重点知识。
因为Handler是支撑整个Android系统运行的基础,本质上Android系统都是由事件驱动的。而处理事件的核心就在于Handler。

看下本文的目录吧,帮你解答这个问题

  • Handler是什么
  • Handler的结构
  • Handler的运行模型
  • 常见的Handler问题解析

Handler是什么

官方的解释:在Android中,Handler是一个用于发送和处理消息的类。它主要用于实现线程之间的通信和异步消息处理机制。
说白了就是:就是线程间通信
引用View中官方的一句话:整个视图树是单线程的, 在任何视图上调用任何方法时,必须始终在 UI 线程上。如果在其他线程上工作并希望从该线程更新视图的状态,则应该使用Handler
也就是说,Handler在解决View的绘制和更新中,有着相当重要的角色。

在Android中的角色

就两种角色:

1、消息的发送者:通过Handler的sendMessage()方法发送消息给目标线程。
2、消息的处理者:通过重写Handler的handleMessage()方法来处理接收到的消息。

在Android常用的方式举例:

  • 在主线程中更新UI:由于主线程是不允许进行耗时操作的,当需要在后台线程中完成一些耗时操作后更新UI时,可以使用Handler将更新UI的工作切换到主线程中。
  • 实现异步任务:当需要在后台线程中执行任务并等待任务完成后才进行下一步操作时,可以使用Handler来实现异步任务处理。
  • 定时任务的执行:通过Handler的postDelayed()方法来实现定时任务的执行,可以延迟指定的时间后在目标线程中执行任务。

了解了Handler能帮助我们什么呢?

解决的都是我们在APP常见的问题。(各个问题因为什么就先不做解释,在下面的介绍会穿插进去)

  • 卡顿检测
  • ANR监控
  • 内存泄漏
  • Looper的使用

Handler成员介绍

先介绍一下我们都有哪些主要的成员:
Handler、Looper、Message、MessageQueue、ThreadLocal

Handler

Handler相当于其他几个成员包装的工具类。
为什么这么说呢? 那我说一说他能干啥,你听听是不是工具类

  • Handler内部包含了Looper、Messager、MessageQueue、ThreadLocal来接受、管理、发送我们的消息。
  • 提供了sendMessage()系列方法:发送普通消息、延迟消息,最终调用queue.enqueueMessage()方法将消息存入消息队列
  • 提供了post()系列方法:提交普通/延迟Runnable,随后封装成Message,调用sendMessage()存入消息队列
  • 提供了dispatchMessage()系列方法:分发消息,优先执行msg.callback(也就是runnable),其次mCallback.handleMessage(),最后handleMessage()

兄弟们是不是:这不就是一个提供外部接口,处理消息的工具类吗。我说的对不。
就是一个封装了消息创建、传递、处理机制的用于处理线程通信的工具类。

Looper

就是消费者,卡卡消费Message的消费者。
不断循环执行(Looper.loop),从MessageQueue中读取Message。之后通过dispatchMessage去分发消息进行处理。
常用方法有两个:

  • prepare():创建消息队列
  • loop():遍历消息队列,不停地从消息队列中取消息,消息队列为空则等待

默认的Looper就是我们的主线程Looper。我们可以通过getMianLooper()去获取。

val mHandler = Handler(Looper.getMainLooper())

也可以通过我们自己去创建自己的Looper。

public class MyHandler extends Handler {
    private static class MyThread extends Thread {
        public Handler mHandler;
        
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler();
            Looper.loop();
        }
    }

    public MyHandler() {
        MyThread myThread = new MyThread();
        myThread.start();
        
        // 等待子线程中的Looper初始化完成
        while (myThread.mHandler == null) {}
        
        // 将子线程中的Handler对象赋给当前Handler
        this.mHandler = myThread.mHandler;
    }
}

在run方法中调用Looper.prepare()来初始化子线程的Looper,然后调用Looper.loop()来使其循环执行消息队列中的消息。

Message

就是一个使用享元模式设计的消息的承载类(就是一个实体类)
源码如下:
在这里插入图片描述
常用方法

  • obtain()系列:获取一个消息实例
  • recycle():回收消息实例

实际的共享消息队列,提供保存和取出消息的功能,底层由链表实现(链表在插入和删除上比较有优势),常用方法就一个:

  • next():获取消息。
      1. 有消息,且消息到期可以执行,返回消息
      1. 有消息,消息未到期,进入限时等待状态
      1. 没有消息,进入无限期等待状态,直到被唤醒

简单来说就是:就是一个管理Message的容器,等待着Looper来处理

ThreadLocal

其实严格上来说,他不算Handler的成员。
但是Handler借用了ThreadLocal来保证MessageQueue在当前线程线程的唯一性。
为什么这么说:看源码

public final class Looper {
    ........
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal存放了Looper对象
而MessageQueue 又是Looper的成员变量

public final class Looper {
    ........
    final MessageQueue mQueue;

所以ThreadLocal保证了MessageQueue在线程的唯一性。
ps;这也是造成内存泄漏的原因(下面讲)

Handler的运行模型

上面讲了各个成员,也说了Handler就是工具类,是外表。
那来看看哥几个在内部干嘛了。
先来一个图(老规矩)
在这里插入图片描述
看到这个图,结合上面的成员介绍整个过程大家应该就清楚了。

  • 1、子线程在处理完耗时时间后,通过sendMessage()发送消息通过enqueueMessage发送

上面的图中的sendMessage()到queue.enqueueMessage()的过程为
在这里插入图片描述
最后两步:源码如下

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • 2、Looper进行for循环,通过next()获取Message进行分发

源码如下:一看就懂
Looper.java

public static void loop() {
    .......
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    。。。。。。。

MessageQueue.java

Message next() {
    ......
    for (;;) {
        //阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
               // Stalled by a barrier.  Find the next asynchronous message in the queue.
               do {
                    prevMsg = msg;
                    msg = msg.next;
               } while (msg != null && !msg.isAsynchronous());
    .........
   }
  • 3、通过Looper拿到Message进行分发

Looper.java

@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
        ......
        msg.target.dispatchMessage(msg);
        ......

Handler.java

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

过程就是这样了,里面的细节我们通过下面的问题解析,来一点点看。

Handler的常见问题解析

看看Handler中常见的问题都有哪些

Android中除了Handler其他的线程通信有哪些

  • BroadcastReceiver(广播接收器):通过发送和接收系统广播来实现不同组件之间的通信。
  • AsyncTask(异步任务):用于在后台线程执行异步任务,并在主线程更新UI。
  • EventBus:是一种发布/订阅事件总线,用于简化不同组件之间的通信。EventBus可以在不同线程之间发送和接收事件。
  • RxJava:是一个基于观察者模式的异步编程库,可以简化多线程编程和异步任务处理,并提供丰富的操作符和线程调度器。

Handler内存泄漏的原因

大家知道内存泄漏是因为在该结束的生命周期没有结束生命的对象。
那么Handler为什么会造成内存泄漏呢?
因为他持有了Activity对象(非静态内部类默认持有外部类引用),导致在Handler没处理完任务时,就关闭Activity,这时候因为Handler还持有着Activity从而造成了内存泄漏。(这么浅显的回答就GG了)
但是问题来了?
在《一篇文章搞定《JVM的完美图解》》我们讲到:
我们知道GC的回收算法是根据可达性分析算法进行回收标记。然而当没有GC Root根去引用的对象,就会被标记到。
那么Handler也不是GC Root根,只是我们创建的Handler类对象,那么不会被回收一定是被某个GC Root根引用了。

下面我们通过源码找寻到底是哪个Root引用到了Handler从而导致Activity不能被回收。
1、第一步:我们通过Handler.sendMessage()发送最终到Handler.enqueueMessage()方法。这个上面也说到了。
2、第二步:我们看一下Handler.enqueueMessage()的源码

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;  //奥秘在这里
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到我们将this也就是当前的Handler引用给了msg.target
3、第三步:msg.target是Message的成员变量。

public final class Message implements Parcelable {
    @UnsupportedAppUsage
    /*package*/ Handler target;

4、第四步:Message不用说了到了MessageQueue中。而MessageQueue是Looper的成员变量

public final class Looper {
    @UnsupportedAppUsage
    final MessageQueue mQueue;

5、Looper呢又被ThreadLoacl存储到ThreadLocalMap中。

public final class Looper {
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    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));
    }
}

6、ThreadLocalMap是静态的也就是我们的GC Root(在 一篇文章搞定《JVM的完美图解》说了哦)

static class ThreadLocalMap { }

这次大家知道为什么Handler会有内存泄漏的问题了吧。可不要再回答”非静态内部类默认持有外部类引用“了哦。

Handler发送延迟消息的原理

首先我们使用Handler的消息延时都是调用sendMessageDelayed函数实现的,其中delayMillis是需要延时的毫秒。

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

那他的原理是什么呢?主要是放入队列和放入队列的时候操作的呗也就是MessageQueue.enqueueMessage和MessageQueue.next()呗。一个进一个出。

  • 首先他肯定要把延时信息带进去啊,对吧。

这个最终是在messageQueue中的enqueueMessage方法中
messageQueue.java

 boolean enqueueMessage(Message msg, long when) {
     .....
     synchronized (this) {
        ...

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake; // 是否需要同步队列休眠 标记

        // 按照 Message 的 when,有序插入 MessageQueue 中
        // 如果 没接收到消息 || 插入的消息的when==0(立即插入)|| 插入的执行时间 < 当前处理消息的时间点
        // 上面的判断条件,无非就是 判断一个队列的插入几个特殊场景(头、尾)
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 需要唤醒 && 消息屏障 && 是一个异步消息
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            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;
        }
        
        if (needWake) {
            nativeWake(mPtr);
        }
        ... 
    }

 }

当sendMessage 时,MessageQueue.enququeMessage()会将消息按照按照实际执行时间顺序(即msg.when)插入到队列,然后执行nativeWake()唤醒。唤醒其实就是立马将消息发出(那么怎么实现延迟的呢?)

  • 是在发出消息的时候,在通过nativePollOnce进行一个等待

还是需要我们看一下next()方法
我就简化一下这个next()方法了,大家可以对照源码阅读
MessageQueue.java

 @UnsupportedAppUsage
 Message next() {
     for (;;) {
         .....
         //借助native的epoll命令阻塞一段时间,超时之后自动恢复
         nativePollOnce(ptr, nextPollTimeoutMillis);
         synchronized (this) {
         .....
             if (msg != null) {
                    /** 2、计算Message执行时间与当前时间的差值,即延迟时间 */
                    if (now < msg.when) {.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        /** 3、无需延迟,也就是达到这个when的时间了,立即返回Message进行后续处理 */
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
               .....
               if (pendingIdleHandlerCount <= 0) {
                    /** 4、没有IdelHandler,重新进行循环 */
                    mBlocked = true;
                    continue;
                }
         }
     }
 }

1、可以看到这里通过nativePollOnce()去进行阻塞
2、阻塞时间可以看到通过下面的进行计算,也就是

nextPollTimeoutMillis = (int)Math.min(msg.when - now, Interge.MAX_VALUE);

3、如果达到这个时间了,或者没有延迟时间。那就直接返回Message发走
4、如果还需要延迟的话那么就给nextPollTimeoutMillis 赋值进行,nativePollOnce去阻塞
5、这个IdelHandler后面具体的说一下是什么

这个需要总结一下:

  • MessageQueue.enququeMessage()这里主要是通过when去排序消息到MessageQueue中的。
  • 为什么排序呢?为了下面next时候,去阻塞时候后面的消息一定是不会超出延时时间的,因为前面的消息比后面的时间短
  • MessageQueue.next()中通过计算nextPollTimeoutMillis,让epoll命令进行阻塞。

nativePollOnce()是什么

nativePollOnce()是next()方法的底层实现,它是一个Native方法,用来实际执行从消息队列中取出下一个消息的操作。

nativePollOnce()会调用底层的epoll函数(Linux的事件驱动库)来监听和等待文件描述符上的事件。当有事件发生时,nativePollOnce()会返回一个非负的整数值,表示事件发生的次数;当没有事件发生时,nativePollOnce()会返回一个负数。通过这个返回值,next()方法可以判断是否有事件发生,并进行相应的处理。

为什么要弄个死循环呢?

主线程确实是通过Looper.loop()进入了死循环状态,也正因为这样,主线程才不会像我们一般创建的线程一样,当可执行代码结束之后,线程生命周期就终止了。
这样能保证我们的主线程一直存活下去,我们也可以模仿这种方法去做线程保活的需求。

Handler的For的死循环为啥不回ANR

首先Handler没有消息是非阻塞的,所以不会ANR,那么看看为什么是非阻塞的。
刚才我们分析过 Looper.loop() 是一个死循环方法的执行过程。但是UI线程却并没有阻塞,反而还能进行各种手势操作。也并没有出现ANR。这是因为在 MessageQueue 的 next 方法中,可以看到:

@UnsupportedAppUsage
Message next() {
    .....
    // native 方法:当调用此方法时,主线程会释放CPU资源进入休眠状态
    // 直到下一条消息到达之前或者有事务发生
    // 通过往 管道 写端写入数据来唤醒主线程工作nativeWake(mPtr);
    // 这里采用 epoll 机制:放弃CPU主权交给别的线程执行,因此只会阻挂起但不会阻塞主线程
    nativePollOnce(ptr, nextPollTimeoutMillis);
    
    // 当Looper 被唤醒后,会继续往下执行
   synchronized (this) {
       if (msg != null) {
                if (now < msg.when) {
                    // 如果上面检索出了消息,但是还没有到可以执行的时间
                    // 则更新 nextPollTimeoutMillis 也就是下一次循环需要阻塞的时间值(动态传递剩余时间,不剩余即可唤醒执行)
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 找到了需要处理的消息
                    mBlocked = false;
                    // 由于这个消息即将被处理,所以需要将它从队列中移除
                    // 通过调整节点的关系,达到队列元素移除的目的。
                    // 有前驱结点
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果没有找到消息,即队列为空,looper将进入永久休眠,知道新消息到达
                nextPollTimeoutMillis = -1;
            }
   }
}

在主线程的 MessageQueue 没有消息时,便阻塞在了 MessageQueue.next() 中的 nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到。所以主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
这里采用Linux的epoll机制,是一种IO的多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或者写操作拿到最新的消息,进而唤醒等待的线程。

post和snedMessage两类发送消息的方法有什么区别?

  • post一类的方法发送是Runnable对象,但是最后还是会被封装成Message对象,post方法不会携带消息的标识符,因此无法在Handler中区分不同的消息。
  • sendMessage一类方法发送的消息直接是Message对象。并在处理消息时通过标识符来区分不同的消息。

总的来说,post方法适用于发送简单的任务或代码块,而sendMessage方法适用于发送需要处理的消息,并可以携带额外的数据。选择使用哪种方法取决于具体的需求和实际情况。
我其实就是觉得post写着更简便一些,没啥区别。

为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?

主线程可以new Handler是因为在Android中,每个应用程序都是单线程模型,主线程也就是UI线程,用于处理用户界面相关的操作。而Handler用于与MessageQueue结合起来处理消息和Runnable对象,从而实现异步消息处理。

如果想要在子线程中new Handler,需要注意以下几点准备:

  • 在子线程中创建Handler之前,需要先调用Looper.prepare()方法,来准备Looper对象。Looper是用来管理与线程关联的MessageQueue和Handler的,一个线程中只能存在一个Looper。
  • 在创建Handler之前,需要调用Looper.loop()方法来启动消息循环机制。Looper.loop()会使得线程进入一个无限循环中,不断地从MessageQueue中获取消息并分发处理。
  • 在子线程中创建Handler时,需要注意在Handler的构造函数中传入的Looper对象。例如,可以使用new Handler(Looper.myLooper())来获取当前线程的Looper对象。

需要注意的是,在子线程中创建Handler后,可以通过Handler发送消息和Runnable对象来更新UI,但在子线程中不能直接进行UI的更新操作。如果需要更新UI,可以通过Handler将消息发送到主线程,然后在主线程中处理UI更新相关的操作。

一个线程有几个Looper几个Handler?如何保证呢?

  • 一个线程只能有一个Looper。

Looper是被存储在ThreadLocal中的,确保每个线程只有一个Looper。ThreadLocal是Java中用于实现线程局部变量(Thread Local Variables)的机制,它为每个线程提供了一个独立的变量副本,这样每个线程都可以独立地改变自己的副本,而不会影响其他线程。

  • 一个线程可以有多个Handler,但是多个Handler都是使用的同一个Looper。

可以看到这里msg.target = this;将当前的Handler给了target。从而标记Handler

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

使用Message时应该如何创建它?

  • 使用Message的obtain()方法创建
  • 如果直接new出来容易造成内存抖动。

内部是如何确保线程安全的?

  • 可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程)
  • 添加消息的方法enqueueMessage()中有synchronize修饰,取消息的方法next()中也有synchronize修饰。
  • 这也是延时时间不那么准确的原因,synchronize还是有效率上的消耗的。

为什么kotlin中的Handler是废弃的了的啊

在Kotlin中,Handler被标记为废弃是因为Kotlin推荐使用更加现代和安全的异步编程机制

  • 例如协程(Coroutines)。使用协程可以更好地管理异步任务,并且能够提供更简洁的代码和更好的性能。
  • 而传统的Handler机制在处理异步任务时存在一些问题,例如需要在UI线程中更新UI元素,但是在其他线程中处理耗时任务时会造成UI卡顿,还有可能导致内存泄漏等问题。

因此,Kotlin推荐开发者使用协程来代替Handler,以更好地处理异步任务。

IdleHandler是什么

IdleHandler 说白了,就是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制。它存放在 mPendingIdleHandlers 队列中。
那怎么理解这个机制呢?
简单说: MessageQueue当前没有立即需要处理的消息时,就会执行 IdleHandler。

public interface IdleHandler {
    boolean queueIdle();
}

先看看怎么使用的呢?
返回 false 表示执行后就将该回调移除掉,返回 true 表示该 IdleHandler 一直处于活跃状态,只要空闲就会被回调。

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        //空闲时处理逻辑

        return false;
    }
});

MessageQueue.java

Message next() {
    .....
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; 
 
      boolean keep = false;
      try {
        keep = idler.queueIdle();
      } catch (Throwable t) {
        Log.wtf(TAG, "IdleHandler threw exception", t);
      }
 
      if (!keep) {
        synchronized (this) {
          mIdleHandlers.remove(idler);
        }
      }
    }
    .......
}

大致的流程是这样的:

  1. 如果本次循环拿到的 Message 为空,或者这个 Message 是一个延时的消息而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态。
  2. 接着就会遍历 mPendingIdleHandlers 数组(这个数组里面的元素每次都会到 mIdleHandlers 中去拿)来调用每一个IdleHandler 实例的 queueIdle 方法。
  3. 如果这个方法返回false的话,那么这个实例就会从 mIdleHandlers 中移除,也就是当下次队列空闲的时候,不会继续回调它的 queueIdle 方法了。

处理完 IdleHandler 后会将 nextPollTimeoutMillis 设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的 message 执行。

IdleHandler的使用场景

  • LeakCanary 进行内存泄漏检测并不是 onDestry 方法执行完成后就进行垃圾回收和一些分析的,而是利用 IdleHandler 在空闲的时候进行这些操作的,尽量不去影响主线程的操作。
  • 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行
  • 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作

总结

Handler的内容比较多,错综复杂。但是不仅工作中的用处很大,能增强我们解决问题的能力。而且在面试中也是重中之重。
Handler是必须要去看源码的,才能理解。主要就是上面提及到的几个关键的类其中的方法。
MessageQueue.next() 还有MessageQueue.enququeMessage()是必须要看的两个核心方法。

猜你喜欢

转载自blog.csdn.net/weixin_45112340/article/details/131905663
今日推荐