Android 13 - Media框架 - 异步消息机制

2023-11-09

由于网上已经有许多优秀的博文讲解了Android的异步消息机制(ALooper/AHandler/AMessage那一套),而且流程也不是很复杂,所以这里将不会去讲代码流程。本篇将会记录学习过程中的疑问以及自己的解答,希望可以帮助有同样疑问的小伙伴们,如果理解有不对或者偏差,欢迎大家一起讨论。
本文中的代码参考自 http://aospxref.com/

1 总览

下图是按照我的理解绘制出的Android异步消息处理流程。过程很简单,总结起来:创建一条消息并指定消息处理对象,将消息放入队列中等待线程处理,线程找到处理对象并处理消息。

图1

以下是异步消息机制相关UML类图:

请添加图片描述


2 ALooper

2.1 start

ALooper的启动有RunningLocally异步处理两种模式,来看代码:

status_t ALooper::start(
        bool runOnCallingThread, bool canCallJava, int32_t priority) {
     if (runOnCallingThread) {
        mRunningLocally = true;
        do {
        } while (loop());
        return OK;
	}
	
	mThread = new LooperThread(this, canCallJava);
    status_t err = mThread->run(
            mName.empty() ? "ALooper" : mName.c_str(), priority);
}

start第一个参数为true时,会阻塞调用线程,这种用法见的比较少,可能会在main函数中使用,阻塞等待任务执行完成;第一个参数如果为false,则会开启一个线程执行loop函数,例如MediaCodec中有如下使用:

    mCodecLooper = new ALooper;
    mCodecLooper->setName("CodecLooper");
    err = mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
    mCodecLooper->registerHandler(mCodec);

2.2 registerHandler

从代码上来看,AHandlerALooper是没有特别大的联系的,ALooper执行AHandler中的onMessageReceived是通过AMessage中存储的AHandler wp获取的。那为什么这边要多调用一个registerHandler呢?

从上面的流程图中我们可以看到,ALooper中保存有一个类型为ALoopRoster静态成员。调用registerHandler时会给AHandler赋予一个id,并将其和ALooper成对储存。如果发现AHandler已经有id了,则说明该AHandler已经和其他ALooper绑定过了,不能再与当前ALooper进行绑定。

调用registerHandler的目的是为了线程同步,假想有两个ALooper在同时处理一个AHandler的事务,那么该Handler中的状态很有可能就发生错乱了,如果用锁来管理,则会变得异常复杂。

ALooper::handler_id ALooperRoster::registerHandler(
        const sp<ALooper> &looper, const sp<AHandler> &handler) {
    Mutex::Autolock autoLock(mLock);
    if (handler->id() != 0) {
        CHECK(!"A handler must only be registered once.");
        return INVALID_OPERATION;
    }
    HandlerInfo info;
    info.mLooper = looper;
    info.mHandler = handler;
    ALooper::handler_id handlerID = mNextHandlerID++;
    mHandlers.add(handlerID, info);
    handler->setID(handlerID, looper);
    return handlerID;
}

2.3 stop

这里我们要注意的是stop之前我们要先执行unregisterHandler。如果是程序结束,我们可以不用去单独执行stop,因为析构函数里面会自动帮助我们执行。

另外我们顺便看下stopThread的用法:

status_t ALooper::stop() {
	thread->requestExit();
	thread->requestExitAndWait();
}

requestExit是设置flag让线程结束,但是线程并不一定会立即结束;requestExitAndWait是会阻塞等待线程结束。


3 AMessage

3.1 AMessage存储类型

AMessage通过Union的特性实现存储多种类型的数据:

struct AMessage : public RefBase {
private:
    struct Item {
        union {
            int32_t int32Value;
            int64_t int64Value;
            size_t sizeValue;
            float floatValue;
            double doubleValue;
            void *ptrValue;
            RefBase *refValue;
            AString *stringValue;
            Rect rectValue;
        } u;
        const char *mName;
        size_t      mNameLength;
        Type mType;
        void setName(const char *name, size_t len);
        Item() : mName(nullptr), mNameLength(0), mType(kTypeInt32) { }
        Item(const char *name, size_t length);
    };
	
	std::vector<Item> mItems;
}

3.2 dup

AMessage给我们提供了一个深拷贝的方法dup,这个方法经常会在Callback中使用到,例如MediaCodec中有如下例子:

void BufferCallback::onInputBufferAvailable(
     size_t index, const sp<MediaCodecBuffer> &buffer) {
     sp<AMessage> notify(mNotify->dup());
     notify->setInt32("what", kWhatFillThisBuffer);
     notify->setSize("index", index);
     notify->setObject("buffer", buffer);
     notify->post();
}

3.3 postAndAwaitResponse

这是让AMessage变成同步消息的方法,方法中会创建一个replyID,也称为Token。消息处理过程和直接调用post方法类似,不同的是执行完postAMessage加入到ALooper的队列中之后,会阻塞等待。

status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        return -ENOENT;
    }
    sp<AReplyToken> token = looper->createReplyToken();
    if (token == NULL) {
        return -ENOMEM;
    }
    setObject("replyID", token);
    looper->post(this, 0 /* delayUs */);
    return looper->awaitResponse(token, response);
}

为什么要创建这个AReplyToken对象呢?

我们在线程中处理完消息之后,有结果要返回给ALooperawaitResponse阻塞等待,结果返回给ALooper就可以返回给调用者),这里有两个问题:1)如何找到处理消息的ALooper? 2)什么时候返回结果,结束阻塞?

第一个问题很好解决,AMessage中就存储有ALooper的弱引用,可以通过promote来找到ALooper;当然通过createReplyToken方法创建的AReplyToken对象中也存储有ALooper的弱引用,同样也可以拿到ALooper对象。

第二个问题就需要AReplyToken来处理了,ALooper会阻塞等待AReplyToken被填充数据。

status_t ALooper::awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response) {
    Mutex::Autolock autoLock(mRepliesLock);
    CHECK(replyToken != NULL);
    while (!replyToken->retrieveReply(response)) {
        {
            Mutex::Autolock autoLock(mLock);
            if (mThread == NULL) {
                return -ENOENT;
            }
        }
        mRepliesCondition.wait(mRepliesLock);
    }
    return OK;
}

status_t ALooper::postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &reply) {
    Mutex::Autolock autoLock(mRepliesLock);
    status_t err = replyToken->setReply(reply);
    if (err == OK) {
        mRepliesCondition.broadcast();
    }
    return err;
}

AReplyToken是如何被填充数据的?参考MediaCodec,先调用senderAwaitsResponse从被处理的AMessage中拿到AReplyToken,接着创建一个新的AMessage用于存储返回结果,当然如果没有结果返回,可以不填充任何东西,接着call AMessage 的postReply方法,这里会层层调用将结果填到AReplyToken当中,最后结束阻塞返回结果,完成同步调用。

void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
	switch (msg->what()) {
		case kWhatSetCallback:
		{
			sp<AReplyToken> replyID;
			CHECK(msg->senderAwaitsResponse(&replyID));
			sp<AMessage> response = new AMessage;
			response->postReply(replyID);
			break;
		}
	}
}		

到这儿Android异步消息处理机制中就学习结束了。

我这边还提供了一个学习demo可供下载 AMessageDemo

下载之后放到源码目录下编译之后,将生成的bin文件push到/system/bin下面,执行即可看到结果。

最后还有几点要注意:

  1. 尝试在PlayerDemo的构造函数中执行registerHandler方法,这里会出现空指针的错误,需要等到构造函数执行完成才能够registerHandler
  2. 尝试把main函数中的registerHandler注释掉,可以看到消息仍旧可以正常运行。这是如预期的,因为处理消息时并没有检查AHandler是否已经注册。
  3. 既然不需要registerHandler AHandler就可以正常工作,那为什么还要它和 unregisterHandler呢?其实上文中已经提到了,为了让 AHandler 只处理一个 ALooper 的工作,从而实现同步调用。registerHandler 会给 AHandler 分配 id,将有 id 的 AHandler 再注册到其他 ALooper 中时将会报错。

以上内容的理解存在问题,后续将做修改,pending

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 13 - Media框架 - 异步消息机制 的相关文章

随机推荐

  • 浅谈当下火热的ChatGPT

    ChatGPT这个词语从今年初发布以来 一度成为一个火热的概念 包括CSDN也推出了C知道 不少大佬博友纷纷蹭热点 发布了无数关于ChatGPT的技术 使用 技能 展望未来的文档和博文 其实很多文章我都没有看过 只知道当下这个工具非常的火
  • 快速入门Spring Cloud Hystrix(服务降级、服务熔断、服务监控)

    文章目录 前言 一 服务雪崩 1 服务雪崩概述 2 造成服务雪崩的原因 3 如何防止雪崩 二 Spring Cloud Hystrix 1 什么是Spring Cloud Hystrix 豪猪哥 2 搭建测试环境 1 创建cloud pro
  • Java 23种设计模式通俗理解

    文章目录 工厂方法 建造者模式 抽象工厂 原型模式 单态模式 适配器模式 桥梁模式 合成模式 装饰模式 门面模式 享元模式 代理模式 责任链模式 命令模式 解释器模式 迭代模式 调停者模式 备忘录模式 观察者模式 状态模式 策略模式 模板方
  • 刀具半径补偿 c语言,数控铣床编程时刀具半径补偿指令及运用

    摘要 本文分析了刀具半径补偿概念及指令 如何灵活和合理地运用刀补值 正确编制加工程序以保证数控加工的有效性和准确性等问题 关键词 数控铣床编程 刀具半径补偿指令 一 刀具半径补偿的概念 在数控铣床上进行轮廓加工时 由于铣刀的刀位点通常是定在
  • mysql查询语句提示Unknown column ‘xxx’ in ‘where clause’

    今天写接口自动化测试 在运行结果中提示Unknown column xxx in where clause 的问题 经过大神的指导 顿时明白其中缘由 如果sql中定义的类型是int型的可以不用加引号 但是如果是字符串类型的 必须加引号 例如
  • C++&QT day1

    思维导图
  • CTF(Web方向练习题)(持续更新)

    1 Training WWW Robots 打开应用场景 如下 网址后面添加 robots txt 查看其中内容 robots协议也叫robots txt 统一小写 是一种存放于网站根目录下的ASCII编码的文本文件 内容如下 根据提示 访
  • numpy.random.choice坑

    numpy random choice 默认是有放回 其中有个 replace 的参数控制 默认是 True 如划分数据集用到 注意重复 Code import numpy as np a np arange 5 print a for i
  • Go-Gateway反向代理,性能比拼Nginx

    前言 在以前的一篇文章 基于Fasthttp实现的Gateway 性能媲美Nginx 中 介绍给大家一款使用Go语言开发的实现反向代理功能的开源项目boot4go gateway boot4go gateway项目以fasthttp作为ht
  • gcc入门及合并静态库

    1 gcc入门 1 gcc即是linux下c c 的编译器 gcc经常用的的选项有 c o c表示只编译 compile 源文件但不链接 会把 c或 cc的c源程序编译成目标文件 二进制文件 一般是 o文件 o用于指定输出 out 文件名
  • C++:内联函数

    1 概念 以inline修饰的函数叫做内联函数 编译时C 编译器会在调用内联函数的地方展开 没有函数调用建立栈帧的开销 内联函数提升程序运行的效率 加inline与未加inline的效果可以通过反汇编查看 由于我使用的是VS2022 我先介
  • VScode使用之搭建linux开发环境

    使用SSH链接linux VScode链接的方法参考如下文章 VScode使用之ssh链接虚拟机 安装C C 插件 安装CMake插件 使用CMake构建项目 新建工程文件夹 main c inc fun h src fun c 快捷键Ct
  • 创建表 DATE类型 DEFAULT默认值

    date类型直接用CURRENT TIMESTAMP或curdate 会报错 是因为在MySQL默认你输入的是一个常量 而不能是一个表达式 如果必须要使用表达式则应该将该表达式整个用小括号包括起来 curdate DDL语句显示如下 参考
  • 【满分】【华为OD机试真题2023B卷 JAVA&JS】乱序整数序列两数之和绝对值最小

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 乱序整数序列两数之和绝对值最小 知识点排序数组 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 给定一个随机的整数 可能存在正整数和负整数 数组 nums 请你在该数组
  • Ubuntu 虚拟机无法连接网络、不显示网络图标、没有网络设置

    Ubuntu20 无法连接网络 不显示网络图标 没有网络设置 出现的问题 在VMWare中使用Ubuntu系统时 通常需要设置网络连接但是有时会出现问题 右上角的网络连接的小图标不见了 网络也没有连接 ifconfig也看不到网卡 解决办法
  • Vue2面试题100问

    Vue2面试题100问 Vue2面试题100问 1 简述一下你对Vue的理解 2 声明式和命令式编程概念的理解 3 Vue 有哪些基本特征 4 vue之防止页面加载时看到花括号解决方案有哪几种 5 Vue中v for与v if能否一起使用
  • XML字体配置

    文章目录 一 前言 二 XML字体配置 2 1 创建字体系列 font family 2 2 在XML中使用字体资源 2 3 在编码中使用字体资源 2 4 使用支持库实现 一 前言 在以往的开发中 开发者都是将字体文件放在assets目录下
  • 分库分表 21 招

    一 好好的系统 为什么要分库分表 咱们先介绍下在分库分表架构实施过程中 会接触到的一些通用概念 了解这些概念能够帮助理解市面上其他的分库分表工具 尽管它们的实现方法可能存在差异 但整体思路基本一致 因此 在开始实际操作之前 我们有必要先掌握
  • Linux下如何修改文件权限?(chmod/chown)

    目录 chmod 全称 change mode 修改文件的权限 最常见的修改权限的方式 chown 全称change owner 改变文件所有权 chgrp 用于设置文件的属组 前言 Linux 系统是一种典型的多用户系统 不同的用户处于不
  • Android 13 - Media框架 - 异步消息机制

    由于网上已经有许多优秀的博文讲解了Android的异步消息机制 ALooper AHandler AMessage那一套 而且流程也不是很复杂 所以这里将不会去讲代码流程 本篇将会记录学习过程中的疑问以及自己的解答 希望可以帮助有同样疑问的