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

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. c# 框架系列 ———— EFCore 模型篇 [一]

    前言 简单介绍一下EfCore 的模型篇 正文 内容来源: 配置模型 配置模型的方式,一种是fluent api 还一种是属性的方式. public class Blog { public int B ...

  2. mysql 重新整理——索引简介[七]

    前言 百度百科索引: 在关系数据库中,索引是一种单独的.物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单. ...

  3. c# webapi 自定义返回数据

    前言 webapi呢,我们创建的时候我给我们5个方法:get get(data)put post delete. 很明显,get,get(data)是用来获取,post用来修改数据的,put,dele ...

  4. Har 版本包发布

    新建 Module 注意名字最好是小写命名 oh-package.json5 修改 description 描述信息 在 Index.ets 导出对外暴露的组件 export{ TitleBar } ...

  5. 力扣150(java)-逆波兰表达式求值(中等)

    题目: 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除法只保留整数部分. 可以保证给定的逆波兰表达式 ...

  6. 实时计算 Flink 版总体介绍

    简介: 实时计算 Flink 版(Alibaba Cloud Realtime Compute for Apache Flink,Powered by Ververica)是阿里云基于 Apache ...

  7. Flink + Iceberg + 对象存储,构建数据湖方案

    ​简介: 上海站 Flink Meetup 分享内容,如何基于Flink.对象存储.Iceberg 来构建数据湖生态. 本文整理自 Dell 科技集团高级软件研发经理孙伟在 4 月 17 日 上海站 ...

  8. Joint Consensus两阶段成员变更的单步实现

    ​简介: Raft提出的两阶段成员变更Joint Consensus是业界主流的成员变更方法,极大的推动了成员变更的工程应用.但Joint Consensus成员变更采用两阶段,一次变更需要提议两条日 ...

  9. 如何玩转 WebGL 并行计算

    ​简介: 如今在 Web 端使用 WebGL 进行高性能计算已有不少实践,例如在端智能领域中的 tensorflow.js,再比如可视化领域中的 Stardust.js. ​ 作者 | 沧东 来源 | ...

  10. Nacos 开源、自研、商业化三位一体战略解读

    简介: Nacos作为整个阿里云原生三位战略中的核心组成部分,我们在2018年以Configserver/VIPServer/Diamond为基础通过Nacos开源输出阿里十年沉淀的注册中心和配置中心 ...