Native Handler实例分析(NuPlayer)
该系列文章,会分为三个部分:
- 介绍AHandler、ALooper源码:Android媒体底层通信框架Native Handler(二):AMessage
- 介绍AMessage源码:AMessage值得一说的地方太多了,就和其它两个类分开了。Android媒体底层通信框架Native Handler(二):AMessage
- 以Nuplayer为例,分析Native Handler在Android底层起到的作用:也即是本文
Native Handler的使用,在Android multimudia框架中有很多使用,原因是,多媒体处理数据通常都需要很多线程来处理对媒体帧数据。但这些线程又需要保持**happens-before **关系,处理好同步问题。
优秀的同步方案很多,但Android设计者还是选择了Native Handler的方案,和java层Handler机制保持了一致。
下面就以NuPlayer的创建过程为例,简单分析一下,Native Handler在其中的作用。
NuPlayerDriver
NuPlayer的创建过程,其实没那么简单,总入口在MediaPlayer中,中间又需要通过MediaPlayerService、打分机制来选择是否使用NuPlayer播放器、NuPlayerDriver这些结构体或者过程。
如果对MediaPlayer感兴趣的,可以进传送门:MediaPlayer源码分析,在这里,你可以了解到,NuPlayer的创建,是如何一步步从MediaPlayer走来的。
因为本文主旨是分析NativeHandler的使用,所以,就直接从NuPlayerDriver开始了。对NuPlayerDriver感兴趣的大兄弟,可以去看看我另一篇文章:传送门 TODO:将于近期更新
先看看NuPlayerDriver的头文件:
struct NuPlayerDriver : public MediaPlayerInterface {
explicit NuPlayerDriver(pid_t pid);
sp<ALooper> mLooper;
const sp<NuPlayer> mPlayer;
};
头文件里边的mLooper
字段够醒目的,我们可以简单通过原理图回顾一下ALooper是干什么的:
- 它维护了一个消息队列mEventQueue。
- 它有一个线程,不断地循环mEvnetQueue队列,找到符合发送条件的消息,交给AHandler去处理。
接下来具体看一下NuPlayerDriver的构造:
NuPlayerDriver::NuPlayerDriver(pid_t pid)
: mState(STATE_IDLE),
mLooper(new ALooper),
mPlayer(new NuPlayer(pid)),
mLooping(false),
mAutoLoop(false) {
ALOGD("NuPlayerDriver(%p) created, clientPid(%d)", this, pid);
mLooper->setName("NuPlayerDriver Looper"); // 设置looper的名称
mLooper->start(
false, /* runOnCallingThread */
true, /* canCallJava */
PRIORITY_AUDIO); // 启动looper中的线程。
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);
}
可以看到,整个构造函数,基本都在为mLooper(ALooper)和mPlayer(NuPlayer)的初始化。先不管省略掉的代码,依次看看构造函数中都做了什么。
- 默认初始化列表中
new ALooper
,为mLooper初始化。 - 默认初始化列表中
new NuPlayer
,为mPlayer初始化。 - 在代码块中,设置looper的名称。
- 调用looper->start启动线程,开始循环消息队列。有疑问的同志,ALooper的start一节回顾一下。
- 为mLooper注册Handler,参数为mPlayer。从这里,我们知道,NuPlayer一定继承了AHandler。
- mPlyaer设置Driver,这对本文来说,看起来似乎没啥用。
这里,着重强调一下第4步和第5步。
从第4步开始ALooper中的线程就已经开始运行,不断地循环ALooper中的loop函数了。而loop函数的作用是从消息队列中去消息,找到合适的时间“发送”出去。但现在消息队列里什么都没有,线程会进入锁等待,直到被唤醒。
第5步,registerHandler将mLooper绑定在mPlayer(也就是AHandler)中,添加到了一个ALooperRoster的花名册中去。详细请看ALooper的registerHandler&unregisterHandler一节。
小结NuPlayerDriver构造
NuPlayerDriver构造的构造函数中,新建了一个ALooper对象,一个AHandler对象(new NuPlayer)。并让ALooper中的循环线程跑起来。
NuPlayer的onMessageReceived
看过AHandler一文或者多少知道java层Handler机制的朋友,一定都知道,继承AHandler(java层是Handler)类,需要实现纯虚函数onMessageReceived
(java层是onHandleMessage)。
前面我们说到,NuPlayer继承了AHandler。那么来找找NuPlayer的onMessageReceived是怎么实现的吧。
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetDataSource:
{
ALOGV("kWhatSetDataSource");
}
case kWhatGetDefaultBufferingSettings:
}
// ......
}
代码不贴多了,意思一下就行。NuPlayer严重依靠NativeHandler机制传递消息,所以它的onMessageReceived函数需要处理的消息很多,case也很多。我们简单体会一下,onMessageReceived是怎么处理的就行了。
ALooper和AHandler在NuPlayerDriver的构造中就已经创建了,那么AMessage呢?
NuPlayer中的AMessage
我们来看一个最简单的使用AMessage的函数NuPlayer::start()
:
void NuPlayer::start() {
(new AMessage(kWhatStart, this))->post();
}
函数短小精悍,一如你……
我们分成两步来看:
-
new AMessage(kWhatStart, this):
看起来就一步,但是啊,和很多少男少女约会一样,毛手毛脚的,小动作很多。还是弄一个图来说明一下吧。
创建Message时,会将通过setTarge函数,和handler(此处为NuPlayer对象)为该条Message的mTarget、mHandler、mLooper 字段赋值。这在后面有大用再说另一个入参
kWhatStart
,对AMessage来说,这个也很重要,用于onMessageReceived中区分不同的case。 -
post():
AMessage中,如果post函数调用时,无参,将使用默认初始化参数。代码如下:
status_t post(int64_t delayUs = 0);
所以,它最终调用的还是有参构造。
还是用途来看一下:
新创建的AMessage对象,调用AMessage::post函数,将一默认的delayUs时间传递给looper执行。这个looper是通过new AMessage的过程中初始化的mLooper中获取的。流程进入到ALooper控制。执行ALooper::post函数,该函数将delayUs时间和AMessage对象本身封装到一个Event结构中,最后加入到mEventQueue消息队列中去。
同时释放信号,将ALooper中等待的线程唤醒。执行LooperThread::threadLoop函数,该函数又调用
mLooper->loop();
,掉用到了ALooper中的loop函数中去。loop将消息队列中的消息取出,调用消息的deliver()函数,直到AHandler子类的onMessageReceived函数处理。整个流程才执行完毕。
好了,实例分析到此结束,因为前面做了不少知识铺垫,所以这里讲的就比较简单,如果有不明白的,可以把该系列前面的文章拿来研究研究。
总结NativeHandler的用法
- 首先要创建一个ALooper和一个AHandler(NuPlayer继承了AHandler实现了onMessageReceived,所以new NuPlayer就算是创建了一个AHandler)。并通过
ALooper::registerHandler
函数,将AHandler对象和ALooper对象绑定。 - 通过
mLooper->start
函数,启动消息队列的循环线程。 - 在需要发送消息的地方,直接创建AMessage对象,并将新的AMessage对象post的出去。
- AMessage对象被post后,继承了AHandler对象的类,会通过实现的onMessageReceived函数,接收到该AMessage对象,做进一步的处理。
完了,就是这么简单。现在问题来了,原理分析完了,实例也不缺。
系统分析Native Handler的初衷,是为了看懂Android底层MultiMedia框架部分关于消息传递的代码。现在来说,就很简单了,只要紧盯AMessage创建时,设置的消息tag是什么,在实现了onMessageReceived函数的地方,找到从AMeesage对象通过what()函数取出的tag所对应的case就可以了。
当然,虽然自己实现一个底层播放器的机会比较少,但不排除有人去探索的。该系列文章也可以为自己实现播放器,需要处理多线程消息传递的哥们提供参考。需要使用哪些头文件啊,怎么实现之类的,去隔壁NuPlayer抄作业就好。
那么,就这样吧。
源码相关路径
Android底层代码,一般*.h文件和.cpp*文件都存放在不同路径下。
头文件
/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.h
.cpp文件
/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp