待写Android技术知识Android开发

1-6.1 Handler 工作原理简析及手写自定义Handle

2020-02-12  本文已影响0人  Shimmer_

[TOC]

1. Handler概述

Handler结构.png

Handler在安卓使用当中主要用于异步消息的处理,主要完成的功能:

2. 工作原理简析(一起送快递)

为帮助理解记忆,这里将handler的工作过程与快递的运输做上对比。handler在开发使用当中扮演的角色就好比作快递小哥,使用handler发送及处理消息就类似与小哥接收我们的快递或是送达我们的快递,在完成“收发快递”的工作当中,“小哥”背后还有着很多重要的角色来帮助我们实现该功能。

2.1 工作原理简述(如何送快递?)

使用handler发送消息,handler会将该消息放入消息队列MessageQueue(小哥取件完成),Looper启动并根据消息队列中消息的时间来决定分发的时间(快递分拣),取出消息并分发至handler进行消息处理(这里有点不同的是,发送及处理消息的handler是同一个,但同一个快递的取件小哥和送件小哥一般不是同一个,handler在将消息放入队列当中时将自身的引用也放至消息中,在分发处理时保证了同一个handler来处理该消息)

2.2 主要相关类(谁在帮你干活?)

1. Handler(快递小哥做什么?)

2. MessageQueue(快递仓库做什么?)

3. Looper(流水线做什么?)

4. Thread (快递公司做什么?)

当然是提供环境给实现消息功能啦;没有快递公司,小哥也没有了。

5. Message(快递做什么?)

消息就是记录信息等待被发送处理;作为一个快递,当然只能被,不能做啦!

3. 仿照源码通过自定义Handler来熟悉各功能重点

3.1 功能正常工作的基础 ——CustomLooper、CustomMessageQueue

想要发送消息,势必要有储存消息的消息队列及循环处理分发消息的Looper;handler在使用过程中是存在多线程交互的,这意味着,handler在其他任意子线程中发送的消息都会被存储到当前线程操作的消息队列中,这里要求第一个重点:MessageQueue与Looper的唯一性;这个唯一性由ThreadLocal来确定looper的唯一,由looper的唯一来确定MessageQueue的唯一这就保证了它的正常工作基础。第二个重点:Looper中有循环对消息队列取消息的操作,这是个死循环;这个死循环的阻塞需要由MessageQueue完成;

3.2 开始工作——CustomHandler、CustomMessage

基础工作环境都搭好了,现在便是进行工作了。这里一个重点:发送及处理消息的handler必须为同一个

3.2 自定义Handler工作流程

  1. Looper先准备
  2. 发送消息之后不要忘记启动
CustomLooper.prepare();
final CustomHandler customHandler = new CustomHandler() {
    @Override
    public void handleMessage(CustomMessage message) {
        Log.e(TAG, "receive:" + message.toString());
    }
};
for (int i = 0; i < 10; i++) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                CustomMessage message = new CustomMessage();
                String msg = Thread.currentThread().getName()+":第" + i + "次交互:send " + UUID.randomUUID().toString();
                Log.e(TAG, msg);
                message.setObject(msg);
                customHandler.sendMessage(message);
            }
        }
    }).start();
}
CustomLooper.loop();

4. 补充

4.1 ThreadLocal

ThreadLocal是为解决多线程程序的并发问题的一种解决方案

它为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立修改自己的副本并不会影响其它线程所对应的副本

可以简单将ThreadLoca当成一种特殊的HashMap,它的key固定为Thread,通过get和set方法来对Value做修改

4.2 主线程使用handler不用Looper准备和启动的原因

在ActivityThread的Main方法中已经准备和启动了

主线程Looper准备与启动.png

4.3 自定义Handler中死循环中阻塞会导致应用无响应,为什么主线程中Looper死循环不会?

主线程loop结束.png

主线程loop在取不到消息时会 退出循环,继而抛出throw new RuntimeException("Main thread loop unexpectedly exited");异常,退出应用,所以说应用在运行时,主线程的loop时会不断的接收消息处理消息的。

Android整体的交互都是有事件驱动的,looper.loop会不断的接收事件处理事件,如果它停止了应用也就停止了,所以一旦出现了ANR,说明是某个消息或者对某个消息的处理超时,阻塞了Looper.loop,从而造成ANR

自定义中死循环阻塞超时导致了主线程的loop阻塞超时引起ANR

而主线程中死循环正常运行是程序正常运行,相反它的死循环被阻塞超时则会引起ANR

4.4 子线程能更新UI的特殊情况

  • onResume执行之前更新UI
    主线程更新UI的检测是通过ViewRootImp的checkThread方法来检查的;
    ViewRootImpl是在onResume之后创建的
  • surfaceView
    SurfaceView在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况:相机预览,游戏

4.5 Handler使用当中的补充

  • Message的发送优化
    享元设计模式,使用obtainMessage 方便回收复用Message,避免新建太多耗费内存

  • 子线程创建Handler时需要准备Looper

  • 注意Handler使用不当造成的OOM
    为什么存在内存泄漏?

    //可能存在泄漏写法
    CustomHandler customHandler = new CustomHandler() {
        @Override
        public void handleMessage(CustomMessage message) {
            Log.e(TAG, "receive:" + message.toString());
        }
    };
    //原因:当使用内部类(包括匿名内部类)来创建Handler的时候,Handler对象会隐式的持有一个外部类对象(一般是Activity)的引用(处理消息时通常会操作Activity的方法或View),如果此时一个很耗时的后台线程在完成任务后通过消息机制通知handler(线程此时持有handler的引用)且Activity此时已被关闭,这就导致应该被销毁回收的Activity无法被GC回收,即内存泄漏;同理,在使用handler进行延时任务时,同样情况下也会造成内存泄漏。最终引起占用内存过高导致OOM
    //解决办法:使用静态内部类和弱引用。静态类不持有外部类的对象,所以Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)
    private static class MyHandler extends CustomHandler{
        WeakReference<Activity> weakReference;
    
        public MyHandler(Activity activity) {
            this.weakReference = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(CustomMessage message) {
    
        }
    }
    //其他方案:主动对Handler进行管理,在页面销毁时主动对耗时线程关闭,对延时任务取消
    
  • 注意使用Handler容易造成空指针异常
    handler在接收到消息进行处理时可能页面已经被销毁,引用资源找不到

5. 参考代码

SJDemoApp-important-handler

上一篇 下一篇

猜你喜欢

热点阅读