AHandler机制
2015-07-28 16:48
176 查看
AHandler机制
Android APP开发中为了不阻塞UI线程,利用handler,把UI线程分开异步执行,使用handler去执行某项比较费时的操作,然后异步更新UI线程。这部分是用Java来实现的,和传统Java的线程机制很类似。流媒体(5.0中用的是NuPlayer)中也是类似的,因为联网,codec都很费时,需要异步执行。AHandler机制基于C++的实现,NuPlayer就是继承了AHandler,实际上就是用的AHandler。
对于handler消息机制,构成就必须包括一个Loop,message。那么对应的AHandler,也应该有对应的ALooper, AMessage。底层的多媒体框架NuPlayer中,都是用AHandler消息机制实现的。
结合Nuplayer的构造和媒体播放实现流程中的setDataSource来说明。
####1. NuPlayer构造
首先从NuplayerDriver的构造函数,这是流媒体(NuPlayer)播放初始化函数。代码位置:
[code]frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp class NuPlayerFactory : public MediaPlayerFactory::IFactory { public: … virtual sp<MediaPlayerBase> createPlayer() { ALOGV(" create NuPlayer"); return new NuPlayerDriver; } }; // NuPlayerDriver中初始化的关键项 NuPlayerDriver::NuPlayerDriver() : mState(STATE_IDLE), mIsAsyncPrepare(false), mAsyncResult(UNKNOWN_ERROR), mSetSurfaceInProgress(false), mDurationUs(-1), mPositionUs(-1), mSeekInProgress(false), mLooper(new ALooper), // 创建一个 ALooper mPlayerFlags(0), mAtEOS(false), mLooping(false), mAutoLoop(false), mStartupSeekTimeUs(-1) { ALOGV("NuPlayerDriver(%p)", this); mLooper->setName("NuPlayerDriver Looper"); // 给该Looper取名字,以便与AHandler一一对应 mLooper->start( // 启动Looper false, /* runOnCallingThread */ true, /* canCallJava */ PRIORITY_AUDIO); mPlayer = new NuPlayer; // 创建一个AHandler即Nuplayer mLooper->registerHandler(mPlayer); // 把该AHandler注册到Looper中,具体的实现我们往后看 mPlayer->setDriver(this); } // 看到Looper就看成thread,ALooper的启动函数 status_t ALooper::start( bool runOnCallingThread, bool canCallJava, int32_t priority) { if (runOnCallingThread) { … } Mutex::Autolock autoLock(mLock); if (mThread != NULL || mRunningLocally) { return INVALID_OPERATION; } mThread = new LooperThread(this, canCallJava); // 创建一个新的thread status_t err = mThread->run( mName.empty() ? "ALooper" : mName.c_str(), priority); // Thread运行 if (err != OK) { mThread.clear(); } return err; } // 注册handler ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) { return gLooperRoster.registerHandler(this, handler); } ALooper::handler_id ALooperRoster::registerHandler( const sp<ALooper> looper, const sp<AHandler> &handler) { Mutex::Autolock autoLock(mLock); if (handler->id() != 0) { CHECK(!"A handler must only be registered once."); return INVALID_OPERATION; } HandlerInfo info; info.mLooper = looper; // NuPlayerDriver Looper info.mHandler = handler; // NuPlayer ALooper::handler_id handlerID = mNextHandlerID++; mHandlers.add(handlerID, info); // 放进vector里 handler->setID(handlerID); // 设置handlerID,以便发送message时找到对应的handler return handlerID; } ALooperRoster::ALooperRoster() : mNextHandlerID(1), // 从一开始 mNextReplyID(1) { }
gLooperRoster是一个全局变量,并且是一个vector,以handler Id对应HandlerInfo,而HandlerInfo中包含的是一对Looper(就是thread)和handler。这样处理消息的时候,就以handler Id很快地找到对应的处理。
[code]// Nuplayer本身也是个AHandler,因为其继承自AHandler。 struct NuPlayer : public AHandler { NuPlayer(); … } struct AHandler : public RefBase { AHandler() : mID(0) { } ALooper::handler_id id() const { return mID; } sp<ALooper> looper(); protected: virtual void onMessageReceived(const sp<AMessage> &msg) = 0; // 处理消息 private: friend struct ALooperRoster;
有了LOOPER,也有了对应的handler,以setdataSource方法为例,很清晰地看到送消息给LOOPER,交个相应的handler去处理。
2. 以setDataSource看AHandler处理消息机制
[code]void NuPlayer::setDataSourceAsync( const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers) { sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); // 创建消息 size_t len = strlen(url); sp<AMessage> notify = new AMessage(kWhatSourceNotify, id()); // 创建消息 sp<Source> source; if (IsHTTPLiveURL(url)) { source = new HTTPLiveSource(notify, httpService, url, headers); } else if (!strncasecmp(url, "rtsp://", 7)) { source = new RTSPSource( notify, httpService, url, headers, mUIDValid, mUID); } else if ((!strncasecmp(url, "http://", 7) || !strncasecmp(url, "https://", 8)) && ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?"))) { source = new RTSPSource( notify, httpService, url, headers, mUIDValid, mUID, true); } else { sp<GenericSource> genericSource = new GenericSource(notify, mUIDValid, mUID); // Don't set FLAG_SECURE on mSourceFlags here for widevine. // The correct flags will be updated in Source::kWhatFlagsChanged // handler when GenericSource is prepared. status_t err = genericSource->setDataSource(httpService, url, headers); if (err == OK) { source = genericSource; } else { ALOGE("Failed to set data source!"); } } msg->setObject("source", source); msg->post(); }
NuPlayer中的setDataSource主要做了这些事情:
1 创建相应的消息
2 根据URL创建对应的source
3 onmessageReceive处理对应的消息
首先新建一个AMessage的实例,传入的参数为事件的名称以及处理该消息的Handlerid,该id在mLooper->registerHandler(mPlayer);方法中设置上。
[code]AMessage::AMessage(uint32_t what, ALooper::handler_id target) : mWhat(what), mTarget(target), mNumItems(0) { } void AMessage::setObject(const char *name, const sp<RefBase> &obj) { setObjectInternal(name, obj, kTypeObject); } void AMessage::setObjectInternal( const char *name, const sp<RefBase> &obj, Type type) { Item *item = allocateItem(name); item->mType = type; if (obj != NULL) { obj->incStrong(this); } item->u.refValue = obj.get(); } // post过程 void AMessage::post(int64_t delayUs) { gLooperRoster.postMessage(this, delayUs); } status_t ALooperRoster::postMessage( const sp<AMessage> &msg, int64_t delayUs) { sp<ALooper> looper = findLooper(msg->target()); // target及为获取handler id if (looper == NULL) { return -ENOENT; } looper->post(msg, delayUs); return OK; } void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) { Mutex::Autolock autoLock(mLock); int64_t whenUs; if (delayUs > 0) { whenUs = GetNowUs() + delayUs; } else { whenUs = GetNowUs(); } List<Event>::iterator it = mEventQueue.begin(); while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) { ++it; } Event event; event.mWhenUs = whenUs; event.mMessage = msg; if (it == mEventQueue.begin()) { mQueueChangedCondition.signal(); } mEventQueue.insert(it, event); // 插入到event queue里 } // 当队列里有消息时便会触发loop函数: bool ALooper::loop() { Event event; { Mutex::Autolock autoLock(mLock); if (mThread == NULL && !mRunningLocally) { return false; } 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()); } gLooperRoster.deliverMessage(event.mMessage); // 处理消息 // NOTE: It's important to note that at this point our "ALooper" object // may no longer exist (its final reference may have gone away while // delivering the message). We have made sure, however, that loop() // won't be called again. return true; } void ALooperRoster::deliverMessage(const sp<AMessage> &msg) { sp<AHandler> handler; { Mutex::Autolock autoLock(mLock); ssize_t index = mHandlers.indexOfKey(msg->target()); if (index < 0) { ALOGW("failed to deliver message. Target handler not registered."); return; } const HandlerInfo &info = mHandlers.valueAt(index); handler = info.mHandler.promote(); if (handler == NULL) { ALOGW("failed to deliver message. " "Target handler %d registered, but object gone.", msg->target()); mHandlers.removeItemsAt(index); return; } } handler->onMessageReceived(msg); // 这里就回去调用NuPlayer } void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatSetDataSource: { ALOGV("kWhatSetDataSource"); CHECK(mSource == NULL); status_t err = OK; sp<RefBase> obj; CHECK(msg->findObject("source", &obj)); if (obj != NULL) { mSource = static_cast<Source *>(obj.get()); } else { err = UNKNOWN_ERROR; } CHECK(mDriver != NULL); sp<NuPlayerDriver> driver = mDriver.promote(); if (driver != NULL) { driver->notifySetDataSourceCompleted(err); } break; } } }
Ahandler就是实现了异步机制,大致就是启动一个threadLooper,监听looper的消息队列是否有变化,如有交给相应的Handler去处理。
NuPlayer底层播放器所有 的实现依赖于这样的AHandler机制转动起来的。Android底层有很多类似的AHandler应用的地方,如在render中、获取m3u8 playlist的地方….
相关文章推荐
- iOS 检测设备旋转状态
- Python 获取根据文件URL 获取文件的基本信息
- 常用第三方库
- js中字符串的拼接的另一种方法
- ShopNC本地生活o2o网站的源代码,没有域名限制
- Xcode界面简介
- 在移动Web页面中使用CSS固定页脚
- Java制作证书的工具keytool用法总结
- 阿里巴巴-业务型产品经理一面
- 理解 pkg-config 工具
- CHKen Tray Clock 2.68珍藏版 51KB免装 任务栏显秒 定时 报时提醒 流量显示
- poj1637 混合图欧拉回路的求解 网络流
- GET和POST的区别
- UI中获取图片的三种方式
- Raspberry vnc 连接
- 阿里巴巴-业务型产品经理一面
- Codeforces313 Equivalent Strings(DFS)
- 重学java23种设计模式(7)桥接模式
- VM中Ubuntu的VMware-Tools的安装方法
- NY 55 懒省事的小明 【队列】