欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android Handler工作原理解析

程序员文章站 2023-12-12 15:35:22
简介 在android 中,只有主线程才能操作 ui,但是主线程不能进行耗时操作,否则会阻塞线程,产生 anr 异常,所以常常把耗时操作放到其它子线程进行。如果在子线...

简介

在android 中,只有主线程才能操作 ui,但是主线程不能进行耗时操作,否则会阻塞线程,产生 anr 异常,所以常常把耗时操作放到其它子线程进行。如果在子线程中需要更新 ui,一般是通过 handler 发送消息,主线程接受消息并且进行相应的逻辑处理。除了直接使用 handler,还可以通过 view 的 post 方法以及 activity 的 runonuithread 方法来更新 ui,它们内部也是利用了handler 。在上一篇文章 android asynctask源码分析 中也讲到,其内部使用了 handler 把任务的处理结果传回 ui 线程。

本文深入分析 android 的消息处理机制,了解 handler 的工作原理。

handler

先通过一个例子看一下 handler 的用法。

public class mainactivity extends appcompatactivity {
  private static final int message_text_view = 0;
  
  private textview mtextview;
  private handler mhandler = new handler() {
    @override
    public void handlemessage(message msg) {
      switch (msg.what) {
        case message_text_view:
          mtextview.settext("ui成功更新");
        default:
          super.handlemessage(msg);
      }
    }
  };

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
    toolbar toolbar = (toolbar) findviewbyid(r.id.toolbar);
    setsupportactionbar(toolbar);

    mtextview = (textview) findviewbyid(r.id.text_view);

    new thread(new runnable() {
      @override
      public void run() {
        try {
          thread.sleep(3000);
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
        mhandler.obtainmessage(message_text_view).sendtotarget();
      }
    }).start();

  }
}

上面的代码先是新建了一个 handler的实例,并且重写了 handlemessage 方法,在这个方法里,便是根据接受到的消息的类型进行相应的 ui 更新。那么看一下 handler的构造方法的源码:

public handler(callback callback, boolean async) {
  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());
    }
  }

  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;
}

在构造方法中,通过调用 looper.mylooper() 获得了 looper 对象。如果 mlooper 为空,那么会抛出异常:"can't create handler inside thread that has not called looper.prepare()",意思是:不能在未调用 looper.prepare() 的线程创建 handler。上面的例子并没有调用这个方法,但是却没有抛出异常。其实是因为主线程在启动的时候已经帮我们调用过了,所以可以直接创建 handler 。如果是在其它子线程,直接创建 handler 是会导致应用崩溃的。

在得到 handler 之后,又获取了它的内部变量 mqueue, 这是 messagequeue 对象,也就是消息队列,用于保存 handler 发送的消息。

到此,android 消息机制的三个重要角色全部出现了,分别是 handler 、looper 以及 messagequeue。 一般在代码我们接触比较多的是 handler ,但 looper 与 messagequeue 却是 handler 运行时不可或缺的。

looper

上一节分析了 handler 的构造,其中调用了 looper.mylooper() 方法,下面是它的源码:

static final threadlocal<looper> sthreadlocal = new threadlocal<looper>();

public static @nullable looper mylooper() {
  return sthreadlocal.get();
}

这个方法的代码很简单,就是从 sthreadlocal 中获取 looper 对象。sthreadlocal 是 threadlocal 对象,这说明 looper 是线程独立的。

在 handler 的构造中,从抛出的异常可知,每个线程想要获得 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));
}

同样很简单,就是给 sthreadlocal 设置一个 looper。不过需要注意的是如果 sthreadlocal 已经设置过了,那么会抛出异常,也就是说一个线程只会有一个 looper。创建 looper 的时候,内部会创建一个消息队列:

private looper(boolean quitallowed) {
  mqueue = new messagequeue(quitallowed);
  mthread = thread.currentthread();
}

现在的问题是, looper看上去很重要的样子,它到底是干嘛的?
回答: looper 开启消息循环系统,不断从消息队列 messagequeue 取出消息交由 handler 处理。

为什么这样说呢,看一下 looper 的 loop方法:

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;

  // make sure the identity of this thread is that of the local process,
  // and keep track of what that identity token actually is.
  binder.clearcallingidentity();
  final long ident = binder.clearcallingidentity();
  
  //无限循环
  for (;;) {
    message msg = queue.next(); // might block
    if (msg == null) {
      // no message indicates that the message queue is quitting.
      return;
    }

    // this must be in a local variable, in case a ui event sets the logger
    printer logging = me.mlogging;
    if (logging != null) {
      logging.println(">>>>> dispatching to " + msg.target + " " +
          msg.callback + ": " + msg.what);
    }

    msg.target.dispatchmessage(msg);

    if (logging != null) {
      logging.println("<<<<< finished to " + msg.target + " " + msg.callback);
    }

    // make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    final long newident = binder.clearcallingidentity();
    if (ident != newident) {
      log.wtf(tag, "thread identity changed from 0x"+ long.tohexstring(ident) + " to 0x"+ long.tohexstring(newident) + " while dispatching to "+ msg.target.getclass().getname() + " "+ msg.callback + " what=" + msg.what);
    }

    msg.recycleunchecked();
  }
}

这个方法的代码有点长,不去追究细节,只看整体逻辑。可以看出,在这个方法内部有个死循环,里面通过 messagequeue 的next() 方法获取下一条消息,没有获取到会阻塞。如果成功获取新消息,便调用 msg.target.dispatchmessage(msg),msg.target是 handler 对象(下一节会看到),dispatchmessage 则是分发消息(此时已经运行在 ui 线程),下面分析消息的发送及处理流程。

消息发送与处理

在子线程发送消息时,是调用一系列的 sendmessage、sendmessagedelayed 以及 sendmessageattime 等方法,最终会辗转调用sendmessageattime(message msg, long uptimemillis),代码如下:

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 在消息队列中插入一条消息,在 enqueuemessage总中,会把 msg.target 设置为当前的handler 对象。

消息插入消息队列后, looper 负责从队列中取出,然后调用 handler 的 dispatchmessage 方法。接下来看看这个方法是怎么处理消息的:

public void dispatchmessage(message msg) {
  if (msg.callback != null) {
    handlecallback(msg);
  } else {
    if (mcallback != null) {
      if (mcallback.handlemessage(msg)) {
        return;
      }
    }
    handlemessage(msg);
  }
}

首先,如果消息的 callback 不是空,便调用 handlecallback 处理。否则判断 handler 的 mcallback 是否为空,不为空则调用它的 handlemessage方法。如果仍然为空,才调用 handler 自身的 handlemessage,也就是我们创建 handler 时重写的方法。

如果发送消息时调用 handler 的 post(runnable r)方法,会把 runnable封装到消息对象的 callback,然后调用sendmessagedelayed,相关代码如下:

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;
}

此时在 dispatchmessage中便会调用 handlecallback进行处理:

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

可以看到是直接调用了 run 方法处理消息。

如果在创建 handler时,直接提供一个 callback 对象,消息就交给这个对象的 handlemessage 方法处理。callback 是 handler内部的一个接口:

public interface callback {
  public boolean handlemessage(message msg);
}

以上便是消息发送与处理的流程,发送时是在子线程,但处理时 dispatchmessage 方法运行在主线程。

总结

至此,android消息处理机制的原理就分析结束了。现在可以知道,消息处理是通过 handler 、looper 以及 messagequeue共同完成。 handler 负责发送以及处理消息,looper 创建消息队列并不断从队列中取出消息交给 handler, messagequeue 则用于保存消息。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: