美文网首页待写Android技术知识Android开发
1-6.1 Handler 工作原理简析及手写自定义Handle

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

作者: Shimmer_ | 来源:发表于2020-02-12 12:09 被阅读0次

    [TOC]

    1. Handler概述

    Handler结构.png

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

    • 处理延时任务
    • 线程间通信

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

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

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

    • Handler ≈ 快递小哥
    • MessageQueue ≈ 快递仓库
    • Looper ≈ 快递分发流水线
    • Thread ≈ 快递公司
    • Message ≈ 快递

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

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

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

    • sendMessage ≈ 收快递
      handler拿来用就是为了发消息,小哥上门自然就是取快递嘛,当然发消息也有各种姿势

      Handler发送消息.png
    • enqueueMessage ≈ 放到仓库
      handler将消息放到消息队列,小哥自然是将快递放进仓库等待处理了

    • dispatchMessage ≈ 小哥被派去送快递

    • handleMessage ≈ 小哥送件上门
      消息已送到,接下来处理就看我们的了;小哥送完收工

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

    • enqueueMessage ≈ 收件存储
    • next ≈ 取出第一个需要出库处理的快递

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

    • prepare ≈ 准备一条能工作的流水线
    • loop ≈ 启动流水线
      启动之后,队列中的消息就会进行取出分发(由消息绑定的handler分发处理);快递分拣完成,小哥开始送件

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

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

    5. Message(快递做什么?)

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

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

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

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

    • CustomLooper

      1. 确保操作的Looper、MessageQueue唯一
      2. 完成准备、启动循环操作功能
      /**
       * Created by SJ on 2020/2/10.
       */
      public class CustomLooper {
          public CustomMessageQueue mQueue;
          public static ThreadLocal<CustomLooper> sThreadLocal = new ThreadLocal<>();
          private CustomLooper(){
              //只会初始化一次,保证当前线程Looper操作的MessageQueue唯一
              mQueue = new CustomMessageQueue();
          }
          /**
           * 准备工作
           * 初始化当前线程Looper
           */
          public static void prepare(){
              //保证当前线程的Looper唯一
              if (sThreadLocal.get()!=null) {
                  throw new RuntimeException("线程只能有一个CustomLooper");
              }
              sThreadLocal.set(new CustomLooper());
          }
          /**
           * 获取当前线程Looper
           */
          public static CustomLooper myLooper() {
              return sThreadLocal.get();
          }
          /**
           * 启动looper,开始操作MessageQueue
           */
          public static void loop(){
              final CustomLooper myLooper = myLooper();
              final CustomMessageQueue mQueue = myLooper.mQueue;
              for (;;){
                  CustomMessage message = mQueue.next();
                  if (message != null) {
                      message.target.dispatchMessage(message);
                  }
              }
          }
      }
      
    • CustomMessageQueue

      1. 实现消息的插入、取出
      2. 能够在无消息要被处理实现阻塞
      3. 消息的插入、取出是一个生产者-消费者模型,子线程操作插入、当前操作线程取出

      在这里是通过BlockingQueue(Java 提供的阻塞队列)实现生-消模型及阻塞功能,源码中不是,源码中还有更多细致的操作,例如根据消息需要处理时间的插入,取消息时的对比时间,存取时的互相唤醒等这些功能,这些之后在叙述

      /**
       * Created by SJ on 2020/2/10.
       */
      public class CustomMessageQueue {
      
          BlockingQueue<CustomMessage> queue;//实现仓库的阻塞功能
          public static final int MAX_COUNT = 10;
          public CustomMessageQueue() {
              queue = new ArrayBlockingQueue<>(MAX_COUNT);
          }
          /**
           * 往消息队列添加消息
           * @param message
           */
          public void enqueueMessage(CustomMessage message) {
              try {
                  queue.put(message);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          /**
           * 从消息队列取消息
           * @return
           */
          public CustomMessage next() {
              CustomMessage message = null;
              try {
                  message = queue.take();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              return message;
          }
      }
      

    3.2 开始工作——CustomHandler、CustomMessage

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

    • CustomHandler

      1. 发送、处理消息
      2. 插入,派发消息
      /**
       * Created by SJ on 2020/2/10.
       */
      public class CustomHandler {
          final CustomLooper looper;
          final CustomMessageQueue messageQueue;
          public CustomHandler() {
              //获取当前线程唯一的Looper
              this.looper = CustomLooper.myLooper();
              //获取当前线程唯一的MessageQueue
              this.messageQueue = looper.mQueue;
          }
          /**
           * 发送消息 
           */
          public void sendMessage(CustomMessage customMessage) {
              enqueueMessage(customMessage);
          }
          /**
           * 插入消息至消息队列
           * @param message
           */
          public void enqueueMessage(CustomMessage message) {
              //将handler与消息做绑定
              message.target = this;
              messageQueue.enqueueMessage(message);
          }
          /**
           * 分发消息
           * @param message
           */
          public void dispatchMessage(CustomMessage message) {
              handleMessage(message);
          }
          /**
           * 处理消息
           * @param message
           */
          public void handleMessage(CustomMessage message) {
      
          }
      }
      
    • CustomMessage

      1. 记录信息
      2. 绑定当前的操作Handler,保证操作Handler唯一
      /**
       * Created by SJ on 2020/2/10.
       */
      public class CustomMessage {
          public CustomHandler target;
          Object object;
          public CustomMessage() {
          }
          public void setObject(Object object) {
              this.object = object;
          }
          @NonNull
          @Override
          public String toString() {
              return object.toString();
          }
      }
      

    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

    相关文章

      网友评论

        本文标题:1-6.1 Handler 工作原理简析及手写自定义Handle

        本文链接:https://www.haomeiwen.com/subject/eowqfhtx.html