本文共 3418 字,大约阅读时间需要 11 分钟。
对与现在组件状态现状以及转换过程有点疑惑,自己边熟悉代码边思考。c++标准库以及Boost库并没有提供这种关于状态机类似的基类模板,原因是因为外面类复杂多样,不同类的状态没有共性。但是聚焦到音视频模块,这些类有着类似的特性,因此我们可以简单的总结和基类化一些基类模板,供模块继承使用。
这里指的模块类,一般是比较大的功能模块的接口类或者代理类(常见的如decode,encode等)。一般下面有对应的impl实现类,功能附带缓冲类等。由于是模块类是一个模块整体对外的表现,因此接口,状态也比较多。因此从上层角度出发,统一规划所有模块类进行统一表现,是个比较好的实现。同时也经常会遇到特殊不符合统一的设计的情况。需要特殊特化处理。
a)构造态,(未初始化态uninit)
模块类一般比较大,成员较多。个人习惯构造函数不进行业务处理,不传参数,不抛异常,仅仅对模块类内部成员进行引用,bind等互相关联。完成构造过程。
b)初始化态,(inited)
一般是外面调用,如果仅仅是自己构造函数调用就比较鸡肋了(还不如写到构造函数里面)。可以接受外部传参数。根据外部参数返回Init成功或者失败,或者抛异常,这都可行的。
c) 运行态,(run/start)
一般是外部调用,外面再初始化设置参数,或者初始化前后设置参数,开始运行。严格意义来说运行时期是不接受参数改变的(部分特殊支持运行态改参数也是可以的)。
d) 暂停态,(pause)
个人理解这也是非运行态,只不过保留了运行态的上下文而已。
以上就是我觉得一个组件必须有的4个必要的状态,或者说是用户必须需要关心的状态。说明一点的就是,这里是异步的情况,当然如果有些同步组件设计比较简单,比如是同步的接口,就是 init/doTask/uninit 就完成了工作,那肯定就没有运行态和暂停态了。但是考虑到模块都是负责比较复杂的业务,如果是纯同步的情况没必要设计成模块,或者说纯同步的模块实在不多我们暂不考虑。
另外,这里需要对reset做一下说明,有时候客户希望的reset是恢复到running的初始状态,但是也有客户reset到uninit 状态。我个人觉得uninit 可以完成第二种业务。
总结一下如下图所示:
3.两种处理状态方法
处理状态无非就是看状态和消息是否匹配if(state can proess this msg)。有两种处理方法,第一种是为每个状态写一个处理函数,这个处理函数判断是否响应该消息:
int processInitstateMsg(const Msg& msg){ switch (msg.type) { case start: start(); state = start_state; break; case uninit: ... default: // 状态不对,不处理 break; }}
另一种方法,也可以给每个消息写一个响应处理函数(alivc_framework现在的实现):
int processMsg(const Msg& msg){ switch (msg.type) { case start: return onStartMsg(msg); case ... default: // 未知消息 Log.e("unknown msg type."); }}int onStartMsg(const Msg& startMsg){ if(state ok) { start(); } else { return state invalid. }}
对应这两种设计,谁优谁劣很明显可以看出。由于组件对于自身状态类型是明确的,对于外部处理的消息类型是未知的。如果先按照状态进行筛选,则只需判断一次即可(是否这个消息符合我的状态),如果按照第二种实现,那就需要判断两次了。第一次是processMsg里面判断消息类型(switch msg type), 然后调用每个响应消息的处理函数,然后在每个函数里面判断状态(switch state),进而再进行消息处理。
当然第二种做法并不是一无是处。它可以提供一个stateBase进行抽象,处理公共的消息。
由于状态很多,我们可以通过构造一个基类stateBase,来管理状态。stateBase暴露模块切换函数,内部有三个逻辑组成:1.检查状态,2.具体实现,3.切换状态。以stop为例:
StateBase::Stop(){ //1.check state if(state != pause && state != running) { return false; } // 2.call virtual func impl; StopImpl(); // 3.make state state = inited; return true;}
这样派生类就可以直接实现impl 而无需关心状态了。
实际应用中,有很多不满足使用基类条件的情况。比如类内部有多个线程,并且多个线程有相互依赖关系,并不是一个简单的Start、Stop能解决的; 又或者某个类是封装的第三方库的类,而且这个第三方库定义的状态机与我们之前假设的状态机不同(例如AndroidMediaCodec Flush状态是不能跳转到Running态,必须要先Clear到Init态等等)。如果遇到这些比较棘手的情况,应该抛弃状态基类的方法,自己实现状态机,并且在对外接口中说明状态切换的时机以及特性。
从类设计角度来讲,设计一个状态机,维护自身状态来保证调用和返回的结果是必要的。
由于每个类具体情况不同,需要每个组件自己实现维护自己的状态机。
由于音视频编排组件比较类似,可以提供统一的接口呈现状态转换。
编排组件可以抽象成一个基类统一维护,也可以自己处理,各有优劣。
转载地址:http://zervl.baihongyu.com/