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

進(jìn)程模型

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

全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續(xù)更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh

Profile架構(gòu)

這篇文章描述了一個(gè)進(jìn)行中的設(shè)計(jì)重構(gòu),始于2012年1月。

注意:2013年六月之后,這篇文章需要更新。相關(guān)的類被重命名(s/ProfileKeyed/BrowserContextKeyed/)以及移動到components/browser_context_keyed_service中。

Chromium有許多與Profile掛鉤的特性,所謂Profile,即一些與當(dāng)前用戶以及跨越多個(gè)瀏覽器window的當(dāng)前chrome會話。在Chromium剛起步的時(shí)候,profile只有一些動態(tài)的部分:cookie jar包,歷史記錄數(shù)據(jù)庫,書簽數(shù)據(jù)庫,以及與用戶首選項(xiàng)相關(guān)的一些東西。在Chromium工程三年的時(shí)間里,Profile變成了各個(gè)特性的連接點(diǎn),派生出了一些東西像Profile::GetInstantPromoCounter()或者Profile::GetHostContentSettingsMap()。直到這個(gè)文章完成時(shí),在Profile里已經(jīng)有58個(gè)純虛函數(shù)了。

Profile應(yīng)當(dāng)是一個(gè)最小引用,即一種不擁有實(shí)體的句柄對象。

設(shè)計(jì)目標(biāo)

  • 我們必須能夠分段地轉(zhuǎn)移到新的架構(gòu)中。每次轉(zhuǎn)移一個(gè)服務(wù)和特性。我們不能停止地球的轉(zhuǎn)動,不能在一瞬間轉(zhuǎn)換所有的東西。寫下這些東西的時(shí)候,我們已經(jīng)將19個(gè)服務(wù)移出Profile了。
  • 我們應(yīng)當(dāng)只對調(diào)用端做小的修改,在調(diào)用端,Profile被用于在問題中獲取服務(wù)。
  • 我們必須修復(fù)Profile移除這個(gè)問題。當(dāng)我們開始這項(xiàng)工作時(shí),Profile外只有一小部分對象,處于拆分目的手動整理它們是可接受的。但現(xiàn)在我們有了75個(gè)組件,我們知道手動拆分整理是不對的,正如這里所寫的。有著這么多組件的話,我們不能再依賴手動整理了。
  • 我們必須允許加入編譯新特性或者移除舊特性。現(xiàn)在我們有一些chromium的分支,它們不包含在Windows/Mac/Linux Google Chrome標(biāo)準(zhǔn)構(gòu)建中所有的特性,我們應(yīng)當(dāng)允許給出這樣一種方式,讓這些分支能在不把#ifdef profile.h和profile_impl.h搞得一團(tuán)糟的情況下,成功編譯。這些分支也有他們需要提供的服務(wù)。(允許chromium分支添加它們自己的服務(wù)也觸及我們不能在Profile移除過程中依賴手動整理的原因。)
  • 延伸目標(biāo):將不同的特性隔離到它們自己的.a或.so文件里,進(jìn)一步減小我們奇葩的編譯鏈接時(shí)間。

BrowserContextKeyedServiceFactory

瀏覽器上下文關(guān)鍵服務(wù)工廠

舊的方式:Profile接口和ProfileImpl實(shí)現(xiàn)

在以前的設(shè)計(jì)里,服務(wù)通常用Profile里的一個(gè)訪問器來獲得:

class ProfileImpl {
  public:
    virtual FooService* GetFooService();
  private:
    scoped_ptr<FooService> foo_service_;
};

在之前的系統(tǒng)里,Profile是由大部分是純虛訪問器組成的結(jié)構(gòu)。Normal(正常),Incognito(匿名)和Testing(測試)profile。

在這個(gè)世界里,Profile是所有活動的中心。profile有用它所有的服務(wù),并向外界傳遞出去。Profile拆分遵循ProfileImpl中對服務(wù)排序的任何原則。另外的分支如果想要增加自己的服務(wù)或移除不需要的服務(wù),而不修改Profile接口,都是不可能的。

新的方式:BrowserContextKeyedServiceFactory

我們不再讓Profile擁有某個(gè)service,而是設(shè)計(jì)了專用的單例FooServiceFactory,比如這樣一個(gè)最小實(shí)現(xiàn):

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;
};

我們有一個(gè)通用的BrowserContextKeyedServiceFactory,它用一個(gè)由你的BuildServiceInstanceFor()方法提供的對象,執(zhí)行與profile相關(guān)的大部分工作。BrowserContextKeyedServiceFactory為你提供了一個(gè)重寫接口,讓你在響應(yīng)Profile生命周期事件時(shí),管理你的Service對象的生命周期,并在service依賴的service關(guān)閉前,關(guān)閉它本身。

一個(gè)絕對最小工廠會提供下面的方法:

  • 一個(gè)static GetInstance()方法,單例指向你的工廠。
  • 一個(gè)構(gòu)造函數(shù),關(guān)聯(lián)這個(gè)BrowserContextKeyedServiceFactory和ProfileDependencyManager實(shí)例,并做DependsOn()聲明。
  • 一個(gè)GetForProfile()方法,包裝BrowserContextKeyedServiceFactory,將返回結(jié)果轉(zhuǎn)換為你需要的返回值。
  • 一個(gè)BuildServiceInstanceFor()方法,框架會為每個(gè)|profile|調(diào)用一次這個(gè)方法,它必須返回你的服務(wù)的一個(gè)合適的實(shí)例。

另外,BrowserContextKeyedServiceFactory為你的控制行為提供了這些另外的輔助:

  • RegisterUserPrefs():每個(gè)Profile在初始化和用戶首選項(xiàng)注冊的地方會調(diào)用它一次

  • 默認(rèn)情況下,BCKSF在給定一個(gè)Incognito profile時(shí)會返回NULL

    • 如果你重寫ServiceRedirectedInIncognito()方法并返回true,它會返回與normal Profile相關(guān)的服務(wù)。
    • 如果你重寫ServiceHasOwnInstanceInIncognito()并返回true,它會為incognito profile創(chuàng)建一個(gè)新的服務(wù)。
  • 默認(rèn)情況下,BCKSF會延遲創(chuàng)建你的service,如果你重寫ServiceIsCreatedWithProfile()并返回true,你的service會與profile一同創(chuàng)建。

  • BCKSF為你在單元測試時(shí)提供了多種方式來控制行為。查看頭文件了解更多。

  • BCKSF為你一種方式提供一種方式增加并固定移除的和釋放的行為。

幾種工廠

并非所有對象都有一樣的生命周期和內(nèi)存管理。前面的段落是一個(gè)主要的簡化版本;基類BrowserContextKeyedBaseFactory定義了大多數(shù)常見依賴部分,BrowserContextKeyedServiceFactory是一個(gè)具體處理通常對象的工廠。另一個(gè)RefcountedBrowserContextKeyedServiceFactory在語義上以及對RefCountedThreadSafe對象的存儲上有輕微的差異。

關(guān)于復(fù)雜度的一個(gè)小插曲

上面的這些,在實(shí)現(xiàn)上比之前的版本要復(fù)雜許多,這是否值得呢?

Yes.

我們絕對應(yīng)該強(qiáng)調(diào)服務(wù)的獨(dú)立性。正如它今天的樣子,在多profile模式不再有必要之后,我們沒有馬上去掉profile,因?yàn)樵谌サ魀rofile時(shí),我們的crash率太高了,不能為用戶所接受。我們有75個(gè)組件插在profile的生命周期當(dāng)中,他們之間的依賴圖如此復(fù)雜以至于我們簡單的手動整理不能處理這種復(fù)雜度。上面所有可重寫的行為之所以存在,是因?yàn)樗擅總€(gè)服務(wù),特定的廣告,以及復(fù)制粘貼實(shí)現(xiàn)。

我們同樣需要讓其他chromium分支能夠方便地添加他們自己的特性,或者排除它們的構(gòu)建以外的特性。

依賴管理概覽

考慮這一點(diǎn),讓我們看一下依賴管理是如何工作的。我們有ProfileDependencyManager的一個(gè)單例,它與Profile創(chuàng)建與銷毀相關(guān)聯(lián)。一個(gè)PKSF由ProfileDependencyManager來注冊以及注銷。ProfileDependencyManager的工作是確保各個(gè)服務(wù)用一種安全的方式創(chuàng)建與銷毀。

考慮下面這個(gè)有者三個(gè)服務(wù)工廠的例子:

AlphaServiceFactory::AlphaServiceFactory()
    : BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
}

BetaServiceFactory::BetaServiceFactory()
    : BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
  DependsOn(AlphaServiceFactory::GetInstance());
     }

GammaServiceFactory::GammaServiceFactory()
    : BrowserContextKeyedServiceFactory(ProfileDependencyManager::GetInstance()) {
  DependsOn(BetaServiceFactory::GetInstance());
     }

在這個(gè)簡化的代碼結(jié)構(gòu)中,顯式聲明的依賴意味著這些服務(wù)唯一有效的創(chuàng)建順序是[Alpha, Beta, Gamma],唯一有效的銷毀順序是[Gamma, Beta, Alpha]。上面的這些是你,也就是這個(gè)框架的使用者,所必須指定的依賴。

在幕后,ProfileDependencyManager管理所聲明的依賴的關(guān)系,展示了一個(gè)Kahn的拓?fù)渑判?,并在CreateProfileServices()和DestroyProfileServices()中得到應(yīng)用。

五分鐘了解如何轉(zhuǎn)換你的代碼

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

如果你需要上面這些步驟的例子,可以看看這些補(bǔ)?。?/p>

  • r100516: 一個(gè)簡單的例子,添加了一個(gè)新的ProfileKeyedService。這展示了一個(gè)最小的ServiceFactory子類。
  • r104806: plugin_prefs_factory.h給出了一個(gè)例子,闡述了如何處理(必須)引用計(jì)數(shù)的東西。 這個(gè)補(bǔ)丁也展示了如何將你的首選項(xiàng)移到你的ProfileKeyedServiceFactory中。

調(diào)試技巧

使用依賴抽象器

Chrome有一個(gè)內(nèi)置的方法來導(dǎo)出profile依賴圖,生成一個(gè)GraphViz格式的文件。當(dāng)你命令行運(yùn)行chrome,附帶--dump-browser-context-graph標(biāo)記時(shí),chrome會將依賴信息寫到你的/path/to/profile/browser-context-dependencies.dot文件。然后你可以用dot轉(zhuǎn)化這個(gè)文件,dot是GraphViz的一個(gè)部分:

dot -Tpng /path/to/profile/browser-context-dependencies.dot > png-file.png

這會給你一個(gè)像下面這樣的抽象圖(2012年1月23日生成,點(diǎn)擊查看大圖):

Shutdown時(shí)的crash

如果出現(xiàn)了一個(gè)這樣的棧:

ProfileDependencyManager::AssertProfileWasntDestroyed()
ProfileKeyedServiceFactory::GetServiceForProfile()
MyServiceFactory::GetForProfile()
... [Probably a bunch of frames] ...
OtherService::~OtherService()
ProfileKeyedServiceFactory::ProfileDestroyed()
ProfileDependencyManager::DestroyProfileServices()
ProfileImpl::~ProfileImpl()

問題就是,OtherService沒有正確地依賴MyService。在你使用Shutdown()組件時(shí),框架會觸發(fā)一個(gè)assert。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容