Handler 消息机制原来解析

2020-12-22 10:09:28 浏览数 (1)

一、主要角色

1.1、message:消息

分为同步消息、异步消息、屏障消息。但是异步消息和屏障消息的相关API都是隐藏的,需要通过反射才能使用。

1.2、MessageQueue:消息队列

负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其 next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。

1.3、Looper:消息循环器

负责关联线程以及消息的分发,在该线程下从 MessageQueue 获取 Message,分发给 Handler,Looper 创建的时候会创建一个 MessageQueue,调用 loop()方法的时候消息循环开始,其中会不断调用 messageQueue 的 next()方法,当有消息就处理,否则阻塞在 messageQueue 的 next()方法中。当 Looper 的 quit()被调用的时候会调用messageQueue 的 quit(),此时 next()会返回 null,然后 loop()方法也就跟着退出。

1.4、Handler:消息处理器

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

二、基本用法

我们可以使用 Handler 发送并处理与一个线程关联的 Message 和 Runnable 。(注意:Runnable 会被封装进一个 Message,所以它本质上还是一个 Message )。

每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起,从而实现消息的管理以及线程间通信。

代码语言:java复制
Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    //这里接受并处理消息
  }
};
//发送消息
handler.sendMessage(message);
handler.post(runnable);

实例化一个 Handler 重写 handleMessage 方法 ,然后在需要的时候调用它的 send 以及 post 系列方法就可以了,非常简单易用,并且支持延时消息。但是奇怪,我们并没有看到任何 MessageQueue 的身影,也没看到它与线程绑定的逻辑,这是怎么回事?

三、原理解析

3.1、整体流程

相信大部分人对Handler的整体工作流程都有所了解了,但是这里还是先简单回顾一下:

第一步:Handler 通过 sendMessage()等系列发送消息 Message 到消息队列 MessageQueue。

第二步:Looper 通过 loop()不断提取触发条件的 Message,并将 Message 交给对应的 target,也就是handler 来处理。

第三步:对应target 调用自身的 handleMessage()方法来处理 Message。

3.2、Handler 与 Looper 的关联

想要了解Handler是怎么和Looper关联的,就要先看一下Handler的构造方法,Handler的显示构造方法有很多种:

代码语言:java复制
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback) {
        this(callback, false);
    }
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

Handler这些显示的构造方法最终都会调到Handler的一个隐藏的构造方法,为什么这个最终的构造方法是隐藏的呢,这就涉及到了异步消息和屏障消息相关API了,这个构造方法里有一个参数是async,表示是否是异步,这个API,默认是隐藏的。代码如下:

代码语言:java复制
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());
            }
        }
        //获取当前线程的looper,如果当前线程没有looper,则抛出一个异常
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //获取当前线程looper对象里的消息队列,也就是上面提到的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

通过上面代码可以看到,在Handler的构造方法里会回去当前线程的looper,并通过获取到的looper对象回去消息队列。也就是说Handler里面持有当前线程的looper和MessageQueue的引用。这样Handler就和looper关联起来了。如果没有获取到当前线程的looper,就会抛出一个异常。

Handler是通过调用Looper.myLooper()方法来获取当前线程的looper的,我们来看下这个方法的实现:

代码语言:java复制
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

可以看到是通过 sThreadLocal.get() 返回的,那么 sThreadLocal 是何许人也,来看一下:

代码语言:java复制
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们发现 sThreadLocal 是Looper类里的一个静态的 ThreadLocal 对象,里面存放的就是looper。(ThreadLocal 不了解的可以自己查找下资料)。那么sThreadLocal 里面的looper是什么时候存进去的?通过查看Looper源码,我们发现了秘密:

代码语言:java复制
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));
    }

原来是在 prepare()方法里直接 创建了一个Looper,然后存了进去。这个方法大家应该都很熟悉吧。

这就是为什么在子线程里面使用Handler的时候,如果不调用Looper.prepare() 方法会抛出异常的原因了(就是上面Handler构造方法里面的异常)。Looper.prepare() 方法只能调用一次,否则会抛异常。

我们平时在主线程使用Handler的时候并没有调用这个方法,怎么没有抛出异常呢,这个我放在后面说。(见4.2)

到这里就明白了,handler是通过Looper和线程关联起来的。

3.3、Message 的存储与管理

Handler 提供了一些列的方法让我们来发送消息,如 send()系列 post()系列 。

不过不管我们调用什么方法,最终都会走到 MessageQueue.enqueueMessage(Message,long) 方法。

代码语言:javascript复制
//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
  			-> queue.enqueueMessage(Message, long);

来看下enqueueMessage方法的实现:

代码语言:java复制
boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg   " This message is already in use.");
        }
        
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target   " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //根据时间,把消息加入到队列
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

通过上面代码可以发现,MessageQueue内部是通过一个链表来维护的,链表里的每一个节点就是一个Message,链表是通过Message的when参数来排序的。

3.4、Message 的分发与处理

我们已经知道通过Looper.prepare() 创建Looper,然后在调用Looper.loop(),looper就开始工作了。我们现在就来看一下loop()

的具体工作怎么实现的。

代码语言:java复制
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 (;;) {
            // 不断从 MessageQueue 获取 消息
            Message msg = queue.next(); // might block
            //退出 Looper 
            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);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //将msg交给他对应的target来处理
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took "   time   "ms on "
                              Thread.currentThread().getName()   ", h="  
                            msg.target   " cb="   msg.callback   " msg="   msg.what);
                }
            }

            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);
            }
            //回收 message
            msg.recycleUnchecked();
        }
    }

从代码可以看出loop()主要工作如下:

第一步:调用 queue.next() 从 MessageQueue 获取消息,如果msg为null,则表示MessageQueue已经推出了,就退出Looper。

第二步:msg.target.dispatchMessage(msg),将msg讲给对应的target处理。

第三步:msg.recycleUnchecked() 回收msg。

来看看MessageQueue.next()的具体实现:

代码语言:java复制
Message next() {
        //判断是否退出了
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //2、msg.target == null 表示当前消息是屏障消息
                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、遍历消息链表找到最近的一条异步消息
                }
                if (msg != null) {
                    //4、判断消息有没有到处理时间
                    if (now < msg.when) {
                        //如果没有到处理时间则等待
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a 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 {
                    // No more messages.
                    // 如果没有消息就一直休眠,等待被唤醒。
                    nextPollTimeoutMillis = -1;
                }

                //。。。省略代码
            }
        }
    }

从上面代码可以看出工作流程如下:

第一步:通过 nativePollOnce(ptr, nextPollTimeoutMillis) 判断是否有新的消息要被处理,如果没有则阻塞。

第二步:判断当前消息是不是屏障消息,如果是屏障消息,则遍历消息链表找到最近的一条异步消息,并处理找到的这个异步消息。如果不是屏障消息,则正常处理该消息。需要注意的是,如果设置了屏障消息,那么MessageQueue就会一直循环的找该屏障消息后面有没有异步消息,如果没有,则会阻塞。而不会在处理同步消息。因为屏障消息没有target,因此屏障消息不会被处理。当你使用了屏障消息后,在不使用的时候一定要手动移除该屏障消息。否则,屏障消息后面的同步消息永远不会被处理。屏障消息前面的消息不受影响。

第三步:判断找到的消息有没有到处理时间,如果已到处理时间,则把该消息从链表中移除,并返回该消息给looper。如果没到处理时间,则阻塞。

Looper在收到 MessageQueue 返回的消息后,会调用 msg.target.dispatchMessage(msg) 来处理消息,target 也就是 Handler

代码语言:java复制
public void dispatchMessage(Message msg) {
        //如果msg设置了callback 就交给callback处理,这里的callback就是封装成的runnable
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果handler设置了mCallback,则优先交给mCallback处理。这个后面还会具体将。
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //回调到 Handler 的 handleMessage 方法
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }

从实现代码可以看出:

如果消息是封装的runnable,则调用runnable的run方法。如果handler设置了mCallback(见4.4),则优先交给mCallback处理,如果mCallback处理返回值为false,则handler可以继续处理。

3.5、小结

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

尝试小结一下它们的职责,如下:

  • Looper :负责关联线程以及消息的分发在该线程下**从 MessageQueue 获取 Message,分发给 Handler ;
  • MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;
  • Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。

线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。

四、 Handler 的延伸问题

Handler 虽然简单易用,但是要用好它还是需要注意一点,另外 Handler相关 还有些鲜为人知的知识技巧,比如 IdleHandler。

由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。

这些我会讲解一些,我没讲到的可以自行搜索相关内容进行了解。

4.1、Handler 引起的内存泄露原因以及最佳解决方案

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。

这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息

示例代码如下:

代码语言:javascript复制
private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}
复制代码

并且再在 Activity.onDestroy() 前移除消息,加一层保障:

代码语言:javascript复制
@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}
复制代码

这样双重保障,就能完全避免内存泄露了。

注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。

4.2、为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

前面我们提到了每个Handler 的线程都有一个 Looper ,主线程当然也不例外,但是我们不曾准备过主线程的 Looper 而可以直接使用,这是为何?

在 ActivityThread.main() 方法中有如下代码:

代码语言:javascript复制
//android.app.ActivityThread
public static void main(String[] args) {
  //...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

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

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

Looper.prepareMainLooper(); 代码如下:

代码语言:javascript复制
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

可以看到在 ActivityThread 里调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。

注意:Looper.loop() 是个死循环,后面的代码正常情况不会执行。

4.3、主线程的 Looper 不允许退出

如果你尝试退出 Looper ,你会得到以下错误信息:

代码语言:javascript复制
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)

why? 其实原因很简单,主线程不允许退出,退出就意味 APP 要挂。

4.4、Handler 里藏着的 Callback 能干什么?

在 Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?

来看看 Handler.dispatchMessage(msg) 方法:

代码语言:javascript复制
public void dispatchMessage(Message msg) {
  //这里的 callback 是 Runnable
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理

这个就很有意思了,这有什么作用呢?

我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!

场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。

4.5、创建 Message 实例的最佳方式

由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。

方法有二:

  1. 通过 Message 的静态方法 Message.obtain(); 获取;
  2. 通过 Handler 的公有方法 handler.obtainMessage();

4.6、子线程里弹 Toast 的正确姿势

当我们尝试在子线程里直接去弹 Toast 的时候,会 crash :

代码语言:javascript复制
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可,同理的还有 Dialog。

正确示例代码如下:

代码语言:javascript复制
new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

4.7、妙用 Looper 机制

我们可以利用 Looper 的机制来帮助我们做一些事情:

  1. 将 Runnable post 到主线程执行;
  2. 利用 Looper 判断当前线程是否是主线程。

完整示例代码如下:

代码语言:javascript复制
public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}

4.8、sendMessageDelayed原理

  1. sendMessageDelayed 最终是调用的 sendMessageAtTime
  2. 消息队列的插入是由msg.when顺序排列。如果当前的消息没有到执行时间,其后的也一定不会到,当前的系统时间小于msg.when,那么会计算一个timeout,在到执行时间时wake up。

4.9、只能保证在when之前消息不被处理,不能够保证一定在when时被处理

  1. 在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。
  2. 即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。
  3. 在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。

所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

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

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

4.11、handler postDelay 这个延迟是怎么实现的? 

handler.postDelay 并不是先等待一定的时间再放入到 MessageQueue 中,而是直接进入MessageQueue,以 MessageQueue 的时间顺序排列和唤醒的方式结合实现的。

五、总结

由前文可得出一些知识点,汇总一下,方便记忆。

  1. Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
  2. 在创建 Handler 之前一定需要先创建 Looper;
  3. Looper 有退出的功能,但是主线程的 Looper 不允许退出;
  4. 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;
  5. Runnable 被封装进了 Message,可以说是一个特殊的 Message;
  6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程;
  7. 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;

六、参考链接

Android中为什么主线程不会卡死

https://juejin.cn/post/6844903783139393550

0 人点赞