Android的Handler消息机制

Handler消息机制

Android消息循环流程图

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时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大。==
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vinson武

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值