关注公众号免费阅读全文,进入音视频开发技术分享群!

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 将两者进行绑定,绑定的过程分为三步:

  1. 检查被注册的 AHandler 的 id 是否为0,如果不为0说明该 AHandler 已经被注册过了,不会再被注册;
  2. 如果 AHandler 没有被注册过,那么赋予它新的 handler id,并且以列表的形式存储;
  3. 调用 AHandler 的 setID 方法记录它的 id 与 注册的 ALooper;

可以看到,ALooper 并没有存储当前与之绑定的 AHandler,只是在 AHandler 内存储有一个 ALooper 的弱引用。以上三个步骤分别可以引申出一个问题:

  1. 为什么一个 AHandler 只能注册到一个 ALooper 当中呢?答:为了能够线程同步,如果可以注册到两个 Looper 中,很有可能会有同时调用 Handler 方法的情况出现,这时候就要用锁来管理,代码会变得复杂,只注册到一个 Looper 当中可以保证所有的消息都是按顺序处理的;
  2. 为什么要将 ALooper 和 AHandler 存储到列表中呢?答:这是为了 debug,我们可以看 MediaPlayerService 这篇文章,里面用到 dumpsys media.player 会把当前进程中所有用到 AHandler 的 id,以及其对应的 ALooper 名字,和当前处理的消息条数打印出来,这也就是为什么 ALooperRoster 它是一个静态变量;
  3. 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,因为析构函数里面会自动帮助我们执行。

另外我们顺便看下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();
}

为什么需要 dup?

比如说在 MediaCodec 中,mNotify 这条消息可能要发送很多次,每次要传递的 what、 index 或者是其他内容会不一样,如果不进行 dup,那么填充一次之后要再使用就要 clear 原来 AMessage 中已经填充的内容,当然 AMessage 已经提供了 clear 方法;如果每次使用前先 dup,那么每次拿到的将会是一个新的 AMessage,并且 target 已经设定好了,这样用起来也一样方便。

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. 如果 AHandler 在使用前不去调用 registerHandler ,那么 AMessage 方法在 post 时将会有如下提示:“failed to post message as target looper for handler %d is gone.”。
  3. 我们在不需要使用一个 AHandler 时,或者要将其与另一个 ALooper 进行绑定时要调用 unregisterHandler 方法,将其从静态 ALooperRoster 中移除。

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

  1. Android异步消息机制

    Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...

  2. Android 面试收集录5 消息机制

    1.消息机制概述 1.1.消息机制的简介 在Android中使用消息机制,我们首先想到的就是Handler. 没错,Handler是Android消息机制的上层接口. Handler的使用过程很简单, ...

  3. 【转载】每个 Android 开发者必须知道的消息机制问题总结

    Android的消息机制几乎是面试必问的话题,当然也并不是因为面试,而去学习,更重要的是它在Android的开发中是必不可少的,占着举足轻重的地位,所以弄懂它是很有必要的.下面就来说说最基本的东西. ...

  4. JMS异步消息机制

    企业消息系统 Java Message Service 是由 Sun Microsystems 开发的,它为 Java 程序提供一种访问 企业消息系统 的方法.在讨论 JMS 之前,我们分来析一下企业 ...

  5. android 进程/线程管理(四)----消息机制的思考(自定义消息机制)

    关于android消息机制 已经写了3篇文章了,想要结束这个系列,总觉得少了点什么? 于是我就在想,android为什么要这个设计消息机制,使用消息机制是现在操作系统基本都会有的特点. 可是andro ...

  6. Android Handler 异步消息处理机制的妙用 创建强大的图片加载类(转)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 最近创建了一个群,方便大家交流,群号: ...

  7. Android Handler 异步消息处理机制的妙用 创建强大的图片载入类

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 近期创建了一个群.方便大家交流,群号: ...

  8. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

  9. Android线程与异步消息处理机制

    在程序开发时,对于一些比较耗时的操作,我们通常会为其开辟一个单独的线程来执行,这样可以尽可能的减少用户等待的时间.在Android中,默认情况下,所有的操作都是在主线程中进行的,这个主线程负责管理与U ...

  10. 【转载】Android异步消息处理机制详解及源码分析

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

随机推荐

  1. Native Drawing开发指导,实现HarmonyOS基本图形和字体的绘制

      场景介绍 Native Drawing模块提供了一系列的接口用于基本图形和字体的绘制.常见的应用场景举例: ● 2D图形绘制. ● 文本绘制. 接口说明 接口名 描述 OH_Drawing_Bit ...

  2. Linux&Ubuntu之更换服务器

    前言 更换网卡.主板上的板载网卡.主板是服务器硬件维护的常规操作.通常新换(板载)网卡的MAC地址会变更,而部分服务器更换主板也会导致板载网卡MAC地址变化.由于CAS(Ubuntu)系统会将新MAC ...

  3. JS解混淆

    JS解混淆 最近在整理之前和一些同伴的分享资料,发现时间已经过了好久,特此整理一些有价值的分享记录. JS混淆 学习js混淆可以逆向分析混淆和加密过程,实战可用于爬虫和渗透信息获取 本文档用于初步介绍 ...

  4. nginx重新整理——————http 模块中的请求过程[十一]

    前言 简单介绍一下http的一些指令. 正文 一般http的嵌套规则是这样的: http{ upstream{} split_clients {} map{} gep{} server{ if(){} ...

  5. css 文字溢出省略号

    前言 css 文字溢出后显示省略号,这是一个非常常规的操作,但是你会发现在网上很多给出的例子两行之后显示省略号,却没有用. 这是为什么呢?please look follow. 正文 在一行省略的: ...

  6. 性能透明提升 50%!SMC + ERDMA 云上超大规模高性能网络协议栈

    简介: 新的协议栈是不是重新发明轮子?一个协议栈能否解决所有问题?适配所有场景? 编者按:当前内核网络协议栈有什么问题?新的协议栈是不是重新发明轮子?一个协议栈能否解决所有问题?适配所有场景?本文整理 ...

  7. 来电科技:基于Flink+Hologres的实时数仓演进之路

    简介: 本文将会讲述共享充电宝开创企业来电科技如何基于Flink+Hologres构建统一数据服务加速的实时数仓 作者:陈健新,来电科技数据仓库开发工程师,目前专注于负责来电科技大数据平台离线和实时架 ...

  8. 数据智能构建管理平台Dataphin V2.9.4.3版本发布

    简介: Dataphin发布V2.9.4.3版本升级多项产品能力,该版本在产品功能和用户体验上都进行了优化和提升,旨在为用户提供更完善的产品能力和体验,以加速企业数据中台建设进程. -更多关于数智化转 ...

  9. 数字农业WMS库存操作重构及思考

    ​简介: 数字农业库存管理系统在2020年时,部门对产地仓生鲜水果生产加工数字化的背景下应运而生.项目一期的数农WMS中的各类库存操作均为单独编写.而伴随着后续的不断迭代,这些库存操作间慢慢积累了大量 ...

  10. ITIL4中的关键概念

    1.价值和价值共创 什么是价值 通俗表达:这有啥用? 正式表达:这能带来什么益处或起什么作用? 反问式求证: 假如没有的话,会有什么后果? 具体情境提问:如果缺少IT运维人员,业务系统会面临怎样的状况 ...