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

发布时间 - 2026-01-10 23:08:37    点击率:

Android 消息机制

1.概述

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

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()也不会有效。因为虽然消息会被追加到消息队列,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。

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


# Android  # 消息机制  # 消息机制详细介绍  # 消息机制分析  # android异步消息机制 从源码层面解析(2)  # android异步消息机制 源码层面彻底解析(1)  # 代码分析Android消息机制  # Android异步消息机制详解  # android线程消息机制之Handler详解  # android利用消息机制获取网络图片  # Android的消息机制  # Android消息机制Handler的工作过程详解  # 深入剖析Android消息机制原理  # Android 消息机制以及handler的内存泄露  # 从源码角度分析Android的消息机制  # 为空  # 会将  # 可以看出  # 在这个  # 链式  # 抛出  # 基本原理  # 自己的  # 类中  # 这是  # 情况下  # 也不  # 放在  # 也就  # 才是  # 子类  # 起来了  # 就不  # 才会  # 会在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中  Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】  Laravel如何升级到最新版本?(升级指南和步骤)  Laravel怎么使用artisan命令缓存配置和视图  html5如何实现懒加载图片_ intersectionobserver api用法【教程】  如何在云主机上快速搭建多站点网站?  Swift中swift中的switch 语句  如何快速搭建高效WAP手机网站?  如何在Tomcat中配置并部署网站项目?  Bootstrap整体框架之CSS12栅格系统  EditPlus中的正则表达式 实战(1)  WordPress 子目录安装中正确处理脚本路径的完整指南  jquery插件bootstrapValidator表单验证详解  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  千库网官网入口推荐 千库网设计创意平台入口  清除minerd进程的简单方法  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  Laravel如何实现API版本控制_Laravel版本化API设计方案  Laravel如何优化应用性能?(缓存和优化命令)  大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  网站建设要注意的标准 促进网站用户好感度!  如何在Windows虚拟主机上快速搭建网站?  *服务器网站为何频现安全漏洞?  Laravel如何使用Eloquent进行子查询  在线制作视频网站免费,都有哪些好的动漫网站?  如何使用 jQuery 正确渲染 Instagram 风格的标签列表  如何在腾讯云服务器快速搭建个人网站?  简历没回改:利用AI润色让你的文字更专业  中山网站推广排名,中山信息港登录入口?  百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭  php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】  Android滚轮选择时间控件使用详解  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  如何在自有机房高效搭建专业网站?  Laravel集合Collection怎么用_Laravel集合常用函数详解  Laravel定时任务怎么设置_Laravel Crontab调度器配置  Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】  Linux安全能力提升路径_长期防护思维说明【指导】  Laravel如何与Docker(Sail)协同开发?(环境搭建教程)  Laravel怎么生成二维码图片_Laravel集成Simple-QrCode扩展包与参数设置【实战】  东莞市网站制作公司有哪些,东莞找工作用什么网站好?  油猴 教程,油猴搜脚本为什么会网页无法显示?  如何快速搭建高效简练网站?  bing浏览器学术搜索入口_bing学术文献检索地址  Laravel如何为API生成Swagger或OpenAPI文档  Laravel如何集成Inertia.js与Vue/React?(安装配置)  怎么用AI帮你设计一套个性化的手机App图标?  香港服务器租用每月最低只需15元?  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置