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

news2024/11/25 18:31:27

网上有许多优秀的博文讲解了Android的异步消息机制(ALooper/AHandler/AMessage那一套),希望看详细代码流程的小伙伴可以去网上搜索。这篇笔记将会记录我对android异步消息机制的理解,这次学完之后就可以熟练运用这套异步消息机制了。
本文中的代码参考自 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时,会阻塞调用线程;如果为false,则会开启一个线程执行loop函数。start方法默认使用第二种,也是用的比较多的一种;第一种可能会在main函数中使用,阻塞等待任务执行完成。

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

既然有registerHandler,那对应的也要有unregisterHandler,如果不执行,那这个AHandler就不能再处理事件了。

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下面,执行即可看到结果。

最后有一点要注意,我尝试在PlayerDemo的构造函数中执行registerHandler方法,这里会出现空指针的错误,需要等到构造函数执行完成才能够register。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/647768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库二】数据库用户管理与授权

数据库用户管理与授权 1.MySQL数据库管理1.1 常用的数据类型1.2 char和varchar区别1.3 SQL语句分类 2.数据表高级操作2.1 克隆表2.2 清空表2.3 创建临时表 3.MySQL的六大约束4.外键约束4.1 外键概述4.2 创建主从表4.3 主从表中插入数据4.4 主从表中删除数据4.5 删除外键约束 5.…

conda环境中配置cuda+cudnn+pytorch深度学习环境

本文参考&#xff1a; 在conda虚拟环境中配置cudacudnnpytorch深度学习环境&#xff08;新手必看&#xff01;简单可行&#xff01;&#xff09;_conda安装cudnn_江江ahh的博客-CSDN博客 一、创建虚拟环境 conda create -n mytorch python3.8 二、执行sudo nvidia-smi查看CU…

物联网通信技术

通信的技术指标是什么&#xff1f;AB A. 可靠性 B. 有效性 C. 实时性D. 广覆盖 多路复用技术有哪些&#xff1f;ABCD A. FDMA B. CDMA C. SDMA D. TDMA 使用多个频率来传输信号的技术被称为扩展频谱技术&#xff0c;该技术使用的目的是什么&#xff1f; AB A. 抗干扰B. 提…

【VMware】VMware安装CentOS8-Stream虚拟机

本文首发于 慕雪的寒舍 VMware安装CentOS8-Stream虚拟机 1.安装VMware 由于最新版的vm要钱&#xff0c;这里提供一个VMware16pro的安装包&#xff1b;我知道度盘下载速度慢&#xff0c;但确实没啥其他选择&#xff0c;见谅。 后文将用vm来简称VMware 提取嘛: gdt9 亚索包解…

解决UGUI的图集导致Shader采样时UV错误的问题

大家好&#xff0c;我是阿赵。 在我们用UGUI的时候&#xff0c;很多时候需要通过在UI上面挂材质球&#xff0c;写Shader&#xff0c;来实现一些特殊的效果。 这里句一个很简单的例子&#xff0c;只为说明问题。 一、简单例子说明 这个例子是这样的&#xff0c;我想在某个Imag…

Python模块openpyxl 操作Excel文件

简介 openpyxl是一个用于读取和编写Excel 2010 xlsx/xlsm/xltx/xltm文件的Python库。openpyxl以Python语言和MIT许可证发布。 openpyxl可以处理Excel文件中的绝大多数内容&#xff0c;包括图表、图像和公式。它可以处理大量数据&#xff0c;支持Pandas和NumPy库导入和导出数据。…

chatgpt赋能python:Python本地安装库:一个简单易懂的指南

Python本地安装库&#xff1a;一个简单易懂的指南 Python是一种高级的编程语言&#xff0c;它拥有庞大的社区支持和无数的第三方库。如果你在使用Python时需要一些额外的功能&#xff0c;那么你可能需要安装一些库。本文将介绍如何在本地安装库&#xff0c;以及一些需要注意的…

chatgpt赋能python:如何更新Python库?Python更新库完全指南

如何更新Python库&#xff1f;Python更新库完全指南 Python作为一种最受欢迎的编程语言&#xff0c;其库和工具的数量是惊人的。这些库是Python生态系统的重要组成部分&#xff0c;以便帮助开发人员解决不同类型的问题。然而&#xff0c;这些库会更新&#xff0c;开发人员需要…

什么是椭圆曲线上的加法

椭圆曲线图形示例 注意&#xff0c;椭圆曲线随着你参数的不同&#xff0c;有不同的形态&#xff0c;这里仅是一种示例&#xff0c;详细的关于椭圆曲线的知识可以后附扩展知识连接 椭圆曲线上的加法 椭圆曲线上的加法不是我们通常意义上的数值加法&#xff0c;而是一种特殊的几…

干翻Mybatis源码系列之第十篇:Mybatis Plugins基本概念

给自己的每日一句 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽…

Oracle中的行列互转———pivot、unpivot函数用法

一、需求说明 项目开发过程中涉及到oracle数据库的数据操作&#xff1b;但是需要将数据进行列的互转&#xff0c;通过查阅资料可知在oracle中有三种方式可以实现行列互转&#xff1a; ①使用decode 函数&#xff1b; ②使用case when 函数&#xff1b; ③使用pivot函数&…

Linux之设置主机名

目录 Linux之设置主机名 查看主机名 语法格式 案例 修改主机名 语法格式 案例 --- 修改静态主机名为joker 配置静态解析 为Linux主机指派域名解析 Linux之设置主机名 查看主机名 语法格式 hostnamectl [status] [--static|--transient|--pretty] 解析&#xff1a; s…

极致呈现系列之:Echarts地图的浩瀚视野(一)

目录 Echarts中的地图组件地图组件初体验下载地图数据准备Echarts的基本结构导入地图数据并注册展示地图数据结合visualMap展示地图数据 Echarts中的地图组件 Echarts中的地图组件是一种用于展示地理数据的可视化组件。它可以显示全国、各省市和各城市的地图&#xff0c;并支持…

整形在内存中的存储-原码补码反码的理解与应用

目录 一、概论 1.1 C语言中基本的数据类型 1.2 类型的基本归类 二、整形在内存中的存储 2.1 原码、反码、补码 2.2 存储补码和大小端存储 三、计算各基本数据类型的范围计算原理 3.1 有符号类型的整形范围 3.2 无符号类型的整形范围 3.3 例题 一、概论 C语言提供了非常…

【Java基础学习打卡07】Java语言概述

目录 引言一、Java语言1.Java语言简介2.Java语言优势3.Java能做什么&#xff1f; 二、Java之父三、Java简史1.Java版本时间线2.Java发展重要节点 总结 引言 一、Java语言 1.Java语言简介 Java语言是一种以面向对象为基础的高级编程语言。吸收了C语言的各种优点&#xff0c;又…

【IMX6ULL驱动开发学习】06.APP与驱动程序传输数据+自动创建设备节点(hello驱动)

一、APP与驱动之间传输数据 /*驱动从APP获取数据*/ unsigned long copy_from_user(void *to, const void *from, unsigned long n)/*驱动传输数据到APP*/ unsigned long copy_to_user(void *to, const void *from, unsigned long n)二、使用copy_to_user、copy_from_user在AP…

32908字长文理解Large CV Model:Segment Anything

作者&#xff1a;猛码Memmat 目录 Abstract1. IntroductionTaskModelData engineDatasetResponsible AIExperimentsRelease 2. Segment Anything TaskTaskPre-trainingZero-shot transferRelated tasksDiscussion 3. Segment Anything ModelImage encoderPrompt encoderMask de…

十个实用MySQL函数

函数 0. 显示当前时间 命令&#xff1a;。 作用: 显示当前时间。 应用场景: 创建时间&#xff0c;修改时间等默认值。 例子&#xff1a; 1. 字符长度 命令&#xff1a;。 作用: 显示指定字符长度。 应用场景: 查看字符长度时。 例子&#xff1a; 2. 日期格式化 命令…

【群智能算法改进】一种改进的白鲸优化算法 改进白鲸优化算法 改进后的EBWO[1]算法【Matlab代码#40】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始BWO算法2. 改进的白鲸优化算法EBWO2.1 Logistic映射2.2 透镜成像折射方向学习 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 原始BWO算法 白鲸优化算法 (BWO&…

LeetCode —— 206. 反转链表

LeetCode —— 206. 反转链表 一、题目描述&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1a;head …