欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android的Handler消息机制

程序员文章站 2022-03-09 19:25:14
...

Handler消息机制

Android消息循环流程图

Android的Handler消息机制
image.png

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

主要涉及的角色如下所示:

  • message:消息。
  • MessageQueue:消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的Message。读取会自动删除消息,单链表维护,在表头插入,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
  • Looper:消息循环器,负责关联线程以及消息的分发,在该线程下从 MessageQueue获取Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。
  • Handler:消息处理器,负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

==整个消息的循环流程==还是比较清晰的,具体说来:

  • Handler通过sendMessage()发送消息Message到消息队列MessageQueue
  • Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理
  • target handler调用自身的handleMessage()方法来处理Message

总结

  • 在实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在,如果不存在则会报异常,也就是说==在创建 Handler 之前一定需要先创建 Looper==。
  • Handler 发送的消息由 MessageQueue 存储管理,并由 Looper 负责回调消息到 handleMessage()。
  • 线程的转换由 Looper 完成,==handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定==,不是创建handler的线程。
  • Looper 有退出的功能,但是主线程的 Looper 不允许退出;
  • 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;

问题

  1. Handler 引起的内存泄露原因以及最佳解决方案?

泄漏原因:handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。

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);
        }
    }
}
@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}
  1. 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者。在 ActivityThread.main() 方法中调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
在子线程中使用:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

Looper 提供了 Looper.prepare() 方法来创建 Looper ,并且会借助 ThreadLocal来实现与当前线程的绑定功能。Looper.loop() 则会开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler。也就是说 Handler 跟线程的关联是靠 Looper 来实现的。一个线程只能有一个Looper

  1. Handler 里的 Callback 是干什么的?

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

  1. 创建 Message 实例的最佳方式

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

  • 通过 Message 的静态方法 Message.obtain();
  • 通过 Handler 的公有方法 handler.obtainMessage()。
  1. 子线程里弹 Toast 的正确姿势?

在子线程弹Toast会报错,本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();
  1. Looper的妙用
  • 将 Runnable post 到主线程执行;
  • 利用 Looper判断当前线程是否是主线程。
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();
    }

}
  1. ==Android为什么要设置只能通过Handler机制更新UI==?
    最根本的问题解决多线程并发的问题;假设如果在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,马么会产生生么样的问题?——更新界面混乱;如果对更新UI 的操作都加锁处理的话会产生什么样子的问题?——性能下降。对于上述问题的考虑,Android提供了一套更新UI的机制,我们只需要遵循这样的机制就好了。 不用关心多线程的问题,更新UI的操作,都是在主线程的消息队列当中轮询处理的。

  2. HandlerThread的作用是什么?
    HandlerThread thread=new HandlerThread("handler thread");自动含等待机制,等Looper创建好了,才创建Handler,避免出现空指针异常。

  3. ==非UI线程真的不能更新UI吗==?

不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。

  1. 一个线程能否创建多个Handler,Handler跟Looper之间的对应关系
  • ==一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler==
  • 以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)
  1. Looper死循环为什么不会导致应用卡死?
  • 主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
  • 造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。
  • 阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
  1. 使用Handler的postDealy后消息队列会有什么变化?
  • 如果队列中==只有这个消息==,那么消息不会被发送,而是计算到时唤醒的时间,==先将Looper阻塞,到时间就唤醒它==。但如果此时==要加入新消息==,该消息队列的队头==跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大。==