1 TimedEventQueue

目录

1、概述

2、一个典型的使用TimedEventQueue的例子

3、TimedEventQueue类介绍

 

1概述

    TimeEventQueue是android多媒体系统中实现异步操作的一种手段

    在Awesomeplayer中,依赖mVideoEvent、mStreamDoneEvent、mBufferingEvent、mCheckAudioStatusEvent等几个事件驱动着整个播放流程

    可以认为TimeEventQueue 是一个调度系统,调度的对象是事件Event,一个TimeEventQueue 可以同时处理多个事件

    本篇只分析TimeEventQueue 类结构及其用法

 

2、一个典型的使用TimedEventQueue的例子

一个简单的使用TimedEventQueue 的序列如下

mEventRead = new dtEvent(this,&dtPlayer::onReadEvent);             
mQueue.start();             
mQueue.postEvent(mEventRead);             
mQueue.stop();

【说明】

首先建立一个事件

启动TimedEventQueue

通过postEvent触发事件,事件触发后会执行mEventRead对应的fire函数

最后结束

 

我们首先建立一个事件类

structdtEvent : publicTimedEventQueue::Event {              
    dtEvent(dtPlayer *player,void(dtPlayer::*method)()):               
      mPlayer(player),              
      mMethod(method)               
    {              
    }              
  
protected:              
    virtual~dtEvent() {}              
  
    virtualvoid fire(TimedEventQueue *queue, int64_t/* now_us */) {              
        (mPlayer->*mMethod)();              
    }              
  
private:              
    dtPlayer *mPlayer;              
    void(dtPlayer::*mMethod)();              
  
    dtEvent(constdtEvent &);              
    dtEvent &operator=(constdtEvent &);              
};


这里处理方法与awesomeplayer等类似,由于Event是抽象类,不能直接使用,因此需要建立一个继承自Event的子类,并重载fire方法

这里构造函数中传入了dtPlayer::*method 参数,作为fire中执行的函数体。这样就保证了对于每一个事件,通过传入不同的mMethod方法,便可以做出不同的反应

 

类似Awesomeplayer,我们需要为TimedEventQueue建立一个载体类dtPlayer

class dtPlayer{           
public:           
  
    dtPlayer();           
  
    virtual ~dtPlayer(){};           
  
private:           
    TimedEventQueue mQueue;           
    bool mQueueStarted;           
  
    sp<TimedEventQueue::Event> mEventRead;             
    sp<TimedEventQueue::Event> mEventWrite;             
    sp<TimedEventQueue::Event> mEventSearch;             
  
    voidonReadEvent();           
    voidonWriteEvent();           
    voidonSearchEvent();           
public:           
    voidstart();           
    voidstop();           
    bool isplaying();           
    voidpostRandomEvent();           
private:           
    dtPlayer(constdtPlayer &);           
    dtPlayer &operator=(constdtPlayer &);           
};


 在载体类中,我们有

TimedEventQueue 对象mQueue,

三个Event对象,分别是mEventRead mEventWrite mEventSearch

三个方法,分别是传入给上面三个事件的参数,是void onReadEvent(); void onWriteEvent(); void onSearchEvent();

状态机:start stop 等

postRandomEvent 方法:封装的触发事件的方法

 

下面看下具体实现

dtPlayer::dtPlayer()          
{          
  mEventRead =new dtEvent(this,&dtPlayer::onReadEvent);            
  mEventWrite =new dtEvent(this,&dtPlayer::onWriteEvent);            
  mEventSearch =new dtEvent(this,&dtPlayer::onSearchEvent);           
  
  mQueueStarted=false;           
}

先看下构造函数,构造函数主要是生成三个事件,并将参数传递进去,这里可以看到调用的是重载的event的构造函数

并设置mQueue状态

void dtPlayer::onReadEvent()          
{          
  ALOGI("--read event occur-- read something \n"); }          
  
void dtPlayer::onWriteEvent()          
{          
  ALOGI("--write event occur-- write something \n"); }          
  
void dtPlayer::onSearchEvent()          
{          
  ALOGI("--search event occur-- search something \n");             
}


 对应的实现,只加了打印,表示执行过了

void dtPlayer::start()        
{        
  if(mQueueStarted==false)        
    mQueue.start();        
  mQueueStarted=true;        
  printf("dtplayer start  \n");        
}        
  
void dtPlayer::stop()        
{        
  if(mQueueStarted==true)        
    mQueue.stop(true);        
  mQueueStarted=false;        
  
}        
  
bool dtPlayer::isplaying()        
{        
  return(mQueueStarted==true);        
}

状态机代码,主要是控制mQueue的状态

void dtPlayer::postRandomEvent()        
{        
  mQueue.postEvent(mEventRead);        
}

事件触发函数,这里可以看到触发方法便是调用mQueue的postEvent方法,并且将事件对象作为参数传入。这样便可以执行对应事件的fire方法了

 

extern "C"int dt_test()        
{        
    ALOGE("--post event start \n");        
    dtPlayer *player=newdtPlayer();        
    player->start();        
    player->postRandomEvent();        
    player->stop();        
    ALOGE("--post event end\n");        
    sleep(5);        
    return0;        
}

这里实现了测试code,只是简单的触发一个事件然后返回

【说明】具体的例子源代码已经打包放在了附件中

测试方法:

(1)将压缩包拷贝至framework/av/media/libstagefright目录下

(2)解压缩并进入_timeeventqueue_test文件夹

(3)mm

(4)将可执行文件拷贝到目标板执行即可

 

通过上面的例子可以看到,TimedEventQueue 的使用时比较简单的,只要新建一个Event子类,并重载fire方法

之后通过TimedEventQueue 的postEvent等就可以出发执行了

而且这里一个TimedEventQueue 可以支持多个事件

 

下面看下TimedEventQueue 具体是如何实现这一机制的

 

3、TimedEventQueue类介绍

2.1 event事件

TimedEventQueue调度的对象是事件

先看下事件的定义,事件是定义在TimedEventQueue 结构体内部的结构体

 typedef int32_t event_id;
    struct Event : public RefBase {
        Event()
            : mEventID(0) {
        }
        virtual ~Event() {}
        event_id eventID() {
            return mEventID;
        }
    protected:
        virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0;
    private:
        friend class TimedEventQueue;
        event_id mEventID;
        void setEventID(event_id id) {
            mEventID = id;
        }
        Event(const Event &);
        Event &operator=(const Event &);
    };


说明:

mEventID 事件ID

fire 纯虚函数,事件触发时调度执行的函数

因此Event不能直接使用,需继承并重载fire方法后使用

 

 2.2 TimedEventQueue 一些重要方法解析

挑一下比较重要的解释下

void TimedEventQueue::start() {        
    if(mRunning) {        
        return;        
    }        
    mStopped =false;        
    pthread_attr_t attr;        
    pthread_attr_init(&attr);        
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);        
    pthread_create(&mThread, &attr, ThreadWrapper,this);        
    pthread_attr_destroy(&attr);        
    mRunning =true;        
}


这里调用mQueue->start 后主要是启动一个线程,看下线程代码

// static        
void *TimedEventQueue::ThreadWrapper(void *me) {        
  
    androidSetThreadPriority(0, ANDROID_PRIORITY_FOREGROUND);        
  
    static_cast<TimedEventQueue *>(me)->threadEntry();        
  
    returnNULL;        
}

 这里线程实现方法的主要作用是调用mQueue->threadEntry 方法,因为上面传入的参数是this指针

代码比较多我们分段来看

void TimedEventQueue::threadEntry() {       
    prctl(PR_SET_NAME, (unsigned long)"TimedEventQueue",0, 0,0);       
  
    for(;;) {       
        int64_t now_us =0;       
        sp<Event> event;       
  
        {       
            Mutex::Autolock autoLock(mLock);       
  
            if(mStopped) {       
                break;       
            }       
  
            while(mQueue.empty()) {       
                mQueueNotEmptyCondition.wait(mLock);       
            }

首先判断mStopped,确定是否要退出

然后判断mQueue中是否为空,若未空表示没有要处理的事件,则等待

event_id eventID = 0;      
            for(;;) {      
                if(mQueue.empty()) {      
                    // The only event in the queue could have been cancelled      
                    // while we were waiting for its scheduled time.      
                    break;      
                }      
  
                List<QueueItem>::iterator it = mQueue.begin();      
                eventID = (*it).event->eventID();      
  
                now_us = ALooper::GetNowUs();      
                int64_t when_us = (*it).realtime_us;      
  
                int64_t delay_us;      
                if(when_us < 0 || when_us == INT64_MAX) {      
                    delay_us = 0;      
                }else {      
                    delay_us = when_us - now_us;      
                }      
  
                if(delay_us <= 0) {      
                    break;      
                }      
  
                staticint64_t kMaxTimeoutUs = 10000000ll;  // 10 secs      
                booltimeoutCapped = false;      
                if(delay_us > kMaxTimeoutUs) {      
                    ALOGW("delay_us exceeds max timeout: %lld us", delay_us);      
  
                    // We'll never block for more than 10 secs, instead      
                    // we will split up the full timeout into chunks of      
                    // 10 secs at a time. This will also avoid overflow      
                    // when converting from us to ns.      
                    delay_us = kMaxTimeoutUs;      
                    timeoutCapped =true;      
                }      
  
                status_t err = mQueueHeadChangedCondition.waitRelative(      
                        mLock, delay_us * 1000ll);      
  
                if(!timeoutCapped && err == -ETIMEDOUT) {      
                    // We finally hit the time this event is supposed to      
                    // trigger.      
                    now_us = ALooper::GetNowUs();      
                    break;      
                }      
            }


若mQueue事件列表非空,则有要处理的事件,进入for loop进行处理,主要工作有

从mQueue的event列表中获取一个event

判断是否执行,如果时间到了(delay_us <= 0),则跳出循环执行事件处理函数,否则若需等待时间低于10s,则mQueueHeadChangedCondition.waitRelative等待。若高于10s,等10s后重新轮询

当条件满足时,通过event = removeEventFromQueue_l(eventID);获取事件对象,并调用event->fire(this, now_us);

 

这里有几个问题

(1)对mQueue,每个event都有一个唯一eventID,mQueue通过eventID来获取事件对象,eventID如何计算的

此问题的答案在mQueue->postEvent中。看下代码

TimedEventQueue::event_id TimedEventQueue::postEvent(constsp<Event> &event) {   
    // Reserve an earlier timeslot an INT64_MIN to be able to post   
    // the StopEvent to the absolute head of the queue.   
    returnpostTimedEvent(event, INT64_MIN + 1);   
}

TimedEventQueue::event_id TimedEventQueue::postTimedEvent(   
        constsp<Event> &event, int64_t realtime_us) {   
    Mutex::Autolock autoLock(mLock);   
  
    event->setEventID(mNextEventID++);   
  
    List<QueueItem>::iterator it = mQueue.begin();   
    while(it != mQueue.end() && realtime_us >= (*it).realtime_us) {   
        ++it;   
    }   
  
    QueueItem item;   
    item.event = event;   
    item.realtime_us = realtime_us;   
  
    if(it == mQueue.begin()) {   
        mQueueHeadChangedCondition.signal();   
    }   
  
    mQueue.insert(it, item);   
  
    mQueueNotEmptyCondition.signal();   
  
    returnevent->eventID();   
}


 从代码可以看出,postEvent会调用postTimedEvent,而在postTimedEvent中第一步操作就是设置eventID,这也就保证了每个event都有唯一一个eventID标识

另外,会将事件通过mQueue.insert(it, item); 插入到TimedEventQueue->mQueue中,如下定义

struct QueueItem {   
    sp<Event> event;   
    int64_t realtime_us;   
};

mQueue是一个QueueItem列表,存储了事件对象及要触生的时间

(2)每次mQueue都从列表中取出第一个事件进行处理,但有个问题,若此事件阻塞的时候,其他事件需要触发,怎么处理?

玄机同样在postTimedEvent中,在插入列表之前,会首先查找要插入到TimedEventQueue->mQueue 中的位置,按照触发时间排序

 

2.3 其他方法介绍

TimedEventQueue 中还有一些接口扩展使得其功能更加强大,主要有

event_id postEventToBack(const sp<Event> &event); 插入到事件列表末尾

TimedEventQueue::event_id TimedEventQueue::postEventWithDelay(const sp<Event> &event, int64_t delay_us) ; 注册事件同时设置delay时间

bool TimedEventQueue::cancelEvent(event_id id);注销某个事件

void TimedEventQueue::cancelEvents(*);注销所有事件

你可能感兴趣的:(1 TimedEventQueue)