【Chromium中文文檔】Profile架構(看看谷歌家的重構)

進程模型

轉載請注明出處:
https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/General_Architecture/Profile_Architecture.html

全書地址
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()中得到應用。

五分鐘了解如何轉換你的代碼

  1. 讓你已有的FooService繼承BrowserContextKeyedService。
  2. 可能的話,不要再讓你的FooService得到引用計數了。大多數與Profile相關的被引用計數的對象似乎因為他們沒有使用base::bind/WeakPtrFactory,而需要在多線程使用自己的數據。(在這個例子里,線程安全的引用計數是有必要的,比如,多線程訪問時,讓你的工廠繼承自RefcountedBrowserContextKeyedServiceFactory,這樣一切都能正常工作。)
  3. 構建一個簡單繼承自BrowserContextKeyedServiceFactory的FooServiceFactory。消費者請求FooService時,你的FooServiceFactory將會是主要的訪問點。
  4. BrowserContextKeyedService* BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(content::BrowserContext* context)是唯一需要的函數。傳入一個BrowserContext句柄,返回一個有效的FooService。
  5. 你可以用ServiceRedirectedInIncognito() 和 ServiceHasOwnInstanceInIncognito()控制incognito行為。
  6. 把你的服務添加到chrome_browser_main_extra_parts_profiles.cc中中的EnsureBrowserContextKeyedServiceFactoriesBuilt()列表
  7. 理解Shutdown行為。出于歷史原因,我們必須做兩個階段的Shutdown操作:
  8. 每個BrowserContextKeyedService首先要調用它的Shutdown()方法。使用這個方法來移除對Profile或其他服務對象的弱引用。
  9. 刪除每個BrowserContextKeyedService,運行它的析構器。最小化的工作需要在這里完成。調用任何*ServiceFactory::GetForProfile()會在調試模式下觸發的一個斷言。
  10. 將每個"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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容