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

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. Solon 的事务管理工具类(TranUtils)

    Solon 在编码上,是强调注解与手写并重的一个风格.它有个 @Tran 注解,用于事务管理(可以参考:<事务的全局控制及应用>).这里,主要是讲讲它的手动处理工具类 TranUtils. ...

  2. maven 设置阿里镜像[二]

    前言 因为我们在国内,网速很慢,所以最好设置一下阿里镜像. 1.在maven中的conf下的setting 修改为: <mirror> <id>alimaven</id& ...

  3. leetcode:655. 输出二叉树

    655. 输出二叉树 在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则: 1> 行数 m 应当等于给定二叉树的高度. 2> 列数 n 应当总是奇数. 3> 根节点的值(以 ...

  4. pid算法函数实现,c语言版

    #include <stdio.h> float pid(float setpoint, float process_variable, float kp, float ki, float ...

  5. 他来了他来了,.net开源智能家居之苹果HomeKit的c#原生sdk【Homekit.Net】1.0.0发布,快来打造你的私人智能家居吧

    背景介绍 hi 大家好,我是三合,作为一个非著名懒人,每天上完班回到家,瘫在沙发上一动都不想动,去开个灯我都嫌累,此时,智能家居拯救了我,只需要在手机点点点,开关灯,空调,窗帘就都搞定了,一开始我用的 ...

  6. 力扣1083(MySQL)-销售分析Ⅲ(简单)

    题目: Table: Product Table: Sales 编写一个SQL查询,报告2019年春季才售出的产品.即仅在2019-01-01至2019-03-31(含)之间出售的商品. 以 任意顺序 ...

  7. 动态尺寸模型优化实践之Shape Constraint IR Part II

    简介: 在本系列分享中我们将介绍BladeDISC在动态shape语义下做性能优化的一些实践和思考.本次分享的是我们最近开展的有关shape constraint IR的工作,Part II 中我们将 ...

  8. Timing:在线自习室快速搭建

    ​通过超低延迟的音视频通信技术.视频连麦.弱网传输算法,快速搭建自习场景,提升自习效率. 客户简介 ​ 氪细胞主打产品Timing,是国内最早推出,也是规模最大的在线自习室,是新一代的教育与社交融合平 ...

  9. 深入解读:获得 2021 Forrester 全球云数仓卓越表现者的阿里云数据仓库

    简介: 阿里云在最新发布的 The Forrester Wave: Cloud Data Warehouse, Q1 2021 全球云数据仓库技术评比中进入卓越表现者象限,成为国内唯一入选厂商.本文针 ...

  10. k8s对接Ceph实现持久化存储(16)

    一.Ceph简介 官网:https://ceph.com/en/ https://docs.ceph.com/en/latest/start/intro/ ceph 是一种开源的分布式的存储系统 包含 ...