[時間:2016-09] [狀態:Open]
[關鍵詞:android,NuPlayer,開源播放器,播放框架,ALooper,AHandler,AMessage]
前文中提到過NuPlayer基於StagefrightPlayer的基礎類構建,利用了更底層的ALooper/AHandler機制來異步地處理請求,ALooper保存消息請求,然后調用AHandler接口去處理。
實際上在代碼中NuPlayer本身繼承自AHandler類,而ALooper對象保存在NuPlayerDriver中。
ALooper/AHandler機制是模擬的消息循環處理方式,通常有三個主要部分:消息(message,通常包含Handler)、消息隊列(queue)、消息處理線程(looper thread)。
對於handler消息機制,構成就必須包括一個Loop,message。那么對應的AHandler,也應該有對應的ALooper、AMessage。
因此本文主要涉及到三個類ALooper、AHandler、AMessage。
1 AHandler接口分析(消息處理類)
下面代碼是AHandler接口:
// code frome "./frameworks/av/include/media/stagefright/AHandler.h"
struct AHandler : public RefBase {
AHandler();
ALooper::handler_id id() const;
sp<ALooper> looper() const;
wp<ALooper> getLooper() const;
wp<AHandler> getHandler() const;
protected:
virtual void onMessageReceived(const sp<AMessage> &msg) = 0;
private:
friend struct AMessage; // deliverMessage()
friend struct ALooperRoster; // setID()
uint32_t mMessageCounter;
KeyedVector<uint32_t, uint32_t> mMessages;
void setID(ALooper::handler_id id, wp<ALooper> looper);
void deliverMessage(const sp<AMessage> &msg);
};
看上面接口,初步印象是AHandler沒有直接對外的接口(只有獲取成員變量的接口),基本上只有一個onMessageReceived用於子類繼承,deliverMessage用於給類AMessage使用,setID用於給友元類ALooperRoster使用。從這點來說,真正代碼應該在AMessage里邊。
2 AMessage接口分析(消息載體)
下面代碼是AMessage的聲明:
// code from "./frameworks/av/include/media/stagefright/AMessage.h"
struct AMessage : public RefBase {
AMessage();
AMessage(uint32_t what, const sp<const AHandler> &handler); // 代碼中常用的構造函數
static sp<AMessage> FromParcel(const Parcel &parcel, size_t maxNestingLevel = 255);
// Write this AMessage to a parcel.
// All items in the AMessage must have types that are recognized by
// FromParcel(); otherwise, TRESPASS error will occur.
void writeToParcel(Parcel *parcel) const;
void setWhat(uint32_t what);
uint32_t what() const;
// 注意這是一個AHandler,通過這個可以獲得ALooper對象引用
void setTarget(const sp<const AHandler> &handler);
// 清除所有設置的消息屬性參數
void clear();
// 一系列設置/獲取 Message 屬性的函數。。。
void setInt32/setInt64/setSize/setFloat/setDouble/setPointer/setPointer/setString/setRect/setObject/setBuffer/setMessage(...);
bool findInt32/findInt64/findSize/findFloat/findDouble/findPointer/findString/findObject/findBuffer/findMessage/findRect(...) const;
// 通過這個函數檢索下指定名稱的消息屬性是否存在
bool contains(const char *name) const;
// 投遞消息的接口,顧名思義直接投遞給構造函數的ALooper,注意支持延時消息,但不支持提前消息,delayUS > 0
status_t post(int64_t delayUs = 0);
// 投遞消息並等待執行結束后發送response消息
status_t postAndAwaitResponse(sp<AMessage> *response);
// If this returns true, the sender of this message is synchronously
// awaiting a response and the reply token is consumed from the message
// and stored into replyID. The reply token must be used to send the response
// using "postReply" below.
bool senderAwaitsResponse(sp<AReplyToken> *replyID);
// Posts the message as a response to a reply token. A reply token can
// only be used once. Returns OK if the response could be posted; otherwise,
// an error.
status_t postReply(const sp<AReplyToken> &replyID);
// 深拷貝
sp<AMessage> dup() const;
// 比較兩個消息,並返回差異
sp<AMessage> changesFrom(const sp<const AMessage> &other, bool deep = false) const;
// 獲取消息屬性存儲的個數及特定索引上的消息屬性參數
size_t countEntries() const;
const char *getEntryNameAt(size_t index, Type *type) const;
protected:
virtual ~AMessage();
private:
friend struct ALooper; // deliver()
uint32_t mWhat;
wp<AHandler> mHandler;
wp<ALooper> mLooper;
// 用於ALooper調用的,發送消息的接口
void deliver();
};
從上面的接口可以看出在使用AMessage是只需要指定消息的id和要處理該消息的AHandler即可,可以通過構造函數,也可以單獨調用setWhat和setTarget接口。AMessage構造完成之后,可以調用setXXX設置對應的參數,通過findXXX獲取傳遞的參數。最后通過post即可將消息投遞到AHandler的消息隊列中。
3 ALooper接口分析(消息處理循環及后台線程)
其簡化的聲明如下:
// code from "./frameworks/av/include/media/stagefright/ALooper.h"
struct ALooper : public RefBase {
ALooper();
// Takes effect in a subsequent call to start().
void setName(const char *name);
const char *getName() const;
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();
protected:
virtual ~ALooper();
private:
friend struct AMessage; // post()
AString mName;
struct Event {
int64_t mWhenUs;
sp<AMessage> mMessage;
};
List<Event> mEventQueue;
struct LooperThread;
sp<LooperThread> mThread;
bool mRunningLocally;
// START --- methods used only by AMessage
// posts a message on this looper with the given timeout
void post(const sp<AMessage> &msg, int64_t delayUs);
// creates a reply token to be used with this looper
sp<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 AMessage
bool loop();
};
ALooper對外接口比較簡單,通常就是NuPlayerDriver構造函數中的調用邏輯。先創建一個ALooper對象,然后調用setName和start接口,之后調用registerHandler設置一個AHandler,這樣就完成了初始化。在析構之前需要調用stop接口。
這里需要說明下,ALooper::start接口會啟動一個線程,並調用ALooper::loop函數,該函數主要實現消息的實際執行。代碼如下:
bool ALooper::loop() {
Event event;
{
Mutex::Autolock autoLock(mLock);
if (mThread == NULL && !mRunningLocally) {
return false;
}
// 從mEventQueue取出消息,判斷是否需要執行,不需要的話就等待
// 需要的話就調用handler執行,並刪除對應消息
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();
return true;
}
那么消息是通過那個函數添加進來的呢? 這就是友元類AMessage的作用,通過調用ALooper::post接口,將AMessage添加到mEventQueue中。
4 一個調用實例
以NuPlayer::setVideoSurfaceTextureAsync為示例分析下ALooper/AHandler機制。
這里不解釋ALooper的初始化過程,有興趣的可以參考資料Android Native層異步消息處理框架的內容。
下面是setVideoSurfaceTextureAsync的代碼。
void NuPlayer::setVideoSurfaceTextureAsync(
const sp<IGraphicBufferProducer> &bufferProducer) {
sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, this);
if (bufferProducer == NULL) {
msg->setObject("surface", NULL);
} else {
msg->setObject("surface", new Surface(bufferProducer, true /* controlledByApp */));
}
msg->post();
}
這段代碼功能很簡單,創建一個AMessage對象,並設置下參數,參數類型為Object,名稱是"surface",然后通過AMessage::post接口,間接調用ALooper::post接口,將消息發送給ALooper-NuPlayerDriver::mLooper;ALooper的消息循環線程檢測到這個消息,在ALooper::loop函數中通過AMessage的deliver接口,調用AHandler::deliverMessage接口,這個函數會調動NuPlayer::onMessageReceived(通過繼承機制實現)接口。這樣繞了一圈。我們就可以通過ALooper/AHandler機制處理消息了。
具體處理代碼如下
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetVideoSurface:
{
sp<RefBase> obj;
CHECK(msg->findObject("surface", &obj));
sp<Surface> surface = static_cast<Surface *>(obj.get());
ALOGD("onSetVideoSurface(%p video decoder)", surface.get());
// Need to check mStarted before calling mSource->getFormat because NuPlayer might
// be in preparing state and it could take long time.
// When mStarted is true, mSource must have been set.
if (mSource == NULL || !mStarted || mSource->getFormat(false /* audio */) == NULL
// NOTE: mVideoDecoder's mSurface is always non-null
|| (mVideoDecoder != NULL && mVideoDecoder->setVideoSurface(surface) == OK)) {
performSetSurface(surface);
break;
}
}
// ... 省略其他部分代碼
}
}
總結
本文主要介紹了NuPlayer中ALooper、AHandler、AMessage三個類直接的關系及代碼結構,並以NuPlayer::setVideoSurfaceTextureAsync接口的實現為例說明。
主要是作為后續深入分析NuPlayer內部機制做一個基礎知識介紹。
參考資料
- AOSP 7.0
- Android異步消息框架