设计解析
Chromium的很多功能依赖于Profile对象。Profile是一个上下文对象,类间关系为:
例如,浏览器的历史记录、书签、cookie记录等功能都是深深依赖Profile对象。在最初版本的实现中,Profile class承载了所有这些功能。于是随着浏览器的迭代,Profile越来越庞大,它直接依赖的子模块也越来越多。Profile类的可维护性变差,子模块的启动和结束顺序靠手工维护变得越来越不现实。
设计目标
- 把 Profile控制在可维护的大小内
- Service的启动和结束要健壮可控
- 支持功能的可选编译(为了跨平台,不是本方案的重点)
引入KeyedService & KeyedServiceFactory
- 所有功能Service类都继承自KeyedService;
- 每一个Service要有自己的ServiceFactory, ServiceFactory的职责是创建和销毁Service实例。
比如我们要实现一个Superman功能,并且我们需要存储一些状态在Profile里。通常我们会实现一个SupermanService
类
// 功能类
class SupermanService: public KeyedService{
...
}
// 工厂类
class SupermanServiceFactory: public KeyedServiceFactory {
// 单例方法
static SupermanServiceFactory* GetInstance() {...}
// 销毁Service, 调用每一个Service实现的自己的Shutdown()方法
void ContextShutdown() {...}
}
功能类的基类(KeyedService)
KeyedService的特点
- 虚析构函数
- 生命周期由 BrowserContextKeyedServiceFactory 单例管理
- 有一个可覆写的接口Shutdown(), 在析构函数之前被调用,实现two-phase shutdown
工厂类
至少提供以下4个接口:
- A static
GetInstance()
method that refers to the factory as a singleton. - A constructor that associates the factory with the
BrowserContextDependencyManager
singleton, and makesDependsOn()
declarations. - A
GetForProfile()
method that wraps BCKSF, casting the result back to whatever type we need to return. - A
BuildServiceInstanceFor()
method which is called once by the framework for each profile. It must initialize and return a proper instance of our service.
使用Service
用GetForProfile()接口
ServiceFactory::GetForProfile(profile)
Two-phase Shutdown
在调用析构函数之前,先调用Service的Shutdown()接口。每一个Service都会实现自己的Shutdown()
接口,其职责是清理资源。
Shutdown()接口是虚函数,在基类被调用,因此利用C++的多态可以实现Shutdown的自动调用。
正是因为在Shutdown()中做了清理工作,所以解决了环形依赖问题。
环形依赖问题:
对象以shared_ptr使用并相互依赖,shared_ptr的ref_count为0才会调用析构函数。如果使用Single-phase shutdown,也即仅仅依赖析构函数,如果shared_ptr之间存在环形依赖,是无法正确析构的。
Service实现的Shutdown()接口在ServiceFactory工厂类中被调用,关键代码:
// keyed_service_factory.cc
void KeyedServiceFactory::ContextShutdown(void* context) {
auto iterator = mapping_.find(context);
if (iterator != mapping_.end() && iterator->second)
iterator->second->Shutdown();
}
为什么需要KeyedServiceFactory mapping_字段
KeyedServiceFactory是单例,关键代码:(以SyncServiceFactory为例)
// static
SyncServiceFactory* SyncServiceFactory::GetInstance() {
// 单例调用
return base::Singleton<SyncServiceFactory>::get();
}
但是具体的Service不是单例,service实例跟context是1对1的关系,所以用mapping_记录。关键代码:
// components/keyed_service/core/keyed_service_factory.cc
KeyedService* KeyedServiceFactory::GetServiceForContext(void* context,
bool create) {
...
auto iterator = mapping_.find(context);
if (iterator != mapping_.end())
return iterator->second.get(); // mapping_命中,直接返回
// Object not found.
if (!create)
return nullptr; // And we're forbidden from creating one.
// Create new object.
// Check to see if we have a per-context testing factory that we should use
// instead of default behavior.
// 新建service实例
std::unique_ptr<KeyedService> service;
auto factory_iterator = testing_factories_.find(context);
if (factory_iterator != testing_factories_.end()) {
if (factory_iterator->second) {
service = factory_iterator->second.Run(context);
}
} else {
service = BuildServiceInstanceFor(context);
}
// 建立context<->service的一对一关系,更新mapping_
return Associate(context, std::move(service));
}
Service的设计思想
工厂+单例。
工厂类实例化时用DependsOn声明依赖关系,在析构阶段利用拓扑排序实现按序析构。
所有的KeyedServiceBaseFactory之间的依赖关系由 DependencyManager
管理。内部有一个DependencyGraph
结构,被依赖(depended)和依赖方(dependee)的构造和析构顺序:
- 所有depended在dependee之后构造,在dependee之前析构
正确顺序的保证由DependencyGraph
基于拓扑排序保证。
Service初始化的全部流程:
软件设计启示
- 如果一个实体需要承载多种不同的功能,可以把功能分类,引入
Service
将其拆分。 - Service承载具体的功能实现,用ServiceFactory工厂类管理Service的构造和清除(Two-phase Shutdown)。
- 基于有向无环图(DAG)的拓扑排序保证Service的构造和析构顺序。