全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
由于网上已经有许多优秀的博文讲解了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 Media Framework(一)异步消息机制
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。