Handler从应用到深入理解

概述:

handler消息机制是由MessageMessageQueueLooperHandler共同组成的一种消息通信机制。handler常用于同进程之间的不同线程的通信,因为线程之间共享内存地址空间。

角色说明:

  • message : 系统生成的消息
  • messageQueue:消息队列,其重要功能是对队列中的消息进行入队(messageQueue.enqueueMessage())和出队-
    (messageQueue.next())操作
  • handler: 消息辅助类,主要功能是向消息队列中发送各种事件消息和接受消息并处理消息
  • looper:不断循环执行消息出队操作(looper.loop()),按分发机制将消息分发给相应的目标handler进行处理

应用实例:

下面以在工作线程中创建handler对象为例来理清handler的工作原理

  Runnable mRunnable = new Runnable() {
    public Handler handler;

    @Override public void run() {
      //第一步
      Looper.prepare();
      //第二步
      handler = new Handler() {
        @Override public void handleMessage(Message msg) {
          // 消息回调===》逻辑处理...
        }
      };
      //第三步
      Looper.loop();
    }
  };

在工作线程创建handler的代码一共分3个步骤:

  1. 调用Looper.praper()
  2. 创建Handler对象
  3. 调用Looper.loop()

为什么要用这种形式来说明呢?因为这是一个典型的handler在工作线程处理消息的一种形式,接下来的分析将围绕这样的一段代码展开,从源码的角度来分析这三步都做了那些工作,以下源码基于Android 7.1.1版本。

looper.praper()

以下代码是looper.praper()的源码部分

  public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
    //该判断表明该方法只允许被调用一次,也就是每个线程只允许有一个Looper 对象,重复调用则会抛出异常    
   if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
    //创建Looper对象,保存到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.praper() 内部调用了prepare(boolean quitAllowed),quitAllowed为true表示这个Looper内部的消息循环操作允许退出,false表示不允许退出(quitAllowed变量可以暂时不用关注);上文代码提到了ThreadLocal,那么先大概了解下什么是ThreadLocal(这里不是所谓的本地线程)

ThreadLocal:线程本地存储区(Thread Local Storage 简称TLS),每个线程都有自己私有的变量存储区,不同线程之间不能彼此访问对方变量存储区域。ThreadLocal常用操作(既然是变量存储区,其操作无非就是CRUD):

set :

以下是 ThreadLocal.set(T value)的源码部分

  public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程存储区域
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      //(this)以当前ThreadLocal对象为key保存value到ThreadLocalMap中
      map.set(this, value);
    } else {
      //创建线程存储区域其实就是ThreadLocalMap类型的对象
      createMap(t, value);
    }
  }
  void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
  }

解释在源码部分已经给出,这里做下总结说明吧,加深印象。

先获取当前县城的变量存储区域对象,然后对它对非空判断,不为空则直接以当前对象对key进行变量存储操作,为空则先创建一个变量存储区域对象并做存储操作。

get

以下是 ThreadLocal.get()的源码部分

  public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取本地存储区域对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      //使用当前ThreadLocalMap为key取出相应的(T)value
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) return (T) e.value;
    }
    //初始化TLS本地存储区域的值
    return setInitialValue();
  }

  private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      map.set(this, value);
    } else {
      createMap(t, value);
    }
    return value;
  }
  protected T initialValue() {
    return null;
  }

以上就是ThreadLocal的概念及基本用法,可以看出LoopersThreadLocal是一个带有Looper泛型的静态变量.

接下来回到主线任务handler,来分析下Looper的构造函数做了那些操作

以下是Looper构造函数的部分源码

  private Looper(boolean quitAllowed) {
    //创建MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //获取当前线程对象
    mThread = Thread.currentThread();
  }

对,你没看错,就两行代码。
创建一个MessageQueue对象,获取当前线程对象。结合上文的分析:每个线程只允许调用一个Looper.preper(),也即是每个线程只允许有一个Looper对象,在Looper的构造函数中创建一个MessageQueue对象,所有此处得出一个结论:

线程、Looper、MessageQueue 一一对应(也就是一个线程有且仅有一个Looper一个MessageQueue

looper.loop()

以下是loop()的源码部分

  public static void loop() {
    //获取`ThreadLocal`存储的`Looper`对象
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //取出Looer对象中的消息队列
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //进入loop的无限循环体
    for (;;) {
       //取出`MessageQueue`中的下一条`message`,该方法可能会导致阻塞
      Message msg = queue.next();
      if (msg == null) { 
       //没有消息,则退出循环
        return;
      }
···
···
···
···
  
      try {

        //如果成功取出 `MessageQuenen`中的消息则进行消息分发
        msg.target.dispatchMessage(msg);

      } finally {
        if (traceTag != 0) {
          Trace.traceEnd(traceTag);
        }
      }
      //将Message放入消息池,以供循环使用
      msg.recycleUnchecked();
    }
  }

从源码可以看出loop()是在做轮询,不断进行如下操作,直到没有消息退出循环为止:

  • 读取MessageQueue的下一条Message
  • Message分发给targettarget是指相应的handler
  • 再把分发后的Message回收到消息池,以便重复利用

Looper.loop()源码是消息处理的核心部分

说到这里那么问题来了:

问题1.系统怎么知道究竟将消息分发给那个handler

问题2.在loop的循环体中调用Message msg = queue.next() 可能会造成阻塞,那我们平时在使用的时候为何又没有造成阻塞呢?

问题3. 在UI线程中并没有显式调用preper()、loop()等方法为何没有抛出异常?

以上问题在分析完整个流程之后再来解释。

分析完preperloop,接下来我们开始说说handler这个类,handler主要是用来发送消息,接受并处理消息;那么我们从handler 的构造函数开始分析它吧:

handler

以下是handler构造函数的部分源码

  public Handler() {
    //无参构造函数默认调用 Handler(Handler.Callback callback, boolean async)
    this(null, false);
  }

  public Handler(Handler.Callback callback, boolean async) {
    //匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露
    if (FIND_POTENTIAL_LEAKS) {
      final Class klass = getClass();
      if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass())
          && (klass.getModifiers() & Modifier.STATIC) == 0) {
        Log.w(TAG, "The following Handler class should be static or leaks might occur: "
            + klass.getCanonicalName());
      }
    }
    //获取当前线程Looper对象
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
          "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //获取该looper对象的消息队列
    mQueue = mLooper.mQueue;
    //回调方法
    mCallback = callback;
    //设置消息是否为异步处理方式
    mAsynchronous = async;
  }

无参的Handler构造函数会默认调用Handler(Handler.Callback callback, boolean async)
该构造函数做的几件事,流程还是比较明白的:

  • 获取当前线程Looper对象、
  • 从该Looper对象中获取与之对应的Messagequeue
  • 回调函数传值为null
  • 设置消息处理不为异步

到此handler创建的前期工作三步骤已经分析完
接下来就是在相应的线程中通过handler来发送和处理消息了

sendMessage()

发送消息可以调用多个方法,这里只是用sendMessage() 作为代表罢了


sendMessage(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendMessageDelayed(Message msg, long delayMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
sendMessageAtFrontOfQueue(Message msg)
enqueueMessage(queue, msg, uptimeMillis)

这么多种方法来发送消息,有什么不一样吗?
下面我们通过一张调用流程图来弄清这个问题

Handler从应用到深入理解_第1张图片
sendMessage.png

从结构图中可以看出所有的方法最终都会走到enqueueMessage方法中,所以我们只需要来看enqueueMessage的流程就行

enqueueMessage:

以下是enqueueMessage的部分源码

  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //this表示当前handler对象,所以此处的target指的是发送消息的handler对象
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }

enqueueMessage的作用就是将消息添加到消息队列中,其中uptimeMillis为系统当前的运行时间,不包括休眠时间,msg.target = this;代码中已有解释,不在赘述,由此可以解决上文中的问题1:

解决问题1.Handler发送消息时最终调用的enqueueMessage中会给发送的Messagetarget变量赋值为当前的handler(也就是发送消息的handler),然后系统会根据target来匹配接受消息的的handler对象是哪个

注意:通常情况下使用obtainMessage()来获取消息池中已经存在message对象,而不是去生成一个新的message对象,这样的一个好处是节约内存空间,提升代码执行效率

handMessage()

发送消息后,系统根据消息分发机制分发消息到相应的handler的handMessage函数中进行处理,像下面代码所示一样

   mHandler = new Handler() {
      @Override public void handleMessage(Message msg) {
        // TODO: 处理消息 
      }
    };

那么所谓的分发机制又是怎么样的呢?还记得上面提到的loop()方法吗?该方法的主要作用是循环遍历消息队列中是否有消息、如果没有则退出循环体,如果有则调用msg.target.dispatchMessage(msg)来进行消息分发,所以要弄清楚消息是如何分发的,就要弄清楚msg.target.dispatchMessage(msg)的工作流程;

以下是dispatchMessage的部分源码

  public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      //当Message存在回调方法时,调用msg.callback.run()方法;
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        //当handler中mCallback 变量不为null则mCallback的handleMessage()方法;
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      //调用handler自身的回调方法handleMessage()
      handleMessage(msg);
    }
  }

  public void handleMessage(Message msg) {
  }

dispatchMessage的分发有3个步骤:

  1. 判断msg是否有回调
  2. 判断handler的成员变量是否被赋值
  3. 调用handler的子类handleMessage,该方法是一个空方法,所以所有子类必须实现他来接受消息。如果mCallback.handleMessage返回true,则handler子类的handleMessage的就不会收到消息,文章开始的应用实例中的就属于 调用handler的子类handleMessage

到这里looperhandler的创建及消息的轮询、发送、接收等流程都分析完了。
下面要说的是messageQueue的工作原理,messageQueue是一个单向链表结构主要负责存取message对象,系统会根据message距离触发时间的长短决定该消息在队列中位置。下面从源码角度分析其对消息处理的流程。

以下是messageQueue的构造方法的源码部分

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

messageQueue的构造方法重点是 mPtr = nativeInit();所以大部分的工作那应该是在nativeInit()中处理了,但是nativeInit()是native代码,还记得上文的loop()方法吗?哈哈哈,多提几次才记得到,loop()的轮询体中会调用messageQueue.next()去进行真正的轮询操作,所以我们从messageQueue.next()开始分析,该方法的主要作用就是轮询取出message对象。

next():

以下是messageQueue.next()的部分源码

Message next() {
    final long ptr = mPtr;
   //当消息循环已经退出,则直接返回
    if (ptr == 0) {
        return null;
    }
   // 循环迭代的首次为-1
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 获取一条消息,并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                   //成功地获取MessageQueue中的下一条即将要执行的消息
                    return msg;  
                }
            } else {
                //没有消息
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //当消息队列为空,或者是消息队列的第一个消息时
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //没有idle handlers 需要运行,则循环并等待。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去掉handler的引用
            boolean keep = false;
            try {
                keep = idler.queueIdle();  //idle时执行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置idle handler个数为0,以保证不会再次重复运行
        pendingIdleHandlerCount = 0;
        //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
        nextPollTimeoutMillis = 0;
    }
}

enqueueMessage

添加一条消息到消息队列,以下是enqueueMessage的源码部分

boolean enqueueMessage(Message msg, long when) {
    // 每一个message都必须关联一个handler对象
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        //正在退出时,回收msg,加入到消息池
        if (mQuitting) {
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //当阻塞时需要唤醒
        } else {
            //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
        //消息没有退出,我们认为此时mPtr != 0
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

至此handler的全部流程已经分析完了。
此处需要一副图,他的名字叫流程图,上面BB了那么多,还不如一张图来的直接。

Handler从应用到深入理解_第2张图片
handler流程图.png

总结一下上面的流程分析:

  • handler负责发、接受和处理消息.

编码中通常的做法是在主线程中创建handler、接受消息、处理消息,在子线程中向主线程发送消息。

  • messageQueue 负责存取消息.

handler发出的消息均会被保存到消息队列messageQueue中.

  • looper负责对messageQueue 做轮询操作。

looper调用loop()方法一直轮询消息队列,并在消息出队时将其派发至对应的handler.

你可能感兴趣的:(Handler从应用到深入理解)