Android Media Player 框架分析-AHandler AMessage ALooper

2024-03-09 18:20

本文主要是介绍Android Media Player 框架分析-AHandler AMessage ALooper,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在之前一篇中简单的介绍了一下setDataSource的流程,其中遇到了一个新的消息机制AHandler,其实这东西本来不需要介绍,因为消息机制原本就是一个很成熟和常见的技术技巧,这玩意里面包含了计算机哲学和计算机玄学的双学位问题,听起来牛逼轰轰,其实也就那回事了。为了这次文档的完整性,再一个后面可能要整理到公司的文档库中,所以在此介绍一下,熟悉的同学直接飘过了。


先说说为什么要用消息机制,在我们的软件开发中经常会遇到多线程的问题,由此便带来了线程同步的问题,比如各种锁,最常见的就是Mutex,而线程同步通常是有代价的,这个代价一方面来自于锁本身,而另一方面来自于程序设计,在一个有众多线程的进程中使用同步锁常常造成各种死锁问题,当然你可以说你水平高超没有这个问题,而通常解决锁之间的竞争往往在效率上也要付出相应的代价,当然了你也可以说你连这也能解决,那我也只能膜拜了。然而通常还有另一个问题,便是线程间的执行顺序问题,如果使用锁和条件变量来控制,在线程众多的情况下,可控性只会越来越差,另外通常来讲CPU的利用率也不高。那么消息机制是如何来解决这些问题的呢?


第一个问题是什么是消息机制,这里先介绍一个同步/异步的的概念,举例来说明,

比如说肚子饿了去饭店吃饭,点好菜后你坐那老老实实等着服务员上菜,什么时候菜做了好你才能结束等待,那这就是同步。

而异步就不用干等了,掏出手机,打开外卖软件,下单,OK,这时你就可以去干别的事情了,应该没有人死盯着手机干等吧,到时候饭店做好饭,小哥自然送上门。


听起来异步比同步是先进很多了,但在现实中事情往往没有想象的那么容易,首先你用外卖软件,如果我是老板,餐厅已经爆满了,顾客在饭店里已经骂娘了,哪还顾得上你,第二你点完得等送餐吧,又多了一段路程,而同步也有自己的缺点,坐在餐馆等饭的时间对于你而言算是浪费了,比如你还有其他事情没做此时也只能乖乖干坐着了。


好吧,扯太远了,回到消息机制上来。

当我们想要我们的程序执行一些事情的时候要么同步,要么异步,而消息机制便是一种异步的方式,首先创建一个消息队列,这个消息队列在一个单独的线程中轮询,一旦有人发送消息给它,它就会将消息取出执行,执行后又回到等待状态直到有新的消息到来。

如下图:


消息被投递到一个消息队列中去,而Looper会持续不断的从Message Queue中拿出消息,再投递给合适的Handler去处理。

好了,开始对Android的这个消息队列进行分析吧,依旧从使用上入手,通常在使用的开始会创建一个ALooper对象,然后会实现一个AHandler的子类,最后创建消息进行投递进入Message Queue。

首先我们先看一下AHandler的定义。代码不多直接贴上来。

namespace android {struct AMessage;
struct AHandler : public RefBase {AHandler(): mID(0),mVerboseStats(false),mMessageCounter(0) {}ALooper::handler_id id() const {return mID;}sp<ALooper> looper() const {return mLooper.promote();}wp<ALooper> getLooper() const {return mLooper;}wp<AHandler> getHandler() const {// allow getting a weak reference to a const handlerreturn const_cast<AHandler *>(this);}protected:virtual void onMessageReceived(const sp<AMessage> &msg) = 0;private:friend struct AMessage;      // deliverMessage()friend struct ALooperRoster; // setID()ALooper::handler_id mID;wp<ALooper> mLooper;inline void setID(ALooper::handler_id id, wp<ALooper> looper) {mID = id;mLooper = looper;}bool mVerboseStats;uint32_t mMessageCounter;KeyedVector<uint32_t, uint32_t> mMessages;void deliverMessage(const sp<AMessage> &msg);DISALLOW_EVIL_CONSTRUCTORS(AHandler);
};}  // namespace android

cpp文件:

namespace android {void AHandler::deliverMessage(const sp<AMessage> &msg) {onMessageReceived(msg);mMessageCounter++;if (mVerboseStats) {uint32_t what = msg->what();ssize_t idx = mMessages.indexOfKey(what);if (idx < 0) {mMessages.add(what, 1);} else {mMessages.editValueAt(idx)++;}}
}
}  // namespace android

由于该类为基类,在使用时需要自己定义子类去继承重新类中的方法,所先以该类的Cpp文件相对比较简单,只定义了一个deliverMessage方法,
先看看.h文件中该类的构成,其中有一个容易混淆的成员mMessages被定义为KeyedVector类型,该并不是真正的Message Queue,相反该对象并没有什么太大的实际作用,只有在verbose模式下才存储一个消息被传递的次数,对我们分析没什么帮助。剩下的就是一个id值,和一个ALooper的弱引用。刚刚我们已经说过了,Looper负责将消息源源不断的发送给Handler来处理,所以两者是必然有关联的。另外还有一个mVerboseStats的bool值,这玩意实际上没啥用,先不看了。之后看看一个重要的纯虚函数onMessageReceived,我们定义的子类最重要的就是实现这个方法。之后会通过调用把Message发给这个函数,而这个函数会根据这个Message中的what值来判断需要进行什么操作,习惯用法是通过一个switch来处理。


实际的Media代码中这个AHandler的使用非常之多,只需简单搜索便可以找到很多的例子来查看。
现在应该来看看ALooper这个类了,这个类的作用非常明确,在创建时会开启一个线程,不断的对消息队列进行检查,如果一旦有消息便拿出来给相应的AHandler处理。比较熟悉的人应该可以想到这种情景下需要一个Condition条件变量来控制循环,否则持续的轮询会给系统产生可怕的负担。
代码都不多直接全放上来好了,先看.h文件
namespace android {struct AHandler;
struct AMessage;
struct AReplyToken;struct ALooper : public RefBase {typedef int32_t event_id;typedef int32_t handler_id;ALooper();// Takes effect in a subsequent call to start().void setName(const char *name);handler_id registerHandler(const sp<AHandler> &handler);void unregisterHandler(handler_id handlerID);status_t start(bool runOnCallingThread = false,bool canCallJava = false,int32_t priority = PRIORITY_DEFAULT);status_t stop();static int64_t GetNowUs();const char *getName() const {return mName.c_str();}protected:virtual ~ALooper();private:friend struct AMessage;       // post()struct Event {int64_t mWhenUs;sp<AMessage> mMessage;};Mutex mLock;Condition mQueueChangedCondition;AString mName;List<Event> mEventQueue;struct LooperThread;sp<LooperThread> mThread;bool mRunningLocally;// use a separate lock for reply handling, as it is always on another thread// use a central lock, however, to avoid creating a mutex for each replyMutex mRepliesLock;Condition mRepliesCondition;// START --- methods used only by AMessage// posts a message on this looper with the given timeoutvoid post(const sp<AMessage> &msg, int64_t delayUs);// creates a reply token to be used with this loopersp<AReplyToken> createReplyToken();// waits for a response for the reply token.  If status is OK, the response// is stored into the supplied variable.  Otherwise, it is unchanged.status_t awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response);// posts a reply for a reply token.  If the reply could be successfully posted,// it returns OK. Otherwise, it returns an error value.status_t postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &msg);// END --- methods used only by AMessagebool loop();DISALLOW_EVIL_CONSTRUCTORS(ALooper);
};
} // namespace android
.cpp文件:
namespace android {ALooperRoster gLooperRoster;
struct ALooper::LooperThread : public Thread {LooperThread(ALooper *looper, bool canCallJava): Thread(canCallJava),mLooper(looper),mThreadId(NULL) {}virtual status_t readyToRun() {mThreadId = androidGetThreadId();return Thread::readyToRun();}virtual bool threadLoop() {return mLooper->loop();}bool isCurrentThread() const {return mThreadId == androidGetThreadId();}protected:virtual ~LooperThread() {}private:ALooper *mLooper;android_thread_id_t mThreadId;DISALLOW_EVIL_CONSTRUCTORS(LooperThread);
};// static
int64_t ALooper::GetNowUs() {return systemTime(SYSTEM_TIME_MONOTONIC) / 1000ll;
}ALooper::ALooper(): mRunningLocally(false) {// clean up stale AHandlers. Doing it here instead of in the destructor avoids// the side effect of objects being deleted from the unregister function recursively.gLooperRoster.unregisterStaleHandlers();
}ALooper::~ALooper() {stop();// stale AHandlers are now cleaned up in the constructor of the next ALooper to come along
}void ALooper::setName(const char *name) {mName = name;
}ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {return gLooperRoster.registerHandler(this, handler);
}void ALooper::unregisterHandler(handler_id handlerID) {gLooperRoster.unregisterHandler(handlerID);
}status_t ALooper::start(bool runOnCallingThread, bool canCallJava, int32_t priority) {if (runOnCallingThread) {{Mutex::Autolock autoLock(mLock);if (mThread != NULL || mRunningLocally) {return INVALID_OPERATION;}mRunningLocally = true;}do {} while (loop());return OK;}Mutex::Autolock autoLock(mLock);if (mThread != NULL || mRunningLocally) {return INVALID_OPERATION;}mThread = new LooperThread(this, canCallJava);status_t err = mThread->run(mName.empty() ? "ALooper" : mName.c_str(), priority);if (err != OK) {mThread.clear();}return err;
}status_t ALooper::stop() {sp<LooperThread> thread;bool runningLocally;{Mutex::Autolock autoLock(mLock);thread = mThread;runningLocally = mRunningLocally;mThread.clear();mRunningLocally = false;}if (thread == NULL && !runningLocally) {return INVALID_OPERATION;}if (thread != NULL) {thread->requestExit();}mQueueChangedCondition.signal();{Mutex::Autolock autoLock(mRepliesLock);mRepliesCondition.broadcast();}if (!runningLocally && !thread->isCurrentThread()) {// If not running locally and this thread _is_ the looper thread,// the loop() function will return and never be called again.thread->requestExitAndWait();}return OK;
}void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {Mutex::Autolock autoLock(mLock);int64_t whenUs;if (delayUs > 0) {whenUs = GetNowUs() + delayUs;} else {whenUs = GetNowUs();}List<Event>::iterator it = mEventQueue.begin();while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {++it;}Event event;event.mWhenUs = whenUs;event.mMessage = msg;if (it == mEventQueue.begin()) {mQueueChangedCondition.signal();}mEventQueue.insert(it, event);
}bool ALooper::loop() {Event event;{Mutex::Autolock autoLock(mLock);if (mThread == NULL && !mRunningLocally) {return false;}if (mEventQueue.empty()) {mQueueChangedCondition.wait(mLock);return true;}int64_t whenUs = (*mEventQueue.begin()).mWhenUs;int64_t nowUs = GetNowUs();if (whenUs > nowUs) {int64_t delayUs = whenUs - nowUs;mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);return true;}event = *mEventQueue.begin();mEventQueue.erase(mEventQueue.begin());}event.mMessage->deliver();// NOTE: It's important to note that at this point our "ALooper" object// may no longer exist (its final reference may have gone away while// delivering the message). We have made sure, however, that loop()// won't be called again.return true;
}// to be called by AMessage::postAndAwaitResponse only
sp<AReplyToken> ALooper::createReplyToken() {return new AReplyToken(this);
}// to be called by AMessage::postAndAwaitResponse only
status_t ALooper::awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response) {// return status in case we want to handle an interrupted waitMutex::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;
}}  // namespace android


首先看一下cpp文件开始变声明了一个LooperThread的内部类型,该类型继承与Thread类。通过构造函数保存了ALooper指针,这些没什么可说的,看一下threadLoop函数直接调用了ALooper的loop函数,果然和我们之前描述的一致,该线程不断的调用loop函数,而loop函数自身则是对消息队列的查询,如此,整个消息队列便在Looper的驱动下活动起来了。成员对象List<Event> mEventQueue;便是我们要寻找的Message Queue了,其他的接口主要有setName给Looper起个名字,这个名字其实蛮重要的,因为最后会用作LooperThread的名字,线程的名字是一个很重要的信息,start函数就无需多言了创建LooperThread然后调用run函数把线程跑起来,就可以了,而这个线程便会不断调用ALooper的loop函数来驱动消息队列。
再来看一下ALooper的成员变量。
    Mutex mLock;Condition mQueueChangedCondition;Mutex mRepliesLock;Condition mRepliesCondition;
首先是两对搭配使用的Mutex和Condition,mQueueChangedCondition是用来阻塞loop函数的,如果没有它loop函数会在消息队列中没有Event时继续空转,那CPU就耗光了,而mRepliesCondition则是用来做消息的同步等待回复的,后续碰到再说吧。
剩下的几个函数,我们先说简单常用的,post方法,该方法是将消息投递到队列中,第一个参数是AMessage也就是消息,第二个参数是延迟时间,实现非常简单,创建一个Event然后将其加入队列即可,另外就是如果消息队列原本为空就通过mQueueChangedCondition.signal();唤醒队列的线程。
其中的内部类型Event就不多说了,对message的简单包装,剩下的createReplyToken/awaitResponse/postReply是用来发同步消息等待反馈的,我们一会再说。
再看看loop方法的具体细节,首先会判断如果mThread==NULL并且mRunningLocally==false则函数返回false,如果返回了false,线程就不会在执行下一次的loop函数了。接下来判断mEventQueue是否为空,也就是判断消息队列中是否有消息,如果没有就使用mQueueChangedCondition.wait使线程阻塞,直到刚才介绍post时说的那样被唤醒。剩下的如果是队列中第一个要执行的消息有延迟时间,那么就是用Condition的waitRelative方法阻塞等待。最后一切都没问题了就准备处理消息了,先把消息从队列删除,之后通过调用event.mMessage->deliver()方法来处理消息。处理的细节我们一会介绍AMessage时再来看。

前面说了ALooper和AHandler相对都是比较简单的,剩下一个AMessage反倒是这个消息队列中比较复杂的一个组成部分了,代码稍微有点多,所以还是分段来看。
首先说一点,这个Message不仅仅是传递消息这么简单,它可以附带上很多的键值对在身上,也就是说这个Message在传递的时候除了最重要的what值之外还能带上一些其他的值,而每个值都有一个对应的key,这样接收方在处理消息时可以根据这个key值拿出在Message中存储的value,这样一次可以传递的信息就大大增加了。
在AMessage的定义中有如下的一个内部类的定义
    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);};

这个类型所定义的就是一个key-value,被命名为Item, 其中使用联合体这段空间来存储value,使用mName来作为key。mNameLength来存储key值得长度,这个长度在这里还挺重要的,后面我们再看,mType是一个枚举值,保存了所存数据是什么类型的,枚举体定义如下:
    enum Type {kTypeInt32,kTypeInt64,kTypeSize,kTypeFloat,kTypeDouble,kTypePointer,kTypeString,kTypeObject,kTypeMessage,kTypeRect,kTypeBuffer,};
之后AMessage定义了一个数组Item mItems[kMaxNumItems];保存所有的Item不过这样确定不浪费空间吗?另外便定义了一堆的find和set函数,都是用来设置这些键值对的。一个AMessage最多携带kMaxNumItems个Item,默认为64.
除了这个之外还有一个类AReplyToken,这个虽然不是AMessage的内部类,但我猜想这个类不作为内部类的原因更多的可能是因为C++的一个语法限制。这个类的作用我们等会再说,先看看AMessage的另外两个成员变量
    wp<AHandler> mHandler;wp<ALooper> mLooper;
这两个猜都知道啦,一个是处理该消息的Handler一个是处理消息队列轮询的Looper。
当然AMessage中少不了的还有个what值,定义如下:
uint32_t mWhat;
因为一个Handler不是给一个Message专用的,可能会处理很多种的消息,那么为了区分便加入一个what值来告诉Handler现在的消息是哪种,一般Handler会使用一个switch来进行处理,这个源码中例子多的不要不要的,自己看吧。

好啦,看看构造函数先:
AMessage();
AMessage(uint32_t what, const sp<const AHandler> &handler);AMessage::AMessage(void): mWhat(0),mTarget(0),mNumItems(0) {
}AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler): mWhat(what),mNumItems(0) {setTarget(handler);
}
一般使用第二种了,传入消息的what值和handler,mNumItems赋0表示目前还没有携带的附加信息,也就是键值对Item啦。之后调用setTarget
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();}
}
mTarge是handler的一个id值,这个id是累加生成的,回头再说吧,暂时用不到,剩下的就没什么,简单赋值。另外为啥要调用handler的getHandler的方法而不直接赋值呢?主要是因为有可能AHandler是一个const的指针,getHandler方法会将其通过const_cast转换,那为啥要搞成非const的呢,因为要用wp,那为啥用wp就要非const呢。。。不想回答你。。。
好吧,看看AMessage的post方法吧,这是我们要分析的重点,
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;
}
实现挺简单的吧,直接调用ALooper的post方法。ALooper的post我们已经说过了加入到消息队列就没了。之后loop函数会把消息取出来调用Message的deliver方法,我们来看看这个deliver方法是干嘛的。
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);
}
直接拿出handler调用了deliverMessage方法,这个方法我们刚才说过了。会不会想为啥AMessage的post方法不直接调用deliver方法呢,如果有这个问题证明你压根没看懂,再看一遍吧,当ALooper通过loop函数拿出消息调用deliver的时候,这个deliver才会在Looper的线程里面执行,而如果直接调用那就成了当前线程同步调用了。

好,我们看最后一个问题,AMessage中的 postAndAwaitResponse方法,这个方法也是用来发送消息的,不同之处在于这个方法发送完消息以后会等待接收方返回一个答复,所以你懂的,main thread里面肾用。

好,此时我们需要看看刚才提了一下的
struct AReplyToken : public RefBase {AReplyToken(const sp<ALooper> &looper): mLooper(looper),mReplied(false) {}private:friend struct AMessage;friend struct ALooper;wp<ALooper> mLooper;sp<AMessage> mReply;bool mReplied;sp<ALooper> getLooper() const {return mLooper.promote();}// if reply is not set, returns false; otherwise, it retrieves the reply and returns truebool retrieveReply(sp<AMessage> *reply) {if (mReplied) {*reply = mReply;mReply.clear();}return mReplied;}// sets the reply for this token. returns OK or errorstatus_t setReply(const sp<AMessage> &reply);
};
这个类先放着,到底干嘛用的一会再说。

我们还是从源头上来看,这种情形通常是从AMessage的对象调用postAndAwaitResponse开始的,这个函数有一个参数是sp<AMessage>*类型了,sp包含的已经是指针了,那这里显然是一个out型的参数,也就是在调用结束后这个参数会被赋值。我们先看看这个函数的定义。
status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {sp<ALooper> looper = mLooper.promote();if (looper == NULL) {ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);return -ENOENT;}sp<AReplyToken> token = looper->createReplyToken();if (token == NULL) {ALOGE("failed to create reply token");return -ENOMEM;}setObject("replyID", token);looper->post(this, 0 /* delayUs */);return looper->awaitResponse(token, response);}
首先拿到looper调用createReplyToken创建了一个token,这点我们一会来看,紧接着looper调用post把消息post出去了。最后调用了looper的awaitResponse方法,这个方法的代码前面已经给出,我们这里再单独拿出来看看
// to be called by AMessage::postAndAwaitResponse only
status_t ALooper::awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response) {// return status in case we want to handle an interrupted waitMutex::Autolock autoLock(mRepliesLock);CHECK(replyToken != NULL);while (!replyToken->retrieveReply(response)) {{Mutex::Autolock autoLock(mLock);if (mThread == NULL) {return -ENOENT;}}mRepliesCondition.wait(mRepliesLock);}return OK;
}
很明显该函数会在mRepliesCondition这个条件变量上等待,直到条件变成replyToken->retrieveReply返回ture。通过上面的代码可以知道这里的replyToken就是刚才从AMessage传过来的,但是有时这个Looper自己调用createReplyToken创建的,而这个创建不过就是调用了AReplyToken的构造函数,然后把Looper自己的指针传了过去。这到底有啥关联呢,我们先不着急看这个,先看看mRepliesCondition这个条件变量在何时才能signal或者broadcast,找到突破口,一共有两处一处是postReply方法,一处是stop方法,不用看也知道stop是万万不行滴。那么就只能是这个postReply了,代码如下:
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;
}
处理唤醒broadcast调用外,还调用了replyToken的setReply方法,先看看这个setReply,前面已经给出过代码了,这里重复一下:
status_t AReplyToken::setReply(const sp<AMessage> &reply) {if (mReplied) {ALOGE("trying to post a duplicate reply");return -EBUSY;}CHECK(mReply == NULL);mReply = reply;mReplied = true;return OK;
}
做了两件事情,给成员mReply赋值为参数,mReplied赋值为ture,好像看不出来啥用。我们接着看看上面的postReply既然broadcast已经发了,那么之前在ALooper::awaitResponse函数中while循环也得跳出吧,现在是时候看看这个while中的retrieveReply返回啥玩意了。

    bool retrieveReply(sp<AMessage> *reply) {if (mReplied) {*reply = mReply;mReply.clear();}return mReplied;}// sets the reply for this token. returns OK or errorstatus_t setReply(const sp<AMessage> &reply);
直接看返回语句,这个返回的值就是mReplied,也就是说如果mReplied为ture这事就算了了,这样就和前面的setReply对上了,只要调用setReply的这个AReplyToken和postReply参数里的那个token是同一个,这个条件变量就可以被唤醒了。那么问题是,谁来做这个postReply的发起人呢,token又从哪来,那么对外的接口而言,AMessage是首当其冲的,所以从它开始找,一找就找到了。AMessage有一个同名函数也叫postReply,刚好就调用了ALooper的postReply函数,我们来仔细看看。
status_t AMessage::postReply(const sp<AReplyToken> &replyToken) {if (replyToken == NULL) {ALOGW("failed to post reply to a NULL token");return -ENOENT;}sp<ALooper> looper = replyToken->getLooper();if (looper == NULL) {ALOGW("failed to post reply as target looper is gone.");return -ENOENT;}return looper->postReply(replyToken, this);
}
调用很简单,问题还是没变token从哪来。我们回想一下刚才在最初调用postAndAwaitResponse中,通过looper的createReplyToken函数创建了一个token,AMessage还通过setObject("replyID", token);这句把它设置为自己的一个item字段,这样我们想在唤醒时再次取到这个token当然要通过AMessage变量来了。直接告知答案了,AMessage有一个叫senderAwaitsResponse的方法,就是干这事的。
bool AMessage::senderAwaitsResponse(sp<AReplyToken> *replyToken) {sp<RefBase> tmp;bool found = findObject("replyID", &tmp);if (!found) {return false;}*replyToken = static_cast<AReplyToken *>(tmp.get());tmp.clear();setObject("replyID", tmp);// TODO: delete Object instead of setting it to NULLreturn *replyToken != NULL;
}

代码中的参数replyToken是一个sp的指针,也就表示在函数中会对这个sp赋值,所以我们只需要声明一个sp<AReplyToken>对象,然后传入它的指针即可。
这样我们便拿到了这个token,那由谁来发送呢?答案是谁都可以,我们回想一下,AMessage的postReply方法是从参数token中拿到looper然后一路调用下去的,跟之前的AMessage可是没一点关系了。所以只需创建一个新的AMessage就行了。
一般的习惯用法,给出一个伪代码的参考:

sp<AMessage> msg = new AMessage(what, handler);
sp<AMessage> response;
msg->postAndAwaitResponse(response);


接收的Handler在处理完消息后发送答复:
            sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));sp<AMessage> response = new AMessage;response->postReply(replyID);

这样整个await的消息就完成了。

文中有一点没有介绍,那就是ALooper有一个registerHandler的方法,这个方法除了调用AHandler的setID将Looper和Handler关联起来之外,其中还引出了一个LooperRoster的类型,而且是一个全局对象,那么这个对象有什么用,这个registerHandler方法不调用行不行,留给各位自己研究啦。 大笑











这篇关于Android Media Player 框架分析-AHandler AMessage ALooper的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/791559

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号