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<? extends Handler> 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)

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

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流程图.png

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

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

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

  • messageQueue 负责存取消息.

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容