一、概述
之前有介绍过chromium的界面层的相关知识,这篇文章则是重点介绍chromium中书签这一个模块,不仅有界面层的知识,还会较多的介绍逻辑层的一些内容。接下来会详细介绍chromium中书签功能的详细实现。主要问题有:
1、书签数据在硬盘和内存中存储结构;
2、初始化过程;
3、相关操作的实现步骤;
4、线程模型;
5、书签同步。
二、书签结构综述
Bookmark界面层在..\src\chrome\browser\ui\views\bookmarks中实现。Bookmark逻辑层在..\src\chrome\browser\bookmarks 中实现,包括读写书签文件,书签导入导出等。
书签在整个工程中是chrome特色功能,它包括三种类型,书签栏、其他、和“mobile”。书签栏管理用户创建的所有书签
作为chromium拓展中心的一个功能,整体流程图如图:
图1 书签整体流程图
在书签模块中,其中主要的两个模块是底层数据处理和界面显示,具体实现分别在BookmarkModel和BookmarkBarView中,它们之间采用的是observer设计模式,BookmarkModel(具体目标),BookmarkBarView(具体观察者)。
三、书签存储结构
书签的存储结构需要从配置文件中结构及内存中存储结构两方面分析。
书签配置文件中存储在本地Bookmarks中,以JSON的格式存储。并在书签模块启动的时候load文件中的书签数据。本地JSON书签数据如下:
"children": [ {
"date_added": "12994954482846617",
"id": "7",
"name": "Google",
"type": "url",
"url": "http://www.google.com.hk/"
},
在内存中每个节点对应一个BookmarkNode。其中包含了favicon, id and type等节点信息。
在BookmarkModel中定义了如下几个节点:
BookmarkNode root_;
BookmarkPermanentNode* bookmark_bar_node_;
BookmarkPermanentNode* other_node_;
BookmarkPermanentNode* mobile_node_;
root_作为根节点,是书签栏中节点和“其他”文件夹中节点的父节点。BookmarkPermanentNode只是BookmarkNode一个简单的封装,用来管理书签栏及“其他”等这种固定的文件夹。
四、书签初始化及释放
书签功能的初始化:可以分为两个部分,数据逻辑层和界面层。从上面的主流程图中我们可以看出,数据逻辑层先于界面层初始化,具体初始化流程如下:
数据逻辑层:
初始化流程,拓展中心统一初始化,在ExtensionService::InitEventRouters()中调用
bookmark_event_router_.reset(new BookmarkExtensionEventRouter(
BookmarkModelFactory::GetForProfile(profile_)));
bookmark_event_router_->Init();
最终在BookmarkModelFactory完成BookmarkModel的创建。另外,BookmarkExtensionEventRouter继承自BookmarkModelObserver,是BookmarkModel的一个具体观察者。会将bookmark中的修改通知装换成event传给extension system
逻辑层初始化执行顺序如下图:
图2 书签数据逻辑层执行
为防止阻塞UI线程,书签文件的加载是在一个后台进程中完成的,chromium启动的时候,主线程Post出一个文件加载的Task,同时传入了一个回调函数的指针参数,当消息中心处理这个Task时会调用之前传入的回调,并完成书签文件的加载接JSON数据的解析。注意这个步骤是在File加载线程中完成的。加载解析完成后,File加载线程会POST出一个Task,并将操作权交还给主线程,去完成后需初始化工作。
书签文件的解析是在JSONFileValueSerializer中完成的,在这个里面会将JSON文件反序列化。并将其内容存入BookmarkLoadDetails中,供接下来的调用。
界面层:
BookmarkBarView类是BookmarkModel的具体观察者,负责书签数据的渲染布局等,与书签界面层相关的操作主要在这里面实现。
BookmarkBarView由BrowserView统一管理,BrowserView不会主动创建BookmarkBarView,而是在之后执行需要书签的操作中,如UpdateUIForContents,若书签界面层尚未创建,则执行创建操作,否则跳过。
书签模块的释放:同样重点完成数据逻辑层和界面层的释放。释放的过程和创建相反。先delete 管理界面模块的BookmarkBarView,这部分功能由BrowserView统一管理释放。之后再进一步delete管理逻辑层的模块BookmarkModel,这部分释放工作在配置文件管理中心统一释放。
五、书签操作执行流程
书签界面层与逻辑层的类关系图如下。
图3 界面层、逻辑层类关系图
BookmarkModel继承自PofileDeyedService,因而可以实现在配置文件管理中心统一释放
BookmarkBarView继承自DetachableToolbarView,是一个与Chrome frame分离的view控件。
BookmarkModel与BookmarkBarView均继承自消息中心NotificationObserver,并会通过registrar_注册自己需要接受的通知。
BookmarkModel类中的实现。它是是observer模式中的具体目标及数据逻辑层的核心实现。BookmarkModel对象需要通过 BookmarkModelFactory才能创建,而不能直接创建。
BookmarkModel类管理者内存中的书签数据,也向外提供了很多的操作接口,如AddNode、RemoveAndDeleteNode、RemoveNode等。用户操作书签时,都会通过调用这些接口来修改书签内容。
在BookmarkModel中通过定义一个BookmarkStorage的对象来操作书签的磁盘读写,上面提到的文件加载就是通过这个对象来执行的。另外,当书签数据与磁盘同步也在 这个里面完成,当用户修改了书签内容时,会调用其中的ScheduleSave()函数写回磁盘。
书签磁盘读写的类图如下:
图4 书签磁盘读写类图
其中BookmarkLoadDetails是用来临时存储书签数据的对象。
BookmarkBarView类派生自BookmarkModelObserver,是observer模式中一个具体的观察者。中完成书签界面展示以及部分用户操作。
另外,书签栏内部的子文件夹显示也存在菜单栏的弹出,包括点击文件夹及拖动书签节点至文件夹都会弹出。BookmarkBarView中定义了两个菜单对书签栏的子菜单进行管理:
BookmarkMenuController* bookmark_menu_;
BookmarkMenuController* bookmark_drop_menu_;
其中bookmark_menu_用于管理用户点击BookmarkBarView中的文件夹时弹出的菜单(BookmarkBarView是MenuButtonListener的子类,可以收听到这个事件)。bookmark_drop_menu_是用户拖拽一个node到文件夹上时弹出的菜单。
图5 书签右键菜单实现类图
Chromium中书签最基本的操作有添加、删除、修改等。同时,用户操作书签的方式也有很多,如快捷键,主菜单,书签右键菜单等。书签操作的执行顺序基本如下。
图6 书签操作执行顺序
书签的显示实现
在chromium的代码中,每次调用网页时都会调用void Browser::UpdateBookmarkBarState(BookmarkBarStateChangeReason reason),用以判断是否需要显示书签,书签有三种状态,SHOW(显示与地址栏不分离)、DETACHED(显示与地址栏分离)、HIDDEN(不显示)。
void Browser::UpdateBookmarkBarState(BookmarkBarStateChangeReason reason) {
BookmarkBar::State state;
// The bookmark bar is hidden in fullscreen mode, unless on the new tab page.
if (browser_defaults::bookmarks_enabled &&
profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) &&
(!window_ || !window_->IsFullscreen())) {
state = BookmarkBar::SHOW;
} else {
WebContents* web_contents = chrome::GetActiveWebContents(this);
BookmarkTabHelper* bookmark_tab_helper =
web_contents ? BookmarkTabHelper::FromWebContents(web_contents) : NULL;
if (bookmark_tab_helper && bookmark_tab_helper->ShouldShowBookmarkBar())
state = BookmarkBar::DETACHED;
else
state = BookmarkBar::HIDDEN;
}
// Only allow the bookmark bar to be shown in default mode.
if (!search_model_->mode().is_default())
state = BookmarkBar::HIDDEN;
if (state == bookmark_bar_state_)
return;
bookmark_bar_state_ = state;
if (!window_)
return; // This is called from the constructor when window_ is NULL.
if (reason == BOOKMARK_BAR_STATE_CHANGE_TAB_SWITCH) {
// Don't notify BrowserWindow on a tab switch as at the time this is invoked
// BrowserWindow hasn't yet switched tabs. The BrowserWindow implementations
// end up querying state once they process the tab switch.
return;
}
BookmarkBar::AnimateChangeType animate_type =
(reason == BOOKMARK_BAR_STATE_CHANGE_PREF_CHANGE) ?
BookmarkBar::ANIMATE_STATE_CHANGE :
BookmarkBar::DONT_ANIMATE_STATE_CHANGE;
window_->BookmarkBarStateChanged(animate_type);
}
在这个函数中最终会调用window_->BookmarkBarStateChanged(animate_type),其中会调用到BrowserView的实现中,再由其中的bookmark_bar_view_来统一接手管理界面的工作。
书签栏中点击打开网页
当从书签栏中点击打开网页时,先会调用void BookmarkBarView::ButtonPressed函数,并在此函数中会调用page_navigator_->OpenURL(params);进一步调用WebContentsImple相应的方法来实现。
当前页直接添加书签
将当前页添加到书签则会调用void BookmarkCurrentPage(Browser* browser)(browser_commands.cc中的全局函数),再调用void BrowserView::ShowBookmarkBubble(const GURL& url, bool already_bookmarked)最终是在BookmarkBubbleView中完成。
六、书签线程模型
在整个书签模块中,除了文件读取是在后台File线程中完成外,其余操作均在主线程中执行,线程执行方法与整个chromium的线程模型保持一致。
为了保证书签文件在加载是不出现线程安全问题,chromium中设计了一个BookmarkStorage的中间层来读写书签文件,并在BookmarkStorage中定义了一个BookmarkLoadDetails,通过操作它来加载书签文件中的详细信息。加载完成后回调到主线程,并把所有权交还给BookmarkModel。但BookmarkModel中没有对BookmarkLoadDetails的引用,不能直接操作其中的内容,这样就确保解决了线程可能带来的问题。
七、登陆后书签同步
用户登陆成功后,数据同步中心会对相关数据进行同步,书签也包括在内。在Chromium中会由browser_sync同步中心统一管理用户同步相关操作。BookmarkModelAssociator负责实现bookmark model 和sync model之间的联系算法、为sync node 及bookmark node之间的相互获取提供方法。同步完成后最终会调用BookmarkModel的相关方法来实现书签的更新。
八、附件及参考资料
chromium Bookmarks:http://www.chromium.org/user-experience/bookmarks