Handler消息处理机制

Handler + Looper + Message + Message Queue关系

基本概念
  • Message:消息对象
  • MessageQueue:存储消息对象的队列
  • Looper:负责循环读取MessageQueen中的消息,读到消息之后就把消息交给Handler去处理。
  • Handler:发送消息和处理消息
基本方法
相互关系
详细关系

下面从源码角度分别看下各个对象的作用。

Looper

要想使用Handler ,首先要保证当前所在线程存在Looper对象。主线程不需要主动创建Looper对象是因为主线程已经为你准备好了,详见android.app.ActivityThread->Looper.prepareMainLooper()
我们创建的子线程如果想用Handler接收数据,需要先通过Looper.prepare()创建Looper

 Looper.prepare();
 // 创建Handler并传入
 Looper.loop()
Looper构造方法
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}

创建Looper对象的时候,同时创建了MessageQueue,并让Looper绑定当前线程。但我们从来不直接调用构造方法获取Looper对象,而是使用Looperprepare()方法。prepare()使用ThreadLocal保存当前Looper对象,ThreadLocal类可以对数据进行线程隔离,保证了在当前线程只能获取当前线程的Looper对象,同时prepare()保证当前线程有且只有一个Looper对象,间接保证了一个线程只有一个MessageQueue对象。
MessageQueue 只是一个消息的存储单元,它不能去处理消息,Looper填补了这个功能,Looper会以无限循环的模式去查看Message中是否有新消息,否则就一直等待。

Looper的prepare()
private static void prepare(boolean quitAllowed) {
  if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
  }
  sThreadLocal.set(new Looper(quitAllowed));
}
Looper开启循环
public static void loop() {
  final Looper me = myLooper();
  if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  final MessageQueue queue = me.mQueue;
  Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();
  for (;;) {
    // might block 也许会堵塞,一会在next方法中解析
    Message msg = queue.next();
    if (msg == null) {
      return;
    }
    try {
      msg.target.dispatchMessage(msg);
    } finally {
      if (traceTag != 0) {
        Trace.traceEnd(traceTag);
      }
    }
    msg.recycleUnchecked();
  }
}

Lopper通过loop()开启无限循环,通过MessageQueuenext()获取message对象。一旦获取就调用msg.target.dispatchMEssage(msg)msg交给Handler对象处理(msg.targetHandler对象),最后回收。

Handler

Hanlder实例化
public Handler(Callback callback, boolean async) {
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

实例化过程中获取当前线程的MessageQueue对象,以便于将消息加入MessageQueue

发送消息
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  if (queue == null) {
    RuntimeException e = new RuntimeException(
        this + " sendMessageAtTime() called with no mQueue");
    Log.w("Looper", e.getMessage(), e);
    return false;
  }
  return enqueueMessage(queue, msg, uptimeMillis);
} 
将消息加入队列
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  msg.target = this;
  if (mAsynchronous) {
    msg.setAsynchronous(true);
  }
  return queue.enqueueMessage(msg, uptimeMillis);
}

enqueueMessage()中首先为msg.target赋值为this,为发送消息出队列交给Handler处理埋下伏笔。

处理消息
public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

前面我们提到Looper.loop()获取到消息时会调用HandlerdispatchMessage()方法进行处理,Handler处理消息就是调用我们重写的handleMessage()方法,或者我们可以在创建Handler实例时实现Callback接口,一样可以处理从MessageQueue出来的消息.

MessageQueue

MessageQueue 构造方法
MessageQueue(boolean quitAllowed) {
  mQuitAllowed = quitAllowed;
  mPtr = nativeInit();
}

MessageQueue初始化过程同时初始化底层的NativeMessageQueue对象,并且持有NativeMessageQueue的内存地址(long)。
MessageQueue它内部存储了一组消息,以队列的形式对外提供插入和删除的工作。内部结构不是真正的队列,而是采用单链表的数据结构来存储消息列表。

MessageQueue的next()
Message next() {
  final long ptr = mPtr;
  if (ptr == 0) {
    return null;
  }
  int pendingIdleHandlerCount = -1; // -1 only during first iteration
  int nextPollTimeoutMillis = 0;
  for (;;) {
    if (nextPollTimeoutMillis != 0) {
      Binder.flushPendingCommands(); // 刷一下,就当是Android系统的一种性能优化操作
    }
    nativePollOnce(ptr, nextPollTimeoutMillis); // native底层实现堵塞,堵塞状态可被新消息唤醒,头一次进来不会延迟
    synchronized (this) {
      final long now = SystemClock.uptimeMillis();
      Message prevMsg = null;
      Message msg = mMessages;//获取头节点消息
      if (msg != null && msg.target == null) {
        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;
          if (DEBUG) {
            Log.v(TAG, "Returning message: " + msg);
          }
          msg.markInUse();
          return msg;   // 直接出队列返回给looper
        }
      } else {
        nextPollTimeoutMillis = -1; // 队列已无消息,一直堵塞
      }
      if (mQuitting) {
        dispose();
        return null;
      }
      pendingIdleHandlerCount = 0;
      nextPollTimeoutMillis = 0;
    }
  }
}

虽然looper也开启了循环,但是到了真正干活的时候它却调用了MessageQueuenext(),要想搞明白怎么个堵塞,先看这三个对应的条件:

  • nextPollTimeoutMillis=0 不堵塞
  • nextPollTimeoutMillis<0 一直堵塞
  • nextPollTimeoutMillis>0 堵塞对应时长,可被新消息唤醒

next()中,因为消息队列是按照延迟时间排序的,所以先考虑延迟最小的也就是头消息。当头消息为空,说明队列中没有消息了,nextPollTimeoutMIllis就被赋值为-1,当头消息延迟时间大于当前时间,堵塞消息要到延迟时间和当前时间的差值
当消息延迟时间小于等于0,直接返回msgHandler处理
nativePollOnce(ptr, nextPollTimeoutMillis)方法是native底层实现堵塞逻辑,堵塞状态会到时间唤醒,也可被新消息唤醒,一旦唤醒会重新获取头消息,重新评估是否堵塞或者直接返回消息

消息入栈enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
  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) {
    if (mQuitting) {
      IllegalStateException e = new IllegalStateException(
                msg.target + " sending message to a Handler on a dead thread");
      Log.w(TAG, e.getMessage(), e);
      msg.recycle();
      return false;
    }

    msg.markInUse();
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) {
      // New head, wake up the event queue if blocked.
      msg.next = p;
      mMessages = msg;
      needWake = mBlocked;
    } else {
      // Inserted within the middle of the queue.  Usually we don't have to wake
      // up the event queue unless there is a barrier at the head of the queue
      // and the message is the earliest asynchronous message in the queue.
      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; // invariant: p == prev.next
      prev.next = msg;
    }

    // We can assume mPtr != 0 because mQuitting is false.
    if (needWake) {
        nativeWake(mPtr);
    }
  }
  return true;
}

消息入栈时,首先会判断新消息如果是第一个消息 或者 新消息没有延迟 或者 新消息延迟时间小于队列第一个消息的,都会立刻对这个消息进行处理。只有当消息延迟大于队列头消息时,才会依次遍历消息队列,将消息按延迟时间插入消息队列响应位置。

Message

Message 初始化
public static Message obtain() {
  synchronized (sPoolSync) {
    if (sPool != null) {
      Message m = sPool;
      sPool = m.next;
      m.next = null;
      m.flags = 0; // clear in-use flag
      sPoolSize--;
      return m;
    }
  }
  return new Message();
}

建议使用obtain()获取Message对象,因为Message维护着一个消息池,这个消息池的数据结构是单向链表,优先从池子里拿数据,如果池子里没有再创建对象。如果Message对象已存在,可以使用obtain(msg)方法,最终也会调用obtain()

消息的回收
void recycleUnchecked() {
  flags = FLAG_IN_USE;
  what = 0;
  arg1 = 0;
  arg2 = 0;
  obj = null;
  replyTo = null;
  sendingUid = -1;
  when = 0;
  target = null;
  callback = null;
  data = null;

  synchronized (sPoolSync) {
    if (sPoolSize < MAX_POOL_SIZE) {
      next = sPool;
      sPool = this;
      sPoolSize++;
    }
  }
}

消息的回收不是将Message对象销毁,而是将Message对象的值恢复初始值然后放回池子,等待使用

因为android会频繁的使用Message的对象,使用“池”这种机制可以减少创建对象开辟内存的时间,更加高效的利用内存,因此"池"这种机制被应用于频繁大量使用的类对象的情况,我们常说的“线程池”也是基于同样的原理。

总结

  • 要想在当前线程使用Handler机制,首先确保当前线程存在Looper
  • Looper.parper()创建一个 当前线程的Looper对象,同时创建一个MessageQueue对象
  • 每个线程只有一个Looper对象和一个MessageQueue对象
  • Looper.loop()开始循环,没有msg情况下进入堵塞状态(-1)
  • Message对象最好通过Message.obtain()获得
  • Handler发送消息进入队列,如果没有延迟唤醒堵塞Looper获得msg,调用msg.targe.dispachMessage处理消息
  • 关闭Activity时如果栈中有未出栈的message,需清除handler.removeMessage(int)
  • 子线程不再使用Handler时,要调用loop.quit(),loop.quitSafely()
  • 虽然表面上看是Looper循环队列,并将messageHandler,但实际上是MessageQueuenext()去完成的,MessageQueue同时还承担消息的入队列,并对消息按照延迟时间从小到大进行了排序。鉴于MessageQueue如此大的工作量,在Android 2.3版本后,MessageQueuenext()方法的堵塞机制转移到native层去处理,也就是我们使用的nativePollOnce(ptr, nextPollTimeoutMillis)方法

异常Can't create handler inside thread that has not called Looper.prepare()

示例代码:

new Thread(new Runnable() {
  @Override
  public void run() {
    timer = new Timer(mTotalTime, TimeSetted.SECOND_TO_MILL);
    timer.start();
    Log.d(TAG,"Countdown start");
  }
}).start();
  • 出现异常:java.lang.RuntimeException: Can't create handler inside thread Thread that has not called Looper.prepare()崩溃
  • 报错原因:Timer底层采用的是Handler+Message实现,非主线程中没有开启Looper,而 Handler对象必须绑定Looper对象需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用。
  • 修复方法
    • 方案一:即在具体逻辑的前后加入Looper.perpare()Looper.loop()方法。
    • 方案二:通过Looper.getMainLooper(),获得主线程的Looper,将其绑定到此Handler对象上。

修复代码:

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.perpare(); // 增加部分
    timer = new Timer(mTotalTime, TimeSetted.SECOND_TO_MILL);
    timer.start();
    Log.d(TAG,"Countdown start");
    Looper.loop(); // 增加部分
  }
}).start();

结论:

  • Can't create handler inside thread Thread that has not called Looper.prepare()通常是在子线程中使用没有绑定Looperhandler时出现,只需在handler语句的前后加Looper.prepare()Looper.loop()方法即可。
  • 创建子线程Thread和使用AsyncTaskdoInBackground时,若没有new一个handler对象,通常不会出现这个错误。本人是由于在调用其他方法时用了handler导致报错。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容