Chromium 中的 Profile 只是个文件夹吗

2025-05-15 pv

普通的 Chrome 或者 Edge 用户很少关注 Profile 这个文件夹,但他们一定清楚,浏览器是支持多账户的

不同账户之间完全独立,互不干扰。

技术上怎么实现呢?

答案其实很简单。每个账户相关联的状态,像浏览记录,Cookie 等,都保存在各自的 Profile 文件夹下。

这有两个好处。

一是借助操作系统自身提供的文件管理能力,简化状态管理。

另外是把隐式的内存状态显式化(内存数据落盘),不仅便于调试,也便于上一次 Session 的快速恢复。

这里要区分下文中 Profile 的两种含义:除了表示 Profile 文件夹外,也代表一组该文件夹相关联的内存状态。

1. 历史演进

起初,Profile 管理的状态很少,只包含 Cookie、History、Bookmark 和一些用户偏好。

但是伴随更多功能被添加进来,Profile 逐渐膨胀。

这就导致,在创建和销毁 Profile 对象时,不同状态间的依赖关系需要有效管理。比如 Sync 服务,必须在 History、Bookmark 等完成初始化后才能启动,否则过早启动会导致一系列难以预测的混乱。

像这种牵涉面极广的系统性问题,解决需要在架构层面动刀子

于是在 2012 年,忍无可忍的 Chromium 开发组,引入了一个新的 Profile 模型。相较于之前的大包大揽,新的模型中,Profile 只是一系列句柄对象(Handle object),仅包含少量状态。并且,为了解决上面提到的依赖问题,两阶段关闭和一个 DependencyManager 被实现。

我品了品,这里面有两个很重要的架构理念:

  1. 大多数问题都可以通过引入一个中间层解决
  2. 管理状态的最好方式,就是没有状态需要管理

2. 当前设计

设计的目标有三个:

  1. Profile 类的大小必须可控。将所有代码放在一起听上去很有吸引力(实现简单),但可维护性差。
  2. 启动和销毁必须足够健壮(Robust)。数百个服务的启动,要能实现自动化编排(Orchestration)。
  3. 必须允许功能被编译进出(In/Out)。便于不同平台,以及和第三方代码库的集成。

2.1 KeyedService

KeyedService 是一个看上去很简单的类,却是构建这套方案的原子基础。

特点有三个:

  1. 有一个 virtual destructor
  2. 生命期由类似 ProfileKeyedServiceFactory 的单例管理
  3. 有一个重载的 Shutdown(),会在析构前被调用,从而实现两阶段关闭

例如要实现一个新的 FooService 类。

class FooService : public KeyedService {
public:
explicit FooService(PrefService* profile_prefs, BarService* bar_service);
virtual ~FooService() = default;
FooService(const FooService&) = delete;
FooService& operator=(const FooService&) = delete;
private:
PrefService* profile_prefs_;
BarService* bar_service_;
};

创建的话通过一个工厂函数。

class FooServiceFactory : public ProfileKeyedServiceFactory {
public:
static FooService* GetForProfile(Profile* profile);
static FooServiceFactory* GetInstance();
private:
FooServiceFactory();
~FooServiceFactory() override;
// BrowserContextKeyedServiceFactory:
std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const override;
};

可见,增加新的 Service,并不会增加 Profile 对象的大小,因为每个 Service 的状态由自己维护。同时,因为继承自同一套接口,每个 Service 可以通过一种相同的方式与 Profile 绑定。

打个形象的比喻,以前的 Profile 是一个单一节点,所有状态都在内部。

现在的 Profile,更像是一个图的起始节点,从它开始可以遍历到其他任意节点。

听上去有点像是服务端的微服务(Microservices)

2.2 Two-phase Shutdown

两阶段关闭,意思是在对象析构前,要先执行 Shutdown()。看起来有些繁琐,却是用来解决生产中存在的问题。

因为 Service 之间的依赖,有时不是单向的,因此可能产生循环依赖(Circular Dependency)

第一阶段借助 Shutdown() 消除依赖,第二阶段利用析构释放对象,便可以很好地完成清理。

2.3 Dependency Management

DependencyManager 是用来确保 Service 之间以一个正确的顺序初始化和销毁。

看一个使用上的例子。

AlphaServiceFactory::AlphaServiceFactory()
: ProfileKeyedServiceFactory("AlphaService") {}
BetaServiceFactory::BetaServiceFactory()
: ProfileKeyedServiceFactory("BetaService") {
DependsOn(AlphaServiceFactory::GetInstance());
}
GammaServiceFactory::GammaServiceFactory()
: ProfileKeyedServiceFactory("GammaService") {
DependsOn(BetaServiceFactory::GetInstance());
}

底层的实现并不复杂,就是构造一个有向无环图,然后拓扑排序

3. 启示

和以往谈到 Chromium 的文章类似,Profile 是一个简单的概念,却有着复杂实现。

它有着清晰的演进脉络,当过往的架构成为限制进化的瓶颈,就要重构,用新的,更高维的解决方案代替。

往深处发掘,发现其思路和办法很直接,比如增加中间层,比如最小化状态管理,再比如图论中的拓扑排序。

工程讲究的,不是理论创新,而是应用场景的创新

用经典的工具,解决新的问题,也是一种创新。

4. 总结

我本人并非计算机科班出身,而是地理出身。

很多数据结构,包括算法,都是自学,了解原理,能刷题,是初衷。

工程的代码写久了,研究深了,不得不感慨,理论并不完全是抽象的文字。

当然,只有结合实践,才能真正发挥它的威力!

大概这就是“干中学”的意思吧。

(完)

参考

  1. Profile Architecture🔗
在 GitHub 上编辑本页面

最后更新于: 2025-05-29T08:18:57+08:00