Android 必备知识之Handler机制

Handler是我们常用的一种通信方式,可用于子线程更新UI

对于Handler我们需要知道的有以下四种对象

  • Handler:用于分发消息
  • MessageQueue:用于存储Message
  • Message:通信的消息
  • Looper:一个消息循环机制

一:Handler的基本使用

我们使用Handler的时候,一般是这么使用的(有多种使用方式,这里简单的举例)

// MainActivity.kt
val handler: Handler = Handler(
  Handler.Callback {
    when (it.what) {
      1 -> {
        // doSomething
      }
      else -> {}
    }
    true         
  }
)

// 在其他地方调用
val obtain = Message.obtain()
obtain.what = 1
handler.sendMessage(obtain)

先新建一个Handler,并声明回调,在回调中根据传递的数据做不同的动作,之后在任何一个地方都可以使用handler对象的sendMessage()方法,发送数据,数据会在之前声明的回调中接收到

二:源码分析

为什么我们的Handler可以这么使用呢?

对于这个问题,我觉得我们学习Android的有必要知道一下,起码以后面试可能也有用处。

我们可以一步一步来分析

首先我们使用Handler就必须创建一个Handler,我们查看Handler的构造函数

// Handler.java

// 本例用的即此构造函数
public Handler(Callback callback) {
  this(callback, false);
}

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

此时我们可以看到1位置上Looper.myLooper()这是去获取Looper对象,Looper是一个循环机制,可以不断的从队列中拿到消息进行发送,我们来看下这个对象是怎么获取到的

// Looper.java

public static @Nullable Looper myLooper() {
  return sThreadLocal.get();
}

看到这里,可能大家会疑惑这sThreadLocal是什么鬼,其实这是一个ThreadLocal对象,我们可以简单理解下,就是它可以让我们每个线程都有一个属于自己的局部变量,隶属于线程,在当前线程中使用sThreadLocal.get()都可以拿到线程中唯一的局部变量,可能介绍的不是那么容易懂,其实它的源码也很简单,有兴趣的大家可以去看看,绝对让你一下子就能理解的。

注意:使用Looper也是有规定的,需要先调用Looper.prepare(),然后才能调用Looper.loop(),为什么?请看下面的解释

一般我们使用Looper,我们如果想要让子线程一直运行,并随时在子线程中处理数据
则可以这么做

Thread {
  Looper.prepare()
    mThreadHandler = Handler(Looper.myLooper()) {
      // 处理数据
    
      true
    }
    Looper.loop()
}.start()

如果我们不使用Looper.prepare(),继而使用Looper.loop()则程序会报错

// Looper.java

public static void loop() {
  final Looper me = myLooper();
  if (me == null) {
     throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  ...
}

那么问题来了,我们的Handler是在主线程实现的,既然能一直接收消息,证明主线程是有Looper存在,并且执行了Looper.prepare()Looper.loop(),不然程序就会奔溃,那么这玩意到底是怎么在哪里创建出来的呢?

答案是在ActivityThread类的main函数创建出来的

// ActivityThread.java

public static void main(String[] args) {
  ...     
  Looper.prepareMainLooper(); // 1
  ...
  ActivityThread thread = new ActivityThread();
  thread.attach(false, startSeq);
  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  if (false) {
    Looper.myLooper().setMessageLogging(new
    LogPrinter(Log.DEBUG, "ActivityThread"));
  }
  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop(); // 2
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到这里的12分别调用了我们想要看到的Looper.prepare()Looper.loop(),至于这个ActivityThread是什么,我们现在只需要知道它就是一个手机中每个应用的入口,正如我们运行Java程序时需要调用main()入口函数一样,这个类中也提供了一个main()函数,就是应用的入口函数,在这个入口函数中,就创建了我们需要的主线程Looper对象,所以我们直接在主线程实例化Handler对象就是使用的这个Looper对象,并可以一直接收消息

既然Looper循环有了,Handler就能一直接收消息了。

接下来我们从发送数据开始看起

val obtain = Message.obtain()
obtain.what = 1
handler.sendMessage(obtain)

以上就是创建一个Message然后发送出去,我们可能也常用handler.post(Runnable r),其实查看源码可以得知不管是用哪一种方式去发送数据,最后调用到Handler对象的一个方法

// Handler.java

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; // 1
    if (mAsynchronous) {
      msg.setAsynchronous(true);
     }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到1这里需要发送的msg的target指定了这个Handler(这里要记住),然后再用这个queue发送一个数据出去
那么这里又疑惑了,这个queue又是哪里来的,也就是HandlermQueue对象从哪里来,其实上面的代码就已经提示了,在Handler的初始化构造函数里面就有

// Handler.java

mQueue = mLooper.mQueue;

这时候我们可以理一下就是,Looper一直循环,Looper里面有一个消息队列MessageQueue,发送数据都是通过这个Looper里面的一个对象mQueue发送的

接下来我们看这个消息队列是怎么发数据的,通过queue.enqueueMessage(msg, uptimeMillis);我们去看下MessageQueue类里面的enqueueMessage方法

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
  ...
  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;

    // 1,先获取当前准备要发送的消息
    Message p = mMessages; 
    boolean needWake;
    // 2,如果当前要准备的消息是空的,或者我们设置的时间是0
    //   意思就是此时消息队列里面是空的,或者我们设置的时间小于这个当前准备发送的消息
    //   则交换下顺序,把发送的消息和当前准备发送的消息调换一下顺序,即先发我们要发送的消息
    if (p == null || when == 0 || when < p.when) { // 2
      // 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();

      // 3,如果不能插入到当前准备发送的消息前面,则往后面依次进行排序,根据这个when时间
      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;
}

代码中标明了注释,仔细品品就知道这个queue.enqueueMessage(msg, uptimeMillis);,与其说是发送数据,更确切的应该说是对消息根据时间进行排序

那么我们真正的发送消息是在哪里呢?

我们知道Looper是一个可以带有循环机制的东西,Looper.loop即开始进行循环,我们可以看下这里面的代码,这个方法里面有很长代码,大家要挑重点看

// Looper.java
 
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;
        ...
        // 1,此时一直循环从队列中获取消息
        for (;;) {
            
            // 2,获取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            try {
                // 3,开始分发消息,此时的target即Handler
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }

我们看代码注释2这个地方,调用queue.next,取出一个消息,然后注释3这块就是使用调用消息里面的target对象,由前面的代码分析可以知道这个target就是发送消息的handler,此时就把消息分发出去了,接下来我们可以查看下这个MessageQueuenext方法,和HandlerdispatchMessage方法

MessageQueuenext方法

// MessageQueue.java

Message next() {
  ...
  for (;;) {
    if (nextPollTimeoutMillis != 0) {
      Binder.flushPendingCommands();
    }
    // 1,nativePollOnce 方法用于“等待”, 直到下一条消息可用为止,当没有消息的时候会阻塞住
    nativePollOnce(ptr, nextPollTimeoutMillis);
    
    synchronized (this) {
      // Try to retrieve the next message.  Return if found.
      final long now = SystemClock.uptimeMillis();
      Message prevMsg = null;
      Message msg = mMessages;

      // 一般的,我们的msg.target是不会为空的,所以可以略过这条判断
      if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
          prevMsg = msg;
          msg = msg.next;
         } while (msg != null && !msg.isAsynchronous());
       }
       // 2,开始取消息
       if (msg != null) {
         // 情况一:还没到消息需要发送的时间,则等待这个间隔时间
         if (now < msg.when) {
           // Next message is not ready.  Set a timeout to wake up when it is ready.
           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
         } 
         // 情况二:到了发送的时间了,则把下一个需要发送的消息赋值给mMessage
         //   并返回这个时候需要发送的消息
         else {
           // Got a message.
           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;
         }
       } else {
         // No more messages.
         nextPollTimeoutMillis = -1;
       }

       // Process the quit message now that all pending messages have been handled.
       if (mQuitting) {
         dispose();
         return null;
       }
       ...
     }
   }
}

这个方法注释写了很清楚,总的来说就是没有消息则阻塞等待,有消息的就取出消息

HandlerdispatchMessage方法

// Handler.java

public void dispatchMessage(Message msg) {
  // 一般我们还可以使用Handler.post(Runnable r)方法发送消息
  // 这个时候的msg.callback 就是这个 r
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

这个方法就简单了,如果我们设置了回调,则会回调出去,具体执行什么方法要看你怎么发送这个消息。

上面我们举的例子是这样发的

val obtain = Message.obtain()
obtain.what = 1
handler.sendMessage(obtain)

如果使用上面的这个handler.sendMessage(Message msg)则会传消息给我们初始化的时候的回调函数

val handler: Handler = Handler(
  Handler.Callback {
    when (it.what) {
      1 -> {
        // doSomething
      }
      else -> {}
    }
    true         
  }
)

还有另外一种发送消息的方法,就是使用post(Runnable r)发送

handler?.post {
  object : Runnable {
    override fun run() {

    }
  }
}

我们可以看下这个post方法

public final boolean post(Runnable r){
  return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
  Message m = Message.obtain();
  m.callback = r;
  return m;
}

可以看到这里有个赋值,m.callback = r,所以我们post发送数据后对应的就是运行这个r

public void dispatchMessage(Message msg) {
  // 一般我们还可以使用Handler.post(Runnable r)方法发送消息
  // 这个时候的msg.callback 就是这个 r
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    ...
  }
}

private static void handleCallback(Message message) {
  message.callback.run();
}

直接运行rrun方法

解决了上面两种数据接收方式,还剩下一个handleMessage(msg)

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    ...
    handleMessage(msg);
  }
}

这个用于我们创建一个类,继承自Handler

class Myhandler : Handler() {
  override fun handleMessage(msg: Message?) {
  }
}

这个时候使用如下方式发送消息,则会在上面重写的方法handleMessage中接收到消息

val obtain = Message.obtain()
obtain.what = 1
myHandler.sendMessage(obtain)

分析到这,基本算是把Handler机制给理清了,看下来可能有点吃力,不过如果大家自己跟着源码一步一步走下去,自己也是可以理解清楚的

总结

Handler机制:

1,Looper消息循环机制 创建

使用Looper.prepare(),创建一个Looper实例,并保存在Looper的一个静态变量sThreadLocal中,然后用Looper.loop()开启消息循环,此时创建的Handler可以传指定的Looper实例进去,如果是主线程的创建的Handler,则已经有Looper实例了,此时就不需要创建Looper实例

2,Handler发送消息

使用Handler.sendMessage(Message msg),或者Handler.post(Runnable r)发送消息,此时会调用HandlerenqueueMessage(),里面调用了MessageQueueenqueueMessage,此时就把消息根据发送时间进行了排序

3,Looper.loop() 循环,检测消息并发送

真实的发送放在了Looper.loop()方法里面,此时会调用Message.next()方法,获取一个消息,如果没有消息,next()方法则会阻塞等待,释放CPU,一旦有消息,则会唤醒,并在Looper.loop()方法里面进行消息的发送,然后消息发送又调用了Handler.dispatchMessage()方法,此时我们就可以接收到消息了

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