[时间: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内部机制做一个基础知识介绍。

参考资料

  1. AOSP 7.0
  2. Android异步消息框架

②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析的更多相关文章

  1. ⑤NuPlayer播放框架之GenericSource源码分析

    [时间:2017-01] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,GenericSource] 0 导读 GenericSource是NuPlayer:: ...

  2. ④NuPlayer播放框架之Renderer源码分析

    [时间:2016-11] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,渲染器,render] 0 导读 之前我们分析了NuPlayer的实现代码,本文将重点聚 ...

  3. ③NuPlayer播放框架之类NuPlayer源码分析

    [时间:2016-10] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架] 0 引言 差不多一个月了,继续分析AOSP的播放框架的源码.这次我们需要深入分析的是N ...

  4. ①Android NuPlayer播放框架

    [时间:2016-09] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,nuplayerdriver] 0 NuPlayer简介 Android2.3时引入流媒 ...

  5. ⑥NuPlayer播放源码分析之DecoderBase分析

    NuPlayer播放源码分析之DecoderBase分析 [时间:2017-02] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,DecoderBase,Med ...

  6. B站开源播放框架ijkplayer(iOS版)使用教程

    最近在关注直播这块儿,开始时直接用ffmpeg写了一个,写得比较烂,卡顿很严重,后来听说了B站开源播放框架ijkplayer,于是就去试试看这是链接 ,一用之下果然不错,逢人便向人安利,可总是有部分同 ...

  7. Java工程师高薪训练营-第一阶段 开源框架源码解析-模块一 持久层框架涉及实现及MyBatis源码分析-任务一:自定义持久层框架

    目录 任务一:自定义持久层框架 1.1 JDBC回顾及问题分析 1.2 自定义持久层框架思路分析 1.3 IPersistence_Test编写 1.3.1 XXXMapper.xml详解 1.3.2 ...

  8. 【笔记】拉勾Java工程师高薪训练营-第一阶段 开源框架源码解析-模块一 持久层框架涉及实现及MyBatis源码分析-任务一:自定义持久层框架

    以下笔记是我看完视频之后总结整理的,部分较为基础的知识点也做了补充,如有问题欢迎沟通. 目录 任务一:自定义持久层框架 1.1 JDBC回顾及问题分析 1.2 自定义持久层框架思路分析 1.3 IPe ...

  9. php-浅谈php底层机制

    php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...

随机推荐

  1. pandas学习(数据分组与分组运算、离散化处理、数据合并)

    pandas学习(数据分组与分组运算.离散化处理.数据合并) 目录 数据分组与分组运算 离散化处理 数据合并 数据分组与分组运算 GroupBy技术:实现数据的分组,和分组运算,作用类似于数据透视表 ...

  2. HDU3915 Game 高斯消元

    题目链接 HDU3915 Game 高斯消元 题解 求异或方程组自由元的子集个数 高斯消元求秩,内存溢出好神 代码 #include<bitset> #include<cstdio& ...

  3. 洛谷P1198 [JSOI2008]最大数(BZOJ.1012 )

    To 洛谷.1198 最大数 题目描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值. 限制:L不超过当 ...

  4. POJ.2774.Long Long Message/SPOJ.1811.LCS(后缀数组 倍增)

    题目链接 POJ2774 SPOJ1811 LCS - Longest Common Substring 比后缀自动机慢好多(废话→_→). \(Description\) 求两个字符串最长公共子串 ...

  5. 51Nod.1237.最大公约数之和 V3(莫比乌斯反演 杜教筛 欧拉函数)

    题目链接 \(Description\) \(n\leq 10^{10}\),求 \[\sum_{i=1}^n\sum_{j=1}^ngcd(i,j)\ mod\ (1e9+7)\] \(Soluti ...

  6. 潭州课堂25班:Ph201805201 爬虫基础 第十一课 点触验证码 (课堂笔记)

    打开 网易盾 http://dun.163.com/trial/picture-click  ——在线体验——图中点选 打码平台 ——超级鹰    http://www.chaojiying.com/ ...

  7. node+koa2 向页面传值方式

    1. router.post('/form',async(ctx,next)=>{ const form = ctx.request.body; console.log('用户名:'+form. ...

  8. C1驾考总结

    C1学车经验总结       科目1:理论考试,要点:驾考宝典上有关科目一的题全部做一遍,然后使用模拟测试,连续能够三次测试通过95分以上,说明差不多了.其中要特别注意,多去回顾几次做错过的题目.   ...

  9. 我定制的Stylish样式

    没有办法上传,只好存到这里. 天涯 .atl-con-ft, .js-zhiyin-area, .ds_seat_foot, .hongbao-btn, .dashang-left-btn, .qrc ...

  10. Linux学习笔记13—Vi编辑器的学习

    文本编辑工具vim.vi1. vim与vi的最大区别是vim编辑的时候是带颜色显示的.Vi不带颜色显示.2. yum install -y vim-enhanced 如果没有安装VIM 使用上面的命令 ...