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

Android 消息机制详解及实例代码

程序员文章站 2023-12-17 17:20:22
android 消息机制 1.概述 android应用启动时,会默认有一个主线程(ui线程),在这个线程中会关联一个消息队列(messagequeue),所有的操作都会...

android 消息机制

1.概述

android应用启动时,会默认有一个主线程(ui线程),在这个线程中会关联一个消息队列(messagequeue),所有的操作都会被封装成消息队列然后交给主线程处理。为了保证主线程不会退出,会将消息队列的操作放在一个死循环中,程序就相当于一直执行死循环,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数(handlermessage),执行完成一个消息后则继续循环,若消息队列为空,线程则会阻塞等待。因此不会退出。如下图所示:

Android 消息机制详解及实例代码

handler 、 looper 、message有啥关系?

在子线程中完成耗时操作,很多情况下需要更新ui,最常用的就是通过handler将一个消息post到ui线程中,然后再在handler的handlermessage方法中进行处理。而每个handler都会关联一个消息队列(messagequeue),looper负责的就是创建一个messagequeue,而每个looper又会关联一个线程(looper通过threadlocal封装)。默认情况下,messagequeue只有一个,即主线程的消息队列。

上面就是android消息机制的基本原理,如果想了解更详细,我们从源码开始看。

2.源码解读

(1)activitythread主线程中启动启动消息循环looper

public final class activitythread {
  public static void main(string[] args) {
    //代码省略
    //1.创建消息循环的looper
    looper.preparemainlooper();

    activitythread thread = new activitythread();
    thread.attach(false);
    if (smainthreadhandler == null) {
      smainthreadhandler = thread.gethandler();
    }
    asynctask.init();

    //2.执行消息循环
    looper.loop();
    throw new runtimeexception("main thread loop unexpectedly exited");
  }
}

activitythread通过looper.preparemainlooper()创建主线程的消息队列,最后执行looper.loop()来启动消息队列。handler关联消息队列和线程。

(2)handler关联消息队列和线程

public handler(callback callback, boolean async) {
    //代码省略
    //获取looper
    mlooper = looper.mylooper();
    if (mlooper == null) {
      throw new runtimeexception(
        "can't create handler inside thread that has not called looper.prepare()");
    }
    //获取消息队列
    mqueue = mlooper.mqueue;
  }

handler会在内部通过looper.getlooper()方法来获取looper对象,并且与之关联,并获取消息队列。那么looper.getlooper()如何工作的呢?

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

  public static @nonnull messagequeue myqueue() {
    return mylooper().mqueue;
  }
  public static void prepare() {
    prepare(true);
  }
  //为当前线程设置一个looper
  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));
  }
  //设置ui线程的looper
  public static void preparemainlooper() {
    prepare(false);
    synchronized (looper.class) {
      if (smainlooper != null) {
        throw new illegalstateexception("the main looper has already been prepared.");
      }
      smainlooper = mylooper();
    }
  }

在looper类中,mylooper()方法,通过sthreadlocal.get()来获取的,在preparemainlooper()中调用prepare()方法,在这个方法中创建了一个looper对象,并将对象设置了sthreadlocal()。这样队列就和线程关联起来了。通过sthreadlocal.get()方法,保证不同的线程不能访问对方的消息队列。

为什么要更新ui的handler必须在主线程中创建?

因为handler要与主线程的消息队列关联上,这样handlermessage才会执行在ui线程,此时ui线程才是安全的。

(3)消息循环,消息处理

消息循环的建立就是通过looper.loop()方法。源代码如下:

/**
   * run the message queue in this thread. be sure to call
   * {@link #quit()} to end the 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.");
    }
    //1.获取消息队列
    final messagequeue queue = me.mqueue;
    //2.死循环,即消息循环
    for (;;) {
      //3.获取消息,可能阻塞
      message msg = queue.next(); // might block
      if (msg == null) {
        // no message indicates that the message queue is quitting.
        return;
      }
      //4.处理消息
      msg.target.dispatchmessage(msg);
      //回收消息
      msg.recycleunchecked();
    }
  }

从上述程序我们可以看出,loop()方法的实质上是建立一个死循环,然后通过从消息队列中逐个取出消息,最后处理消息。对于looper:通过looper.prepare()来创建looper对象(消息队列封装在looper对象中),并且保存在sthreadlocal中,然后通过通过looper.loop()进行消息循环,这两步通常成对出现。

public final class message implements parcelable {
  //target处理
  handler target; 
  //runnable类型的callback
  runnable callback;
  //下一条消息,消息队列是链式存储的
  message next;
}

从源码中可以看出,target是handler类型。实际上就是转了一圈,通过handler发送消息给消息队列,消息队列又将消息分发给handler处理。在handle类中:

//消息处理函数,子类覆写
public void handlemessage(message msg) {
}

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

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

从上述程序可以看出,dispatchmessage只是一个分发的方法,如果run nable类型的callback为空,则执行handlemessage来处理消息,该方法为空,我们会将更新ui的代码写在该函数中;如果callback不为空,则执行handlecallback来处理,该方法会调用callback的run方法。其实这是handler分发的两种类型,比如post(runnable callback)则callback就不为空,当我们使用handler来sendmessage时通常不设置callback,因此,执行handlermessage。

 public final boolean post(runnable r)
  {
    return sendmessagedelayed(getpostmessage(r), 0);
  }

  public string getmessagename(message message) {
    if (message.callback != null) {
      return message.callback.getclass().getname();
    }
    return "0x" + integer.tohexstring(message.what);
  }

  public final boolean sendmessagedelayed(message msg, long delaymillis)
  {
    if (delaymillis < 0) {
      delaymillis = 0;
    }
    return sendmessageattime(msg, systemclock.uptimemillis() + delaymillis);

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

从上述程序可以看到,在post(runnable r)时,会将runnable包装成message对象,并且将runnable对象设置给message对象的callback,最后会将该对象插入消息队列。sendmessage也是类似实现:

public final boolean sendmessage(message msg)
  {
    return sendmessagedelayed(msg, 0);
  }

不管是post一个runnable还是message,都会调用sendmessagedelayed(msg, time)方法。handler最终将消息追加到messagequeue中,而looper不断地从messagequeue中读取消息,并且调用handler的dispatchmessage分发消息,这样消息就源源不断地被产生、添加到messagequeue、被handler处理,android应用就运转起来了。

3.检验

new thread(){
  handler handler = null;
  public void run () {
    handler = new handler();
  };
}.start();

上述代码有问题吗?

looper对象是threadlocal的,即每个线程都用自己的looper,这个looper可以为空。但是,当在子线程中创建handler对象时,如果looper为空,那么会出现异常。

public handler(callback callback, boolean async) {
    //代码省略
    //获取looper
    mlooper = looper.mylooper();
    if (mlooper == null) {
      throw new runtimeexception(
        "can't create handler inside thread that has not called looper.prepare()");
    }
    //获取消息队列
    mqueue = mlooper.mqueue;
  }

当mlooper为空时,抛出异常。这是因为looper对象没有创建,因此,sthreadlocal.get()会返回null。handler的基本原理就是要与messagequeue建立关联,并且将消息投递给messagequeue,如果没有messagequeue,则handler没有存在的必要,而messagequeue又被封住在looper中,因此创建handler时,looper一定不能为空。解决办法如下:

new thread(){
  handler handler = null;
  public void run () {
    //为当前线程创建looper,并且绑定到threadlocal中
    looper.prepare()
    handler = new handler();
    //启动消息循环
    looper.loop();
  };
}.start();

如果只创建looper不启动消息循环,虽然不抛出异常,但是通过handler来post或者sendmessage()也不会有效。因为虽然消息会被追加到消息队列,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

上一篇:

下一篇: