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

進(jìn)程模型

轉(zhuǎn)載請(qǐng)注明出處:
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/)以及移動(dòng)到components/browser_context_keyed_service中先匪。

Chromium有許多與Profile掛鉤的特性,所謂Profile死陆,即一些與當(dāng)前用戶以及跨越多個(gè)瀏覽器window的當(dāng)前chrome會(huì)話待牵。在Chromium剛起步的時(shí)候其屏,profile只有一些動(dòng)態(tài)的部分:cookie jar包,歷史記錄數(shù)據(jù)庫(kù)缨该,書簽數(shù)據(jù)庫(kù)漫玄,以及與用戶首選項(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í)體的句柄對(duì)象询一。

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

  • 我們必須能夠分段地轉(zhuǎn)移到新的架構(gòu)中隐孽。每次轉(zhuǎn)移一個(gè)服務(wù)和特性癌椿。我們不能停止地球的轉(zhuǎn)動(dòng),不能在一瞬間轉(zhuǎn)換所有的東西菱阵。寫下這些東西的時(shí)候踢俄,我們已經(jīng)將19個(gè)服務(wù)移出Profile了。
  • 我們應(yīng)當(dāng)只對(duì)調(diào)用端做小的修改晴及,在調(diào)用端都办,Profile被用于在問題中獲取服務(wù)。
  • 我們必須修復(fù)Profile移除這個(gè)問題虑稼。當(dāng)我們開始這項(xiàng)工作時(shí)琳钉,Profile外只有一小部分對(duì)象,處于拆分目的手動(dòng)整理它們是可接受的蛛倦。但現(xiàn)在我們有了75個(gè)組件歌懒,我們知道手動(dòng)拆分整理是不對(duì)的,正如這里所寫的溯壶。有著這么多組件的話及皂,我們不能再依賴手動(dòng)整理了。
  • 我們必須允許加入編譯新特性或者移除舊特性∏腋模現(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移除過程中依賴手動(dòng)整理的原因。)
  • 延伸目標(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(測(cè)試)profile洽蛀。

在這個(gè)世界里摹迷,Profile是所有活動(dòng)的中心。profile有用它所有的服務(wù)郊供,并向外界傳遞出去峡碉。Profile拆分遵循ProfileImpl中對(duì)服務(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()方法提供的對(duì)象,執(zhí)行與profile相關(guān)的大部分工作未斑。BrowserContextKeyedServiceFactory為你提供了一個(gè)重寫接口咕宿,讓你在響應(yīng)Profile生命周期事件時(shí),管理你的Service對(duì)象的生命周期颂碧,并在service依賴的service關(guān)閉前荠列,關(guān)閉它本身。

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

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

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

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

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

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

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

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

幾種工廠

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

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

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

Yes.

我們絕對(duì)應(yīng)該強(qiáng)調(diào)服務(wù)的獨(dú)立性沛申。正如它今天的樣子,在多profile模式不再有必要之后姐军,我們沒有馬上去掉profile铁材,因?yàn)樵谌サ魀rofile時(shí),我們的crash率太高了奕锌,不能為用戶所接受著觉。我們有75個(gè)組件插在profile的生命周期當(dāng)中,他們之間的依賴圖如此復(fù)雜以至于我們簡(jiǎn)單的手動(dòng)整理不能處理這種復(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來注冊(cè)以及注銷。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ǎn)化的代碼結(jié)構(gòu)中梅鹦,顯式聲明的依賴意味著這些服務(wù)唯一有效的創(chuàng)建順序是[Alpha, Beta, Gamma],唯一有效的銷毀順序是[Gamma, Beta, Alpha]。上面的這些是你评腺,也就是這個(gè)框架的使用者帘瞭,所必須指定的依賴淑掌。

在幕后蒿讥,ProfileDependencyManager管理所聲明的依賴的關(guān)系,展示了一個(gè)Kahn的拓?fù)渑判蚺淄螅⒃贑reateProfileServices()和DestroyProfileServices()中得到應(yīng)用芋绸。

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

  1. 讓你已有的FooService繼承BrowserContextKeyedService。
  2. 可能的話担敌,不要再讓你的FooService得到引用計(jì)數(shù)了摔敛。大多數(shù)與Profile相關(guān)的被引用計(jì)數(shù)的對(duì)象似乎因?yàn)樗麄儧]有使用base::bind/WeakPtrFactory,而需要在多線程使用自己的數(shù)據(jù)全封。(在這個(gè)例子里马昙,線程安全的引用計(jì)數(shù)是有必要的桃犬,比如,多線程訪問時(shí)行楞,讓你的工廠繼承自RefcountedBrowserContextKeyedServiceFactory攒暇,這樣一切都能正常工作。)
  3. 構(gòu)建一個(gè)簡(jiǎn)單繼承自BrowserContextKeyedServiceFactory的FooServiceFactory子房。消費(fèi)者請(qǐng)求FooService時(shí)形用,你的FooServiceFactory將會(huì)是主要的訪問點(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è)方法來移除對(duì)Profile或其他服務(wù)對(duì)象的弱引用。
  9. 刪除每個(gè)BrowserContextKeyedService李茫,運(yùn)行它的析構(gòu)器揭保。最小化的工作需要在這里完成。調(diào)用任何*ServiceFactory::GetForProfile()會(huì)在調(diào)試模式下觸發(fā)的一個(gè)斷言魄宏。
  10. 將每個(gè)"profile_->GetFooService()"實(shí)例改為"FooServiceFactory::GetForProfile(profile_)"秸侣。

如果你需要上面這些步驟的例子,可以看看這些補(bǔ)冻杌ァ:

  • r100516: 一個(gè)簡(jiǎn)單的例子味榛,添加了一個(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會(huì)將依賴信息寫到你的/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

這會(huì)給你一個(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í)赚窃,框架會(huì)觸發(fā)一個(gè)assert册招。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勒极,隨后出現(xiàn)的幾起案子跨细,更是在濱河造成了極大的恐慌,老刑警劉巖河质,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冀惭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掀鹅,警方通過查閱死者的電腦和手機(jī)散休,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乐尊,“玉大人戚丸,你說我怎么就攤上這事∪忧叮” “怎么了限府?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)痢缎。 經(jīng)常有香客問我胁勺,道長(zhǎng),這世上最難降的妖魔是什么独旷? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任署穗,我火速辦了婚禮,結(jié)果婚禮上嵌洼,老公的妹妹穿的比我還像新娘案疲。我一直安慰自己,他們只是感情好麻养,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布褐啡。 她就那樣靜靜地躺著,像睡著了一般鳖昌。 火紅的嫁衣襯著肌膚如雪备畦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天遗遵,我揣著相機(jī)與錄音萍恕,去河邊找鬼逸嘀。 笑死车要,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崭倘。 我是一名探鬼主播翼岁,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼类垫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了琅坡?” 一聲冷哼從身側(cè)響起悉患,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榆俺,沒想到半個(gè)月后售躁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴晋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年陪捷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺擅。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡市袖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出烁涌,到底是詐尸還是另有隱情苍碟,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布撮执,位于F島的核電站微峰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抒钱。R本人自食惡果不足惜县忌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望继效。 院中可真熱鬧症杏,春花似錦、人聲如沸瑞信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凡简。三九已至逼友,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秤涩,已是汗流浹背帜乞。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筐眷,地道東北人黎烈。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親照棋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子资溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容