進程模型
全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh
Profile架構
這篇文章描述了一個進行中的設計重構,始于2012年1月。
注意:2013年六月之后,這篇文章需要更新。相關的類被重命名(s/ProfileKeyed/BrowserContextKeyed/)以及移動到components/browser_context_keyed_service中。
Chromium有許多與Profile掛鉤的特性,所謂Profile,即一些與當前用戶以及跨越多個瀏覽器window的當前chrome會話。在Chromium剛起步的時候,profile只有一些動態的部分:cookie jar包,歷史記錄數據庫,書簽數據庫,以及與用戶首選項相關的一些東西。在Chromium工程三年的時間里,Profile變成了各個特性的連接點,派生出了一些東西像Profile::GetInstantPromoCounter()或者Profile::GetHostContentSettingsMap()。直到這個文章完成時,在Profile里已經有58個純虛函數了。
Profile應當是一個最小引用,即一種不擁有實體的句柄對象。
設計目標
- 我們必須能夠分段地轉移到新的架構中。每次轉移一個服務和特性。我們不能停止地球的轉動,不能在一瞬間轉換所有的東西。寫下這些東西的時候,我們已經將19個服務移出Profile了。
- 我們應當只對調用端做小的修改,在調用端,Profile被用于在問題中獲取服務。
- 我們必須修復Profile移除這個問題。當我們開始這項工作時,Profile外只有一小部分對象,處于拆分目的手動整理它們是可接受的。但現在我們有了75個組件,我們知道手動拆分整理是不對的,正如這里所寫的。有著這么多組件的話,我們不能再依賴手動整理了。
- 我們必須允許加入編譯新特性或者移除舊特性。現在我們有一些chromium的分支,它們不包含在Windows/Mac/Linux Google Chrome標準構建中所有的特性,我們應當允許給出這樣一種方式,讓這些分支能在不把#ifdef profile.h和profile_impl.h搞得一團糟的情況下,成功編譯。這些分支也有他們需要提供的服務。(允許chromium分支添加它們自己的服務也觸及我們不能在Profile移除過程中依賴手動整理的原因。)
- 延伸目標:將不同的特性隔離到它們自己的.a或.so文件里,進一步減小我們奇葩的編譯鏈接時間。
BrowserContextKeyedServiceFactory
瀏覽器上下文關鍵服務工廠
舊的方式:Profile接口和ProfileImpl實現
在以前的設計里,服務通常用Profile里的一個訪問器來獲得:
class ProfileImpl {
public:
virtual FooService* GetFooService();
private:
scoped_ptr<FooService> foo_service_;
};
在之前的系統里,Profile是由大部分是純虛訪問器組成的結構。Normal(正常),Incognito(匿名)和Testing(測試)profile。
在這個世界里,Profile是所有活動的中心。profile有用它所有的服務,并向外界傳遞出去。Profile拆分遵循ProfileImpl中對服務排序的任何原則。另外的分支如果想要增加自己的服務或移除不需要的服務,而不修改Profile接口,都是不可能的。
新的方式:BrowserContextKeyedServiceFactory
我們不再讓Profile擁有某個service,而是設計了專用的單例FooServiceFactory,比如這樣一個最小實現:
class FooServiceFactory : public BrowserContextKeyedServiceFactory {
public:
static FooService* GetForProfile(Profile* profile);
static FooServiceFactory* GetInstance();
private:
friend struct DefaultSingletonTraits<FooServiceFactory>;
FooServiceFactory();
virtual ~FooServiceFactory();
// BrowserContextKeyedServiceFactory:
virtual BrowserContextKeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const OVERRIDE;
};
我們有一個通用的BrowserContextKeyedServiceFactory,它用一個由你的BuildServiceInstanceFor()方法提供的對象,執行與profile相關的大部分工作。BrowserContextKeyedServiceFactory為你提供了一個重寫接口,讓你在響應Profile生命周期事件時,管理你的Service對象的生命周期,并在service依賴的service關閉前,關閉它本身。
一個絕對最小工廠會提供下面的方法:
- 一個static GetInstance()方法,單例指向你的工廠。
- 一個構造函數,關聯這個BrowserContextKeyedServiceFactory和ProfileDependencyManager實例,并做DependsOn()聲明。
- 一個GetForProfile()方法,包裝BrowserContextKeyedServiceFactory,將返回結果轉換為你需要的返回值。
- 一個BuildServiceInstanceFor()方法,框架會為每個|profile|調用一次這個方法,它必須返回你的服務的一個合適的實例。
另外,BrowserContextKeyedServiceFactory為你的控制行為提供了這些另外的輔助:
RegisterUserPrefs():每個Profile在初始化和用戶首選項注冊的地方會調用它一次
-
默認情況下,BCKSF在給定一個Incognito profile時會返回NULL
- 如果你重寫ServiceRedirectedInIncognito()方法并返回true,它會返回與normal Profile相關的服務。
- 如果你重寫ServiceHasOwnInstanceInIncognito()并返回true,它會為incognito profile創建一個新的服務。
默認情況下,BCKSF會延遲創建你的service,如果你重寫ServiceIsCreatedWithProfile()并返回true,你的service會與profile一同創建。
BCKSF為你在單元測試時提供了多種方式來控制行為。查看頭文件了解更多。
BCKSF為你一種方式提供一種方式增加并固定移除的和釋放的行為。
幾種工廠
并非所有對象都有一樣的生命周期和內存管理。前面的段落是一個主要的簡化版本;基類BrowserContextKeyedBaseFactory定義了大多數常見依賴部分,BrowserContextKeyedServiceFactory是一個具體處理通常對象的工廠。另一個RefcountedBrowserContextKeyedServiceFactory在語義上以及對RefCountedThreadSafe對象的存儲上有輕微的差異。
關于復雜度的一個小插曲
上面的這些,在實現上比之前的版本要復雜許多,這是否值得呢?
Yes.
我們絕對應該強調服務的獨立性。正如它今天的樣子,在多profile模式不再有必要之后,我們沒有馬上去掉profile,因為在去掉profile時,我們的crash率太高了,不能為用戶所接受。我們有75個組件插在profile的生命周期當中,他們之間的依賴圖如此復雜以至于我們簡單的手動整理不能處理這種復雜度。上面所有可重寫的行為之所以存在,是因為它由每個服務,特定的廣告,以及復制粘貼實現。
我們同樣需要讓其他chromium分支能夠方便地添加他們自己的特性,或者排除它們的構建以外的特性。
依賴管理概覽
考慮這一點,讓我們看一下依賴管理是如何工作的。我們有ProfileDependencyManager的一個單例,它與Profile創建與銷毀相關聯。一個PKSF由ProfileDependencyManager來注冊以及注銷。ProfileDependencyManager的工作是確保各個服務用一種安全的方式創建與銷毀。
考慮下面這個有者三個服務工廠的例子:
AlphaServiceFactory::AlphaServiceFactory()
: BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
}
BetaServiceFactory::BetaServiceFactory()
: BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
DependsOn(AlphaServiceFactory::GetInstance());
}
GammaServiceFactory::GammaServiceFactory()
: BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
DependsOn(BetaServiceFactory::GetInstance());
}
在這個簡化的代碼結構中,顯式聲明的依賴意味著這些服務唯一有效的創建順序是[Alpha, Beta, Gamma],唯一有效的銷毀順序是[Gamma, Beta, Alpha]。上面的這些是你,也就是這個框架的使用者,所必須指定的依賴。
在幕后,ProfileDependencyManager管理所聲明的依賴的關系,展示了一個Kahn的拓撲排序,并在CreateProfileServices()和DestroyProfileServices()中得到應用。
五分鐘了解如何轉換你的代碼
- 讓你已有的FooService繼承BrowserContextKeyedService。
- 可能的話,不要再讓你的FooService得到引用計數了。大多數與Profile相關的被引用計數的對象似乎因為他們沒有使用base::bind/WeakPtrFactory,而需要在多線程使用自己的數據。(在這個例子里,線程安全的引用計數是有必要的,比如,多線程訪問時,讓你的工廠繼承自RefcountedBrowserContextKeyedServiceFactory,這樣一切都能正常工作。)
- 構建一個簡單繼承自BrowserContextKeyedServiceFactory的FooServiceFactory。消費者請求FooService時,你的FooServiceFactory將會是主要的訪問點。
- BrowserContextKeyedService* BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(content::BrowserContext* context)是唯一需要的函數。傳入一個BrowserContext句柄,返回一個有效的FooService。
- 你可以用ServiceRedirectedInIncognito() 和 ServiceHasOwnInstanceInIncognito()控制incognito行為。
- 把你的服務添加到chrome_browser_main_extra_parts_profiles.cc中中的EnsureBrowserContextKeyedServiceFactoriesBuilt()列表。
- 理解Shutdown行為。出于歷史原因,我們必須做兩個階段的Shutdown操作:
- 每個BrowserContextKeyedService首先要調用它的Shutdown()方法。使用這個方法來移除對Profile或其他服務對象的弱引用。
- 刪除每個BrowserContextKeyedService,運行它的析構器。最小化的工作需要在這里完成。調用任何*ServiceFactory::GetForProfile()會在調試模式下觸發的一個斷言。
- 將每個"profile_->GetFooService()"實例改為"FooServiceFactory::GetForProfile(profile_)"。
如果你需要上面這些步驟的例子,可以看看這些補丁:
- r100516: 一個簡單的例子,添加了一個新的ProfileKeyedService。這展示了一個最小的ServiceFactory子類。
- r104806: plugin_prefs_factory.h給出了一個例子,闡述了如何處理(必須)引用計數的東西。 這個補丁也展示了如何將你的首選項移到你的ProfileKeyedServiceFactory中。
調試技巧
使用依賴抽象器
Chrome有一個內置的方法來導出profile依賴圖,生成一個GraphViz格式的文件。當你命令行運行chrome,附帶--dump-browser-context-graph標記時,chrome會將依賴信息寫到你的/path/to/profile/browser-context-dependencies.dot文件。然后你可以用dot轉化這個文件,dot是GraphViz的一個部分:
dot -Tpng /path/to/profile/browser-context-dependencies.dot > png-file.png
這會給你一個像下面這樣的抽象圖(2012年1月23日生成,點擊查看大圖):
Shutdown時的crash
如果出現了一個這樣的棧:
ProfileDependencyManager::AssertProfileWasntDestroyed()
ProfileKeyedServiceFactory::GetServiceForProfile()
MyServiceFactory::GetForProfile()
... [Probably a bunch of frames] ...
OtherService::~OtherService()
ProfileKeyedServiceFactory::ProfileDestroyed()
ProfileDependencyManager::DestroyProfileServices()
ProfileImpl::~ProfileImpl()
問題就是,OtherService沒有正確地依賴MyService。在你使用Shutdown()組件時,框架會觸發一個assert。