Android之Handler浅析

Handler相信每个从事Android开发的小伙伴都非常熟悉了, 最常用的场景就是在子线程中进行数据操作然后通过handler消息机制通知到UI线程来更新UI,地球人都知道在子线程中更新UI,一般情况下都会报错。每每出去面试被问到“handler原理”,“消息是怎么从子线程发送到主线程的”等等handler底层的实现,就懵逼了。

虽然网上关于分析handler的博客问丈夫非常多,已经有很多大佬分析的非常清晰了。这里分析主要是为了让自己加深理解,另一方面就是想分享所学知识。

Android消息循环流程图如下所示:

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

  • 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,并隐藏背后实现的细节。

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

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

事实上,在整个消息循环的流程中,并不止只有Java层参与,很多重要的工作都是在C++层来完成,我们来看看下图的这些类的调用它关系。

注:虚线表示关联关系,实现表示调用关系。

在这些类中MessageQueue 是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关系的桥梁是MessageQueue.

总结:

  • Handler发送的消息由MessageQueue存储管理,并由Looper负责回调消息到handlerMessage()
  • 线程的切换由Looper完成,handlerMessage()所在线程由Looper.loop()调用者所在线程决定。

下面来列举我们几个工作中或许都会遇到的问题。

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

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

解决的办法就是:将Handler定义为静态的内部类,在内部持有activity的弱引用,并在activity的ondestroy()中调用handler.removeCallbacksAndMessage(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);
        }
    }
}

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

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}

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

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

因此我们可以利用Callback这个拦截机制来拦截Handler的消息,如大部分插件化框架中的Hook ActivityThread.mH的处理。

主线程的Looper不允许退出

主线程不允许退出,退出就意味APP要挂了。

Handler里藏着Callback能干什么?

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

创建Message实例的最佳方式

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

  • 通过Message的静态方法Message.obtain()
  • 通过Handler的共有方法handler.obtainMessage()

子线程里弹Toast的正确姿势

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

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(MainActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

妙用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();
    }

}

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

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


七號座先生
17 声望0 粉丝