Android媒体底层通信框架Native Handler(二):AMessage

该系列文章,会分为三个部分:

AMessage

AOSP Version: Oreo 8.0.0_r4

AMessage可以算的上市整个消息系统中的核心接口了。自然,它的接口也比其它两个结构体复杂得多。

namespace android {

struct ABuffer;
struct AHandler;
struct AString;
class Parcel;

struct AMessage : public RefBase {
    AMessage();
    AMessage(uint32_t what, const sp<const AHandler> &handler);

    /**
     * 根据parcel构建一个AMessage
     *
     * @param parcel 数据包
     * @param maxNestingLevel 最大嵌套层级
     * @return AMessage是可以嵌套存在的,但如果嵌套层级大于maxNestingLevel,将产生异常,并返回NULL
     * 如果消息类型无法被函数识别也会产生异常,并返回NULL。支持的消息类型有:
     * int32_t Int32 int64_t Int64 size_t Size float Float double Double
     * AString String AMessage Message
     */
    static sp<AMessage> FromParcel(const Parcel &parcel,
                                   size_t maxNestingLevel = 255);

    /** 将当前AMessage写入数据包parcel。AMessage中所有的items类型必须能被识别(见FromParcel部 
     *  分),否则会产生异常。
     */
    void writeToParcel(Parcel *parcel) const;
    void clear();
	/**
	 * 设置Int32类型的item,类似的函数还有setInt64、setSize、setFloat、setDouble、setPointer、
	 * setString、setRect、setBuffer、setObject等。并通过findXXX获取每个item的值。
	 * 
	 * @param name 每个item都会有一个独立的key
	 * @param value 每个item的值
	 */
    void setInt32(const char *name, int32_t value);
    bool contains(const char *name) const; // 判断是否包含名为name的item
    status_t post(int64_t delayUs = 0); // 发送消息
    // 将消息post到目标AHandler,并等待回复或者异常。
    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);

    // 将message作为回复令牌发送,一个回复令牌只能使用一次,如何可以被发送,返回true,否则返回false
    status_t postReply(const sp<AReplyToken> &replyID);

    // 执行当前对象的深拷贝。警告:RefBase类型item值,不会被拷贝,只会让引用计数加+1。
    sp<AMessage> dup() const;

    /**
     * 对当前对象进行深/浅拷贝,并返回一个包含差异的AMessage。
     * 警告:RefBase类型item值,不会被拷贝,只会让引用计数加+1。
     *
     * @param other 用于和当前对象比较的AMessage对象。
     * @param deep 是否进行深比较。
     * @return AMessage 返回一个差异的AMessage对象。
     */
    sp<AMessage> changesFrom(const sp<const AMessage> &other, bool deep = false) const;

    AString debugString(int32_t indent = 0) const;// 这是干啥的?可能是用来打印消息本身的,有兴趣的小伙伴可以自己试一下
    enum Type { // 消息中,item的类型
        kTypeInt32,
        kTypeInt64,
        kTypeSize,
        kTypeFloat,
        kTypeDouble,
        kTypePointer,
        kTypeString,
        kTypeObject,
        kTypeMessage,
        kTypeRect,
        kTypeBuffer,
    };

    size_t countEntries() const;
    const char *getEntryNameAt(size_t index, Type *type) const;
protected:
    virtual ~AMessage(); // 虚析构函数

private:
    friend struct ALooper; // deliver()
    // 通过setWhat函数设置该值,通过what函数获取该值。该值用于区分不同的消息,可以认为是一种消息类型
    uint32_t mWhat; 

    // used only for debugging
    ALooper::handler_id mTarget;
    // 消息的处理器:指定该消息最终由那个AHandler处理。通过setTarget函数初始化。
    wp<AHandler> mHandler;
    wp<ALooper> mLooper;
    struct Rect { // 矩形结构体, 用于保存视频帧的显示尺寸的
        int32_t mLeft, mTop, mRight, mBottom;
    };

    struct Item { // item结构体
        union { // item 的值
            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; // item的key
        size_t      mNameLength; // item key的长度
        Type mType; // item的消息类型
        void setName(const char *name, size_t len);
    };

    enum {
        kMaxNumItems = 64 // 最大item数
    };
    Item mItems[kMaxNumItems]; // item 数组
    size_t mNumItems; // 实际item数

    Item *allocateItem(const char *name);
    void freeItemValue(Item *item);
    const Item *findItem(const char *name, Type type) const;

    void setObjectInternal(
            const char *name, const sp<RefBase> &obj, Type type);

    size_t findItemIndex(const char *name, size_t len) const;

    void deliver();

    DISALLOW_EVIL_CONSTRUCTORS(AMessage);
};

}  // namespace android

AMessage的函数接口相对较多,这里就挑一个我认为重要的展开一下:

构造函数

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);
}
  1. 无参构造没什么好说的,将mWhat、mTarget、mNumItems设置为零。
  2. 有参构造:将mNumItems设置为零,参数中的handler设置给mHandler。

这里出现了setTarget函数,索性先看看这个函数呗。

setTarget

void AMessage::setTarget(const sp<const AHandler> &handler) {
    if (handler == NULL) { // 如果参数为NULL,AMessage对象回到初始化的状态
        mTarget = 0;
        mHandler.clear();
        mLooper.clear();
    } else { // 将handler中的相关应用赋值给AMessage对象
        mTarget = handler->id(); 
        mHandler = handler->getHandler();
        mLooper = handler->getLooper();
    }
}

为什么取名叫做setTarget呢? 因为AMessage对象,最终需要AHandler对象处理,这两个原本孤立的对象,通过各自内部对彼此的引用持有,达到这样的目的。所以,该函数可以理解为,为该条消息(AMessage)设定目标处理对象。

setXXX/findXXX

在看基本数据类型的setXXX/findXXX函数之前,先介绍一下几个比较重要的函数,allocateItem、findItem、findItemIndex。

findItemIndex

findItemIndex函数中,有大量的DUMP_STATS的宏定义,对本文来说没什么意义。去掉之后的代码就像这个样子:

inline size_t AMessage::findItemIndex(const char *name, size_t len) const {
    size_t i = 0;
    for (; i < mNumItems; i++) {
        if (len != mItems[i].mNameLength) { // 如果入参len和itemName长度不一致,继续下一次判断
            continue;
        }
        if (!memcmp(mItems[i].mName, name, len)) { // 长度一致,且名字相同,找到了目标item,跳出循环
            break;
        }
    }
    return i;
}

在AMessage.h的代码分析部分,已经说明了:

  • mNumItems:是当前AMessage中的Item计数。
  • mItems:是当前AMessage的Item数组。

这个函数的功能从名字上就很直观:findItemIndex可以直接看出是“找到指定item的数组index”的意思,这种自注释的命名,值得我们学习。

另外,方法实现上也很简单:

  1. 编译mItems数组。
  2. 通过比较入参len的值,和每个item名称长度作对比,如果相等进入下一个判断逻辑,如果不相等进入下一步循环。
  3. 如果名称长度相等,且名称通过memcmp判断相同,则找到和入参name匹配的item,返回找到的item在数组中的index。如果没有找到,则返回数组mItems的长度。

findItem

const AMessage::Item *AMessage::findItem(
        const char *name, Type type) const {
    size_t i = findItemIndex(name, strlen(name));
    if (i < mNumItems) {
        const Item *item = &mItems[i];
        return item->mType == type ? item : NULL;
    }
    return NULL;
}

findItem 函数主要功能:

  1. 通过findItemIndex函数,找到对应入参name在mItems数组中的索引。
  2. 通过上一步的索引,从mItems数组中获取Item指针,并返回。
  3. 没找到,返回NULL。
allocateItem

我们都知道AMessage中有很多的Item,Amessage为创建这些Item,专门编写了一个函数:allocateItem

AMessage::Item *AMessage::allocateItem(const char *name) {
    size_t len = strlen(name);
    size_t i = findItemIndex(name, len); // 查看需要新建的item是否已近存在
    Item *item;
    if (i < mNumItems) { // 如果需要新建的item已存在,将已存在的item清空
        item = &mItems[i];
        freeItemValue(item);
    } else { // 需要新建的item不存在
        CHECK(mNumItems < kMaxNumItems);
        i = mNumItems++; // item计数加1
        item = &mItems[i]; // 将mItems中第i个位置留给新的item
        item->setName(name, len); // 为item设置名称
    }
    return item;
}

allocateItem函数,并不会真的去alloc内存,因为mItems数组中已经将内存分配好了,只需要将对应数组中index的位置指针赋值给一个临时变量,然后通过临时指针变量来初始化数组中item的值就可以了。这也就是allocateItem函数的逻辑。

setXXX/findXXX

#define BASIC_TYPE(NAME,FIELDNAME,TYPENAME)                             
void AMessage::set##NAME(const char *name, TYPENAME value) {            
    Item *item = allocateItem(name);                                    
                                                                        
    item->mType = kType##NAME;                                          
    item->u.FIELDNAME = value;                                          
}                                                                       
                                                                        
/* NOLINT added to avoid incorrect warning/fix from clang.tidy */       
bool AMessage::find##NAME(const char *name, TYPENAME *value) const {  /* NOLINT */ 
    const Item *item = findItem(name, kType##NAME);                     
    if (item) {                                                         
        *value = item->u.FIELDNAME;                                     
        return true;                                                    
    }                                                                   
    return false;                                                       
}

BASIC_TYPE(Int32,int32Value,int32_t)
BASIC_TYPE(Int64,int64Value,int64_t)
BASIC_TYPE(Size,sizeValue,size_t)
BASIC_TYPE(Float,floatValue,float)
BASIC_TYPE(Double,doubleValue,double)
BASIC_TYPE(Pointer,ptrValue,void *)

#undef BASIC_TYPE

AMessage中,基本数据类型的set/find方法定义就在上边了。通过宏定义,设计一个同样算法的函数,表达N个函数的做法,真的是绝了,如果java里也可以这么做的话,要少多少set/get函数,少多少重复代码啊。我代表自己,强烈建议java9引入这种机制。

在这里,通过宏替换,总共可以展开12个不同函数。篇幅起见,就只展开Int32类型的分析分析。

set##NAME中的“##”符号的作用是,将相邻的两个宏连接起来。于是就有了setInt32、setInt64等函数。

setInt32

void AMessage::setInt32(const char *name, int32_t value) {            
    Item *item = allocateItem(name); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看
    item->mType = kTypeInt32;                                          
    item->u.int32Value = value;                                          
}                                                 

set系列函数都比较简单:

  1. 通过allocateItem函数,获取mItems数组中,对应name的空item的指针。
  2. 通过上一步获取的指针,修改mItems数组对应item的mType值。
  3. 通过第一步获取的指针,修改mItems数组对应item的值(该值的内存使用联合体表示)。

简单直白又透彻。完美的代码

findInt32

/* NOLINT added to avoid incorrect warning/fix from clang.tidy */       
bool AMessage::findInt32(const char *name, int32_t *value) const {  /* NOLINT */ 
    const Item *item = findItem(name, kTypeInt32); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看                    
    if (item) {                                                         
        *value = item->u.int32Value;                                     
        return true;                                                    
    }                                                                   
    return false;                                                       
}

find系列函数,同样直白:

  1. 通过findItem找到name相同且type相同的item,返回它的指针。没找到的话,会返回一个NULL。
  2. 如果第一步找到了对应指针,则通过指针,将对应item的值赋值给*value,并返回true。表示找到了对应item,函数调用线程可以根据*value 指向的内存,获取对应item的值。
  3. 如果第一步没有找到,返回false。

其它常见的set/find函数

setMessage

void AMessage::setMessage(const char *name, const sp<AMessage> &obj) {
    Item *item = allocateItem(name); // 前面已经对这个函数解释过了,忘记的小朋友可以回头去看看
    item->mType = kTypeMessage;

    if (obj != NULL) { obj->incStrong(this); }
    item->u.refValue = obj.get();
}

如果你记性足够好的话,应该记得AMessage的源码介绍的时候,FromParcel函数的注释部分提示:AMessage是可以嵌套存在的,但如果嵌套层级大于maxNestingLevel,将产生异常。

这个函数,就可以实现AMessage嵌套,只需将item类型指定为kTypeMessage即可,剩下的操作和其它set函数也没啥区别了。

我发现,set/find系列方法,都不是很复杂:

  1. 通过allocateItem找到合适的item内存指针。
  2. 通过指针,设置对应item的mType类型,在这个函数中,类型自然是kTypeMessage
  3. 将被嵌套的AMessage对象引用计数加1,后,将AMessage对象的值放到当前item中去。

findMessage

bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const {
    const Item *item = findItem(name, kTypeMessage);
    if (item) {
        *obj = static_cast<AMessage *>(item->u.refValue);
        return true;
    }
    return false;
}

和基础类型的find函数类似:

  1. 通过findItem找到对应的item,如果没找到*item为NULL。
  2. 如果找到了对应item,通过*obj将找到的AMessage提供给调用线程使用,并返回true。
  3. 没找到,返回false。

其它的setBuffer、setObject以及与之对应的find函数,都和set/findMessage相关函数类似,就不多说了。

setRect

不得不说一下Rect 相关的函数(Rect结构体,前面代码中已介绍)。Rect结构体是用来保存视频帧的显示尺寸的。所以,就Android多媒体框架来说,非常重要。

void AMessage::setRect(
        const char *name,
        int32_t left, int32_t top, int32_t right, int32_t bottom) {
    Item *item = allocateItem(name); // 拿到一个空的item
    item->mType = kTypeRect; // 设置item类型
	// 左上右下的边界值
    item->u.rectValue.mLeft = left;
    item->u.rectValue.mTop = top;
    item->u.rectValue.mRight = right;
    item->u.rectValue.mBottom = bottom;
}

findRect

再看一下find函数:

bool AMessage::findRect(
        const char *name,
        int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const {
    const Item *item = findItem(name, kTypeRect); // 找到item
    if (item == NULL) {
        return false;
    }
	// 将上下左右值,通过指针,放到不同的内存地址去。
    *left = item->u.rectValue.mLeft; 
    *top = item->u.rectValue.mTop;
    *right = item->u.rectValue.mRight;
    *bottom = item->u.rectValue.mBottom;
    return true;
}

和其他find函数也没啥区别,一定要有的话,大概就是:findRect的参数比较多 .~. 。

消息交互相关函数

deliver

void AMessage::deliver() {
    sp<AHandler> handler = mHandler.promote(); // 获取当前AMessage绑定的Handler对象
    if (handler == NULL) {
        ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
        return;
    }

    handler->deliverMessage(this); // 将自己作为参数,调用目标handler的deliverMessage
}

deliver 也不复杂,咦,为啥说也?

哎,简单的说一下需要注意的地方:

  • 关于mHandler:mHandler对象是AMessage中的私有字段,该字段唯一初始化的地方在前面讲过的AMessage::setTarget函数中。虽然,到现在,还没有分析整个消息机制工作流程,但我们可以大胆的猜想一下:AMessage实际只是消息的载体,消息只有发送出去了,被处理了才有意义。但是在AMessage的deliver(发送)函数中,却用到了一个mHandler对象。那么,可以考虑,mHandler对象,在消息创建之初便已经被初始化。换句话说,setTarget函数,在AMessage创建之初就会调用。这点,我希望在下一篇文章得到验证。
  • 关于handler->deliverMessage:看到这里,该函数应该已经熟悉了,AHandler::deliverMessage函数什么都没做,直接将入参原封不动的通过onMessageReceived纯虚函数,交给子类处理了。子类怎么处理,关我屁事!!!! 好吧,不急,后面分析具体事例的时候肯定会稍微提一下。

post

status_t AMessage::post(int64_t delayUs) {
    sp<ALooper> looper = mLooper.promote(); // 获取当前AMessage绑定的mLooper对象
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    looper->post(this, delayUs); // 将自己和发送时间传递给looper处理。
    return OK;
}

哇哇哇~,这函数简直就是AMessage::deliver函数的翻版,已经在deliver函数中说过的就不再说了。说一下不同的地方:

  • looper: mLooper赋值的地方,也在AMessage::setTarget函数中,和mHandler赋值的时机是一样的,在AMessage创建时。
  • AMessage::post直接将自己和消息的发送时间,通过AMessage创建时赋值的ALooper::post函数,交给looper去处理,处理结果就是将AMessage对象自己,和发送时间(delayUs)包装一下,放到一个消息队列中区。不知道ALooper::post函数的帅哥(应该没有仙女会看这种文章吧!)可以去ALooper一小节回顾一下。

postAndAwaitResponse

status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
    sp<ALooper> looper = mLooper.promote(); // 获取当前AMessage绑定的mLooper对象
    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);
}

从函数名可以看出,该函数除了和AMessage::post函数一样,会将消息放到ALooper中的消息队列中外,还会等待消息的返回。而在整个Native Handler 消息机制中,消息的返回都是通过回复令牌体现。

这里重点看一下回复令牌和等待返回的代码。

1、回复令牌的创建

sp<AReplyToken> token = looper->createReplyToken();
    if (token == NULL) {
        ALOGE("failed to create reply token");
        return -ENOMEM;
}
setObject("replyID", token);

这段代码,直接通过mLooper获取一个回复令牌AReplyToken。并将回复令牌的指针当作自己的一个item存起来,key是“replyID”,值是刚刚获取的回复令牌。

来来来,我们这里刨一个坑先。再来联想一下,在一个需要等待回复的函数调用中,创建了一个回复令牌,并将该令牌作为消息的一部分(一个item)存储起来,但在不需要回复的另一个函数中,却没有这种动作。是不是可以大胆的判断:

  • 一个消息,如果有名为"replyID"的item,那么它就已经在准备返回了。
  • 如果没有“replyID”的item,那么它应该是处在创建或者刚加入到消息队列中,而没有被线程处理。

大胆假设,我会在后面小心求证的。至于假设的意义? 哎~ 想太多了, 消磨廉价的光阴罢了。我的发际线~

2、等待回复

looper->post(this, 0 /* delayUs */);
return looper->awaitResponse(token, response);

post函数就不多说了。简单聊一下awaitResponse:

两个参数:

  • token:回复令牌实际上是记录当前消息是否已经回复等消息的。因为前面记录到当前消息item中的是token的指针,所以,这里传参的意义是通过指针,间接修改当前消息的token。
  • response:外界传入的AMessage指针,用于回传消息处理的结果。

另外:因为ALooper::awaitResponse函数中存在while循环和Condition锁,所以该函数是个会阻塞的函数。直到有了回复消息,才会解除锁定状态。

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);
}

该函数的调用时机,暂时还不知道,通过“postReply”函数名称可以猜测,该函数是在消息接收端处理好消息和对应的回复令牌后,调用该函数,执行消息回复的逻辑。这里又挖了个坑,看后面能不能圆回来。

根据上面的假设:回复令牌已经处理好,准备回复消息,那么代码块前面的各种非空校验显然是能够通过的。

最终调用了ALooper::postReply函数,这里有两个参数:

  • replyToken:即已经设置了回复的回复令牌本身
  • this:AMessage消息对象自身。

好累啊,不过还是先来回顾一下,前面说过的ALooper::postReply函数吧:

postReply的主要作用,就是将回复令牌和回复消息绑定,并唤醒awaitResponse函数,处理回复消息。

小结一下:AMessage::postReply函数也只是将传入的回复令牌和自己,交给ALooper::postReply函数处理,处理的结果是:将令牌与自身绑定后,通过广播,将所有等待mRepliesCondition锁的线程唤醒,告诉它们消息处理完了,起来干活。

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 NULL

    return *replyToken != NULL;
}

根据内心的指引,让我们畅想一下… ,咳咳,不对。

应该是根据函数名,让我们继续猜一猜函数功能。sender Awaits Response = 发件人等待回应 。 什么鬼,我姑且理解为,这个函数是在消息回复的时候调用的吧,实在看不出其它含义了。还是看代码靠谱点。

  • findObject: 找到当前AMessage对象的回复令牌,如果没有就返回false。
  • *replyToken = static_cast<AReplyToken *>(tmp.get());:如果有就将指针赋值给入参,供调用者差遣,最后将当前AMessage的回复令牌指针清掉,返回true。

小结一下:根据代码的意思,很简单,就是将当前AMessage的回复令牌拿给别人用,自己不要了。clear之后,又是一具白花花的身体。

源码相关路径

Android底层代码,一般*.h文件和.cpp*文件都存放在不同路径下。

头文件

/frameworks/av/include/media/stagefright/foundation/

AMessage.h

AHandler.h

ALooper.h

.cpp文件

/frameworks/av/media/libstagefright/foundation/

AMessage.cpp

AHandler.cpp

ALooper.cpp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值