转chromeUI4
PrefService
和许多其他的程序一样,chrome也包含一系列本地配置文件,这些文件保存程序需要在重启后还能够记忆的参数或者其他数据。Chromium的配置文件存放在路径【C:\Documents and Settings\Username\Local Settings\Application Data\Chromium\User Data\Default】。Chrome典型的配置文件格式如下:
{
… 省略 …
"profile": {
"exited_cleanly": true,
"id": "not-signed-in",
"name": "",
"nickname": ""
},
"session": {
"urls_to_restore_on_startup": [ ]
}
}
chrome本地配置文件通过一个核心的类【chrome\common\pref_service.cc】来实现,在这个类中,它调用解析器JSONStringValueSerializer【chrome\common\pref_service.cc】将PrefService 结构中的内容转换成一个可写入文本文件的字符串。最后通过WriteFile函数【base\file_util_win.cc】将字符串写入文本。需要注意的是这个函数并不是UI线程调用的,而是用后台线程来实现。具体参见下列代码:
【chrome\common\pref_service.cc】
DE>SaveLaterTask* task = new SaveLaterTask(pref_filename_, data);
if (thread != NULL) {
// We can use the background thread, it will take ownership of the task.
thread->message_loop()->PostTask(FROM_HERE, task);
} else {
// In unit test mode, we have no background thread, just execute.
task->Run();
delete task;
}DE>
为了方便管理PrefService,chrome提供了一个ProfileManager类【chrome\browser\profile_manager.h】和Profile 类【chrome\browser\profile.h】。这里不做分析,因为这几个类并不通用。
PrefService使用
为了新建一个在路径为”C:\Preferences”的配置文件,我们可以声明一个PrefService 实例:
DE>PrefService * pService = new PrefService(L”C:\Preferences”);DE>
当需要往该配置文件增加一个参数时,可以参考下列步骤
声明一个合适的类型,chrome提供了若干种类型,例如BooleanPrefMember、 IntegerPrefMember、 StringPrefMember等。当需要存取一个bool类型。可以声明一个BooleanPrefMember。具体的实现见【chrome\common\pref_member.cc】。
向prefService结构里注册一个pref变量,并提供默认值。每一个pref变量必须有一个名字,该名字对应于该变量在配置文件中的“路径”。pref名字可以直接输入。但是保险的做法是将所有pref的名字存放在一个统一的地方以方便管理。chrome的所有prefName存放在文件【chrome\common\pref_names.h】中。perfName通过英语句号分割,每一节表示一层。例如perfName【L"general.luaguage.language"】在实际的配置文件的表现如下。
{
"general": {
"luaguage": {
"language": "zh-CN"
}
}
}
初始化这个类型。初始化时pref类型需要提供三个参数,该变量的名字,prefService的指针和一个回调。一般情况下,变量的名字和第二步注册的名字相同,prefService指针也和注册该pref变量使用的prefService一致。这一步的目的就是将一个本地变量和某一个全局的prefService中的某一个变量关联起来。既然他们是两个不同的变量(而不是两个指针指向同一个变量),那么就存在同步的问题。当prefService中的变量被其他线程偷偷的修改了怎么办呢,后面再分析。
对这个类型进行读写操作。例如成员函数GetValue()和SetValue(value)。这两个函数对bool,int,string等类型都适用,底层使用很容易想到用模版类,如果研究过stl源码,这个其实是很容易理解。
一般情况下,prefService总是作为一个全局变量存储在内存中,它在程序初始化时从磁盘读入数据,在程序退出时,将数据写入磁盘。很明显。如果程序中途崩溃或者断电等因素。这之前作出的配置改动都会丢失。貌似chrome有一个周期性将这些数据写入磁盘的机制,具体没去研究。
当一个新的程序安装后,磁盘没有任何配置参数,但是每一个pref变量在注册到prefService时都指定了一个默认值,新程序即使用这个值来初始化程序。也许有人会注意到,程序运行一次以后磁盘还是没有写入任何东西,是不是没有在退出程序时写入磁盘啊?chrome对每一个pref变量会判断是否该修改,如果未修改,它实际上什么都不做。很显然,前面的情况是因为用户或者程序根本没有修改任何一个pref参数。
Pref同步
将配置参数封装成PrefMember等类的一个重要原因是支持实时同步。该类实际上使用
了一个”OBSERVER”<观察者>设计模式。
在PrefMember初始化时,它会将自己注册至赋值给自己的 PrefService对象,以便在信
息发生变化时能够尽快得到通知。而外部应用同样可以在初始化pref变量注册一个回调,以便在该pref变量被其他人修改时及时得到通知。
PrefService内部实现
PrefService将所有从对应配置文件读取的信息存储在内部的一个【PreferenceSet prefs_】【typedef std::set<Preference*, PreferencePathComparator> PreferenceSet;】中, Preference则是封装了每一个pref变量的Name和value等信息:
Preference类中的部分成员【chrome\common\pref_service.h】
DE>Value::ValueType type_;
std::wstring name_;
scoped_ptr
Value这个类值得研究一下,它可用于表示所有类型,不过底层实现并不是模版类,而是继承和多态来实现。Value只不过是众多具体实现类的一个基类。
chrome这一套pref机制具有很好的扩展性。当新的需要导致一个新的pref变量时,除了需要在【chrome\common\pref_names.h】文件中增加一个pref名字以外,其他的处理都可以在“当地”进行。
PathService
前面提到,chrome包含若干配置文件。这些文件的路径怎么指定,直接在程序输入地址么,这并不是一个好的办法。chrome提供了一个PathService类【src\base\path_service.cc】。通过这个类的Get函数,其他模块可以很方便地获取一个Path(路径)。
在PathService中,每一个Path都有一个唯一的PathID(int类型)。
在pathService中,所有的path都存储在一个由结构 Provider构成的链表中,每一个Provider中包含一个ProviderFunc函数指针。当用户通过PathService的Get函数查询一个path时,PathService以此查询每一个Provider,而Provider由通过函数func来获得实际的path。
DE>struct Provider {
PathService::ProviderFunc func;
struct Provider* next;
...
bool is_static;
};DE>
为了获得更好的性能,chrome在PathService中内置了path cache。下面的 PathData结构存储了PathService的所有数据。 providers是Provider的首节点。 cache即上面提到的缓存.
DE>struct PathData {
Lock lock;
PathMap cache; // Track mappings from path key to path value.
PathSet overrides; // Track whether a path has been overridden.
Provider* providers; DE>
Chrome默认提供三个Provider。
base_provider :【base\path_service.cc】
base_provider_win :【base\path_service.cc】
PathProvider :【chrome\common\chrome_paths.cc】
用户可以通过RegisterProvider函数【base\path_service.cc】注册自己的Provider。 PathProvider的注册代码如下:
DE>void RegisterPathProvider() {
PathService::RegisterProvider(PathProvider, PATH_START, PATH_END);
}DE>
自定义Provider
如果用户需要注册自定义Provider,只需实现一个函数【 typedef bool (ProviderFunc)(int, FilePath)】。这个函数收到一个PathID。如果该ID在本函数内合法,则返回true,并且将Path通过第二个参数传回来。否则返回false。具体实现参考PathProvider函数【chrome\common\chrome_paths.cc】。
然后通过 PathService::RegisterProvider函数注册自己即可。