Android 13 - Media框架 - 异步消息机制
关注公众号免费阅读全文,进入音视频开发技术分享群!
b7693967-317e-4c46-96d3-d40d9d87e382
由于网上已经有许多优秀的博文讲解了Android的异步消息机制(ALooper/AHandler/AMessage那一套),而且流程也不是很复杂,所以这里将不会去讲代码流程。本篇将会记录学习过程中的疑问以及自己的解答,希望可以帮助有同样疑问的小伙伴们,如果理解有不对或者偏差,欢迎大家一起讨论。
本文中的代码参考自 http://aospxref.com/
1 总览
下图是按照我的理解绘制出的Android异步消息处理流程。过程很简单,总结起来:创建一条消息并指定消息处理对象,将消息放入队列中等待线程处理,线程找到处理对象并处理消息。
以下是异步消息机制相关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
为什么要 AHandler
在使用前先要调用 ALooper 的 registerHandler 方法呢?先来看代码:
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;
}
AHandler 和 ALooper 本是两个独立的个体,需要通过 registerHandler 将两者进行绑定,绑定的过程分为三步:
- 检查被注册的 AHandler 的 id 是否为0,如果不为0说明该 AHandler 已经被注册过了,不会再被注册;
- 如果 AHandler 没有被注册过,那么赋予它新的 handler id,并且以列表的形式存储;
- 调用 AHandler 的 setID 方法记录它的 id 与 注册的 ALooper;
可以看到,ALooper 并没有存储当前与之绑定的 AHandler,只是在 AHandler 内存储有一个 ALooper 的弱引用。以上三个步骤分别可以引申出一个问题:
- 为什么一个 AHandler 只能注册到一个 ALooper 当中呢?答:为了能够线程同步,如果可以注册到两个 Looper 中,很有可能会有同时调用 Handler 方法的情况出现,这时候就要用锁来管理,代码会变得复杂,只注册到一个 Looper 当中可以保证所有的消息都是按顺序处理的;
- 为什么要将 ALooper 和 AHandler 存储到列表中呢?答:这是为了 debug,我们可以看 MediaPlayerService 这篇文章,里面用到 dumpsys media.player 会把当前进程中所有用到 AHandler 的 id,以及其对应的 ALooper 名字,和当前处理的消息条数打印出来,这也就是为什么
ALooperRoster
它是一个静态变量; - AHandler 调用 setID 方法记录 ALooper,AMessage 如何将消息发送给 ALooper 呢?
第三个问题要看 AMessage
的代码:
void AMessage::setTarget(const sp<const AHandler> &handler) {
if (handler == NULL) {
mTarget = 0;
mHandler.clear();
mLooper.clear();
} else {
mTarget = handler->id();
mHandler = handler->getHandler();
mLooper = handler->getLooper();
}
}
我们在创建 AMessage 时可以给它指定处理自己的 AHandler,如果一开始没有指定,也可以在后期调用 setTarget 方法来指定,代码中可以看到 AMessage 存储有目标 ALooper 和 目标 AHandler 两者。
status_t AMessage::post(int64_t delayUs) {
sp<ALooper> looper = mLooper.promote();
if (looper == NULL) {
ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
return -ENOENT;
}
looper->post(this, delayUs);
return OK;
}
调用 post 方法就会将 AMessage 自身发送到 AHandler 绑定的 ALooper 当中;
void AMessage::deliver() {
sp<AHandler> handler = mHandler.promote();
if (handler == NULL) {
ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
return;
}
handler->deliverMessage(this);
}
Looper 处理完之后调用 AMessage 的 deliver 方法就可以将消息交给目标 AHandler 来处理,这样就形成了三者的绑定。
2.3 stop
这里我们要注意的是stop之前我们要先执行unregisterHandler。如果是程序结束,我们可以不用去单独执行stop,因为析构函数里面会自动帮助我们执行。
另外我们顺便看下stop中Thread
的用法:
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();
}
为什么需要 dup?
比如说在 MediaCodec 中,mNotify
这条消息可能要发送很多次,每次要传递的 what、 index 或者是其他内容会不一样,如果不进行 dup,那么填充一次之后要再使用就要 clear 原来 AMessage 中已经填充的内容,当然 AMessage 已经提供了 clear 方法;如果每次使用前先 dup,那么每次拿到的将会是一个新的 AMessage,并且 target 已经设定好了,这样用起来也一样方便。
3.3 postAndAwaitResponse
这是让AMessage
变成同步消息的方法,方法中会创建一个replyID,也称为Token。消息处理过程和直接调用post方法类似,不同的是执行完post将AMessage
加入到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
对象呢?
我们在线程中处理完消息之后,有结果要返回给ALooper
(awaitResponse阻塞等待,结果返回给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 方法,这里会出现空指针的错误,需要等到构造函数执行完成才能够 registerHandler 。
- 如果 AHandler 在使用前不去调用 registerHandler ,那么 AMessage 方法在 post 时将会有如下提示:“failed to post message as target looper for handler %d is gone.”。
- 我们在不需要使用一个 AHandler 时,或者要将其与另一个 ALooper 进行绑定时要调用 unregisterHandler 方法,将其从静态 ALooperRoster 中移除。
Android 13 - Media框架 - 异步消息机制的更多相关文章
- Android异步消息机制
Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...
- Android 面试收集录5 消息机制
1.消息机制概述 1.1.消息机制的简介 在Android中使用消息机制,我们首先想到的就是Handler. 没错,Handler是Android消息机制的上层接口. Handler的使用过程很简单, ...
- 【转载】每个 Android 开发者必须知道的消息机制问题总结
Android的消息机制几乎是面试必问的话题,当然也并不是因为面试,而去学习,更重要的是它在Android的开发中是必不可少的,占着举足轻重的地位,所以弄懂它是很有必要的.下面就来说说最基本的东西. ...
- JMS异步消息机制
企业消息系统 Java Message Service 是由 Sun Microsystems 开发的,它为 Java 程序提供一种访问 企业消息系统 的方法.在讨论 JMS 之前,我们分来析一下企业 ...
- android 进程/线程管理(四)----消息机制的思考(自定义消息机制)
关于android消息机制 已经写了3篇文章了,想要结束这个系列,总觉得少了点什么? 于是我就在想,android为什么要这个设计消息机制,使用消息机制是现在操作系统基本都会有的特点. 可是andro ...
- Android Handler 异步消息处理机制的妙用 创建强大的图片加载类(转)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 最近创建了一个群,方便大家交流,群号: ...
- Android Handler 异步消息处理机制的妙用 创建强大的图片载入类
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 近期创建了一个群.方便大家交流,群号: ...
- Android之消息机制Handler,Looper,Message解析
PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...
- Android线程与异步消息处理机制
在程序开发时,对于一些比较耗时的操作,我们通常会为其开辟一个单独的线程来执行,这样可以尽可能的减少用户等待的时间.在Android中,默认情况下,所有的操作都是在主线程中进行的,这个主线程负责管理与U ...
- 【转载】Android异步消息处理机制详解及源码分析
PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...
随机推荐
- Native Drawing开发指导,实现HarmonyOS基本图形和字体的绘制
场景介绍 Native Drawing模块提供了一系列的接口用于基本图形和字体的绘制.常见的应用场景举例: ● 2D图形绘制. ● 文本绘制. 接口说明 接口名 描述 OH_Drawing_Bit ...
- Linux&Ubuntu之更换服务器
前言 更换网卡.主板上的板载网卡.主板是服务器硬件维护的常规操作.通常新换(板载)网卡的MAC地址会变更,而部分服务器更换主板也会导致板载网卡MAC地址变化.由于CAS(Ubuntu)系统会将新MAC ...
- JS解混淆
JS解混淆 最近在整理之前和一些同伴的分享资料,发现时间已经过了好久,特此整理一些有价值的分享记录. JS混淆 学习js混淆可以逆向分析混淆和加密过程,实战可用于爬虫和渗透信息获取 本文档用于初步介绍 ...
- nginx重新整理——————http 模块中的请求过程[十一]
前言 简单介绍一下http的一些指令. 正文 一般http的嵌套规则是这样的: http{ upstream{} split_clients {} map{} gep{} server{ if(){} ...
- css 文字溢出省略号
前言 css 文字溢出后显示省略号,这是一个非常常规的操作,但是你会发现在网上很多给出的例子两行之后显示省略号,却没有用. 这是为什么呢?please look follow. 正文 在一行省略的: ...
- 性能透明提升 50%!SMC + ERDMA 云上超大规模高性能网络协议栈
简介: 新的协议栈是不是重新发明轮子?一个协议栈能否解决所有问题?适配所有场景? 编者按:当前内核网络协议栈有什么问题?新的协议栈是不是重新发明轮子?一个协议栈能否解决所有问题?适配所有场景?本文整理 ...
- 来电科技:基于Flink+Hologres的实时数仓演进之路
简介: 本文将会讲述共享充电宝开创企业来电科技如何基于Flink+Hologres构建统一数据服务加速的实时数仓 作者:陈健新,来电科技数据仓库开发工程师,目前专注于负责来电科技大数据平台离线和实时架 ...
- 数据智能构建管理平台Dataphin V2.9.4.3版本发布
简介: Dataphin发布V2.9.4.3版本升级多项产品能力,该版本在产品功能和用户体验上都进行了优化和提升,旨在为用户提供更完善的产品能力和体验,以加速企业数据中台建设进程. -更多关于数智化转 ...
- 数字农业WMS库存操作重构及思考
简介: 数字农业库存管理系统在2020年时,部门对产地仓生鲜水果生产加工数字化的背景下应运而生.项目一期的数农WMS中的各类库存操作均为单独编写.而伴随着后续的不断迭代,这些库存操作间慢慢积累了大量 ...
- ITIL4中的关键概念
1.价值和价值共创 什么是价值 通俗表达:这有啥用? 正式表达:这能带来什么益处或起什么作用? 反问式求证: 假如没有的话,会有什么后果? 具体情境提问:如果缺少IT运维人员,业务系统会面临怎样的状况 ...