承接上一章节分析:【若前一章节没看过,建议先看上一章节】
Android native层媒体通信架构AHandler/ALooper机制实现源码分析【Part 1】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
3、发送消息:
ALooper内部线程或其他线程中均可发送消息。
【以MediaClock为例】
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::processTimers_l() {
// 省略其他代码
// 发送该事件消息,可延迟发送并携带参数
// 见3.1小节分析
sp<AMessage> msg = new AMessage(kWhatTimeIsUp, this);
// 传递参数,可传递非常多的数据类型
// 见3.2小节分析
msg->setInt32("generation", mGeneration);
// post发送事件消息
// 见3.3小节分析
msg->post(nextLapseRealUs);
}
3.1、AMessage类声明:
省略其他代码
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/AMessage.h]
struct AMessage : public RefBase {}
AMessage构造函数实现:
有两个构造函数:无参构造函数和有参构造函数
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
AMessage::AMessage(void)
// 默认全为0
: mWhat(0),
mTarget(0),
mNumItems(0) {
}
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler)
// 事件常量值即事件id
: mWhat(what),
// 事件个数,默认为0
mNumItems(0) {
// 设置目标handler对象
// 见下面的分析
setTarget(handler);
}
setTarget(handler)实现分析:
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
void AMessage::setTarget(const sp<const AHandler> &handler) {
// 判断handler消息接收处理器是否存在
if (handler == NULL) {
// handler 为空时,直接清除工作,
// 清空当前AMessage对象中可能缓存的AHandler和ALooper对象,并将mTarget置为0 。
// 注:mTarget该值为handler id
mTarget = 0;
mHandler.clear();
mLooper.clear();
} else {
// handler不为空时,则获取这些值
mTarget = handler->id();
mHandler = handler->getHandler();
mLooper = handler->getLooper();
}
}
3.2、AMessage 传递参数,可传递非常多的数据类型,可调用 setXXX() 方法来传递。此处举例分析几个典型的数据处理过程。
// 传递一个Key值名称为“generation”的参数数据mGeneration
msg->setInt32("generation", mGeneration);
主要实现的设置数据参数setXXX()方法有如下几个声明:【省略其他代码】
第一个参数都是作为key值字符串(字段)来标识存储对应的字段值。
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/AMessage.h]
struct AMessage : public RefBase {
void setInt32(const char *name, int32_t value);
void setInt64(const char *name, int64_t value);
void setSize(const char *name, size_t value);
void setFloat(const char *name, float value);
void setDouble(const char *name, double value);
void setPointer(const char *name, void *value);
void setString(const char *name, const char *s, ssize_t len = -1);
void setString(const char *name, const AString &s);
void setObject(const char *name, const sp<RefBase> &obj);
void setBuffer(const char *name, const sp<ABuffer> &buffer);
void setMessage(const char *name, const sp<AMessage> &obj);
void setRect(
const char *name,
int32_t left, int32_t top, int32_t right, int32_t bottom);
}
支持非常多数据类型的参数。
但当我们查看其实现文件中的定义实现时,发现这些方法前面几个方法没有对应的定义实现。其实我们仔细找就可以发现,有部分方法定义实现其实是使用的宏定义来完成。
如下基础数据类型宏定义实现:
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
// 由该宏定义可知,有三个参数:方法名字组成部分(NAME)、字段名(FIELDNAME)、数据类型名(TYPENAME)
#define BASIC_TYPE(NAME,FIELDNAME,TYPENAME) \
void AMessage::set##NAME(const char *name, TYPENAME value) { \
// 分配Key值名称为(name)参数名的(新或旧)可用数据项对象指针
// 见3.2.1小节分析
Item *item = allocateItem(name); \
\
// 数据项数据类型赋值
item->mType = kType##NAME;
// 数据项参数数据赋值 \
item->u.FIELDNAME = value; \
} \
\
// NOLINT含义:简单了解就是避免clang编译器编译时报错(警告报错等)
/* NOLINT added to avoid incorrect warning/fix from clang.tidy */ \
bool AMessage::find##NAME(const char *name, TYPENAME *value) const { /* NOLINT */ \
// (在数据项 Item 类型数组参数集中)
// 查找指定数据项Key值名称(name)的数据项对象,返回其一个对象指针
// 见3.2.2小节分析
const Item *item = findItem(name, kType##NAME); \
if (item) { \
// 查询到该数据项时,则赋值给value指针来存储该参数值,并返回true即查找成功
*value = item->u.FIELDNAME; \
return true; \
} \
// 未查找到该参数值则返回false
return false; \
}
// 6种 setXXX() 设置数据参数方法通过宏定义来完成实现
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
因此由上面宏定义实现可知,有6种 setXXX() 设置数据参数方法通过宏定义来完成实现,那么也肯定会有对应的6种 findXXX() 查询获取对应数据参数值的方法。
并且存储的每一个参数数据都是通过一个创建一个Key值名称为(name)参数名的数据项对象来缓存的,并且最终会将整个AMessage中的每个参数数据都添加到数据项 Item 类型的数组参数集中。【注意:该数组只允许最多携带64个数据项数据,若超出则程序将抛错并停止运行】
由该部分小节分析可知,AMessage添加每个参数时,若此前已添加过该Key值参数,那么将会覆盖前面的值。
警告:根据上面完整分析可知,就算是数据项数据类型不同,也要求每个数据项的Key值必须不能相同,否则会被重置参数值。
其提供主要实现的findXXX()方法声明如下:【省略其他代码】
第一个参数都是作为key值字符串(字段)来查找对应的数据参数值。
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/AMessage.h]
struct AMessage : public RefBase {
bool findInt32(const char *name, int32_t *value) const;
bool findInt64(const char *name, int64_t *value) const;
bool findSize(const char *name, size_t *value) const;
bool findFloat(const char *name, float *value) const;
bool findDouble(const char *name, double *value) const;
bool findPointer(const char *name, void **value) const;
bool findString(const char *name, AString *value) const;
bool findObject(const char *name, sp<RefBase> *obj) const;
bool findBuffer(const char *name, sp<ABuffer> *buffer) const;
bool findMessage(const char *name, sp<AMessage> *obj) const;
// finds signed integer types cast to int64_t
bool findAsInt64(const char *name, int64_t *value) const;
// finds any numeric type cast to a float
bool findAsFloat(const char *name, float *value) const;
bool findRect(
const char *name,
int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const;
}
【Item】数据项的相关定义如下,是AMessage的内部struct类结构:【省略其他代码】
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/AMessage.h]
struct AMessage : public RefBase {
enum Type {
kTypeInt32,
kTypeInt64,
kTypeSize,
kTypeFloat,
kTypeDouble,
kTypePointer,
kTypeString,
kTypeObject,
kTypeMessage,
kTypeRect,
kTypeBuffer,
};
struct Rect {
int32_t mLeft, mTop, mRight, mBottom;
};
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;
// 当前数据项Key值名称字符串指针
const char *mName;
// 缓存当前数据项Key值名称字符串长度
size_t mNameLength;
// 当前数据项数据类型
Type mType;
// 设置数据项Key值名称
// 见下面的分析
void setName(const char *name, size_t len);
};
}
item->setName(name, len)实现分析:
设置数据项Key值名称
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
// assumes item's name was uninitialized or NULL
void AMessage::Item::setName(const char *name, size_t len) {
mNameLength = len;
// 注意此处的字符串指针内存申请的处理,必现用字符串名称长度len加1,
// 因为len长度不包含空值字符,
// 因此字符串必现包含空值字符串即'\0',否在其只是个字符数组而不是字符串
mName = new char[len + 1];
// 复制字符串到新分配内存中
memcpy((void*)mName, name, len + 1);
}
3.2.1、allocateItem(name)实现分析:
分配Key值名称为(name)参数名的数据项对象指针,即返回一个可用数据项对象指针【新数据项或已释放参数数据的旧数据项】
警告:根据下面的分析可知,就算是数据项数据类型不同,也要求每个数据项的Key值必须不能相同,否则会被重置参数值。
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
AMessage::Item *AMessage::allocateItem(const char *name) {
// 获取参数Key值名称字符串长度
size_t len = strlen(name);
// (在数据项 Item 类型数组参数集中)查找指定数据项Key值名称的数据项索引
// 注意这个返回值的含义:
// 返回查找到的索引值,若不存在则默认返回当前需要创建的数据索引
// 即此时i值和mNumItems数组大小值相同(若无数据时i=mNumItems=0)
// findItemIndex() 见3.2.1.1小节分析
size_t i = findItemIndex(name, len);
Item *item;
// 备注:AMessage构造函数初始化时 mNumItems 该值为0
if (i < mNumItems) {
// 查找到旧数据项时
item = &mItems[i];
// 有旧数据项时,则释放该数据项所持有的数据参数值
// freeItemValue() 见3.2.1.2小节分析
freeItemValue(item);
} else {
// 未查找到旧数据项时
// 检查已添加的参数数据项总个数
// 【kMaxNumItems常量为64,若超出则程序将抛错并停止运行】
CHECK(mNumItems < kMaxNumItems);
// i先使用mNumItems的值,然后mNumItems值再加1
i = mNumItems++;
// 获取i索引对应的新数据项对象的地址,即赋值给指针
item = &mItems[i];
// 默认数据项数据类型为 kTypeInt32
item->mType = kTypeInt32;
// 设置数据项Key值名称
item->setName(name, len);
}
// 然后返回一个可用数据项对象指针【新数据项或已释放参数数据的旧数据项】
return item;
}
3.2.1.1、findItemIndex(name, len)实现分析:
(在数据项 Item 类型数组参数集中)查找指定数据项Key值名称的数据项索引。
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
inline size_t AMessage::findItemIndex(const char *name, size_t len) const {
// DUMP_STATS 宏定义在AMessage.cpp最前面默认是未定义。【被注释掉了】
// 其主要作用就是收集数据统计
#ifdef DUMP_STATS
size_t memchecks = 0;
#endif
// for循环处理:循环匹配指定数据项Key值名称的数据项及其查询其存在的索引
// 备注:AMessage构造函数初始化时 mNumItems 该值为0
size_t i = 0;
for (; i < mNumItems; i++) {
// 首先通过名称长度来匹配,可以更加高效的完成匹配
if (len != mItems[i].mNameLength) {
continue;
}
#ifdef DUMP_STATS
// 该值记录相同长度的数据项Key值名称所执行的匹配次数
++memchecks;
#endif
// 使用memcmp()方法来匹配,返回0则表示匹配成功,否则匹配失败继续下一次循环匹配
if (!memcmp(mItems[i].mName, name, len)) {
break;
}
}
#ifdef DUMP_STATS
// 此处就不分析了,默认不开启。主要就是统计数据【统计本次查询的效率】
{
Mutex::Autolock _l(gLock);
++gFindItemCalls;
gAverageNumItems += mNumItems;
gAverageNumMemChecks += memchecks;
gAverageNumChecks += i;
// 该方法就是上报统计数据,其实际就是LOGI打印这些数据,然后reset它们
reportStats();
}
#endif
// 注意这个返回值的含义:
// 返回查找到的索引值,若不存在则默认返回当前需要创建的数据索引
// 即此时i值和mNumItems数组大小值相同(若无数据时i=mNumItems=0)
return i;
}
3.2.1.2、freeItemValue(item)实现分析:
释放该数据项所持有的数据参数值
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
void AMessage::freeItemValue(Item *item) {
// 判断数据项数据类型
switch (item->mType) {
case kTypeString:
{
// 若是String字符串类型,则使用delete释放其携带的数据参数值
delete item->u.stringValue;
break;
}
case kTypeObject:
case kTypeMessage:
case kTypeBuffer:
{
// 若为Object、Message、Buffer类型,则若其数据参数对象指针不为空时,
// 减少其携带的数据参数对象指针强引用计数(可能会释放若计数为0的话)
if (item->u.refValue != NULL) {
item->u.refValue->decStrong(this);
}
break;
}
default:
break;
}
// 清除数据项数据类型,并初始化为 kTypeInt32
item->mType = kTypeInt32; // clear type
}
3.2.2、findItem(name, kType##NAME)实现分析:
(在数据项 Item 类型数组参数集中)查找指定数据项Key值名称(name)的数据项对象,返回其一个对象指针
备注:通过下面的分析可知,查找指定Key值数据时,就算是Key值相同,也会再次检查其数据类型是否相同,若不相同则返回NULL。
// [frameworks/av/media/libstagefright/foundation/AMessage.cpp]
const AMessage::Item *AMessage::findItem(
const char *name, Type type) const {
// (在数据项 Item 类型数组参数集中)查找指定数据项Key值名称的数据项索引
// 注意这个返回值的含义:
// 返回查找到的索引值,若不存在则默认返回当前需要创建的数据索引
// 即此时i值和mNumItems数组大小值相同(若无数据时i=mNumItems=0)
// findItemIndex() 见前面的3.2.1.1小节分析
size_t i = findItemIndex(name, strlen(name));
// 备注:AMessage构造函数初始化时 mNumItems 该值为0
if (i < mNumItems) {
// 查询到对应的数据项,赋值给对象指针
const Item *item = &mItems[i];
// 然后再判断数据项数据类型释放一致,一致则返回,否在返回NULL
return item->mType == type ? item : NULL;
}
// 未查找到则返回NULL
return NULL;
}
3.2.3、此处再分析一下另外几种数据类型的setXXX() 方法实现,如下
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/AMessage.h]
struct AMessage : public RefBase {
void setString(const char *name, const char *s, ssize_t len = -1);
void setString(const char *name, const AString &s);
void setObject(const char *name, const sp<RefBase> &obj);
void setBuffer(const char *name, const sp<ABuffer> &buffer);
void setMessage(const char *name, const sp<AMessage> &obj);
void setRect(
const char *name,
int32_t left, int32_t top, int32_t right, int32_t bottom);
}
它们对应的实现:
均在该文件中 [frameworks/av/media/libstagefright/foundation/AMessage.cpp] 实现。实现都比较简单,只分析重点。
void AMessage::setString(
const char *name, const char *s, ssize_t len) {
// 分配并返回一个(新或旧)数据项对象指针
Item *item = allocateItem(name);
// 数据类型
item->mType = kTypeString;
// 重新复制一份当前字符串到新内存中
item->u.stringValue = new AString(s, len < 0 ? strlen(s) : len);
}
void AMessage::setString(
const char *name, const AString &s) {
// 转换AString类型字符串为 const char *
setString(name, s.c_str(), s.size());
}
// 该方法是设置【sp<RefBase>】对象类型数据项处理,此处传入的是强引用指针对象引用
void AMessage::setObjectInternal(
const char *name, const sp<RefBase> &obj, Type type) {
Item *item = allocateItem(name);
item->mType = type;
// 增加强引用计数
// 注意:智能指针对象的【!=】不等运算符的含义,并不是判断obj是否为空,
// 而是判断它内部持有的目标指针【RefBase *】是否为空,若不为空才增加强引用计数。
if (obj != NULL) { obj->incStrong(this); }
// 获取obj内部持有的目标指针【RefBase *】
item->u.refValue = obj.get();
}
void AMessage::setObject(const char *name, const sp<RefBase> &obj) {
setObjectInternal(name, obj, kTypeObject);
}
void AMessage::setBuffer(const char *name, const sp<ABuffer> &buffer) {
// 直接将 sp<ABuffer> 类型数据转换为 sp<RefBase> 类型
setObjectInternal(name, sp<RefBase>(buffer), kTypeBuffer);
}
// 存储参数为另一个AMessage数据类型的数据项,其实这个实现和上面两个方法实现类似的,
// 因此我们可以直接改写成:setObjectInternal(name, sp<RefBase>(obj), kTypeMessage);
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();
}
void AMessage::setRect(
const char *name,
int32_t left, int32_t top, int32_t right, int32_t bottom) {
Item *item = allocateItem(name);
item->mType = kTypeRect;
item->u.rectValue.mLeft = left;
item->u.rectValue.mTop = top;
item->u.rectValue.mRight = right;
item->u.rectValue.mBottom = bottom;
}
3.3、AMessage post发送事件消息实现分析
由于本章节篇幅过长,因此将后续分析部分放在本系列下一章节分析,请查看:
Android native层媒体通信架构AHandler/ALooper机制实现源码分析【Part 3】