【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將會是主要的訪問點。
    1. BrowserContextKeyedService* BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(content::BrowserContext* context)是唯一需要的函數。傳入一個BrowserContext句柄,返回一個有效的FooService。
    2. 你可以用ServiceRedirectedInIncognito() 和 ServiceHasOwnInstanceInIncognito()控制incognito行爲。
  4. 把你的服務添加到*chrome_browser_main_extra_parts_profiles.cc中中的EnsureBrowserContextKeyedServiceFactoriesBuilt()列表*。
  5. 理解Shutdown行爲。出於歷史原因,我們必須做兩個階段的Shutdown操作:
    1. 每個BrowserContextKeyedService首先要調用它的Shutdown()方法。使用這個方法來移除對Profile或其他服務對象的弱引用。
    2. 刪除每個BrowserContextKeyedService,運行它的析構器。最小化的工作需要在這裏完成。調用任何*ServiceFactory::GetForProfile()會在調試模式下觸發的一個斷言。
  6. 將每個”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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章