前言
本系列文章尝试着从新的角度,逐步分析Chromium的部分关键知识点,希望能分析清楚初学者常常比较困惑的问题,比如Chromium怎么启动,各个进程(Browser/Render/GPU等)怎么启动,Chromium怎么显示一个网页等,进而理顺Chromium中各种类的关系和作用。
ContentShell
鉴于Chromium本身的复杂和庞大,从ContentShell来分析是一个比较好的选择。关于Content模块和Chromium的关系,可以参考这里。
ContentShell App的启动是从ContentShellActivity.java
的onCreate(...)
开始,这里首先会初始化CommandLine,利用CommandLine可以设置各种启动参数,比如singleprocess,GPU hardware acceleration等,这里详细列出了Chrome中支持的command lines。但这部分和本文主题无关,暂且略过。
接着会调用LibraryLoader.java
的静态函数get(...)
来实例化LibraryLoader
,并指定LibraryProcessType
为PROCESS_BROWSER
。从名字可以看出来,LibraryLoader
的目的就是加载和注册native的libraries。这是通过ensureInitialized(...)
来调用loadAlreadyLocked(...)
和initializeAlreadyLocked(...)
来完成的。在loadAlreadyLocked(...)
中,通过Linker.loadLibrary(...)
或者System.loadLibrary(...)
来触发native层的JNI_OnLoad(...)
。ContentShell对应的native文件是shell_library_loader.cc
,可以看到在JNI_OnLoad(...)
中主要做了两件事情:
- Compositor的初始化。
- 创建并设置
ContentMainDelegate
,这里是ShellMainDelegate
.
Compositor的初始化是在content/browser/renderer_host/compositor_impl_android.cc
的Initialize()
完成的,但其实只是设置了变量g_initialized
(意义是什么?)。而ShellMainDelegate
是比较核心的class,后面的分析会多次提到。
现在回到ContentShellActivity.java
,会看到接下来的重点在于BrowserStartupController
。先是通过其静态函数get(...)
来实例化一个class,并设置其LibraryProcessType
为PROCESS_BROWSER
。这和前面的LibraryLoader
一样,都是用了设计模式的单例模式。然后调用其startBrowserProcessesAsync(...)
异步启动和初始化Content模块。
Tips :
startBrowserProcessesAsync(…)和startBrowserProcessesSync(…)的区别:调用startBrowserProcessesAsync(…)会同时传入参数BrowserStartupController.StartupCallback,使得当Content模块初始化完成后,会调用ContentShellActivity类的成员函数finishInitialization(…)继续执行启动ContentShell APK的其他工作。startBrowserProcessesAsync(…)中会调用prepareToStartBrowserProcess(…),同时new Runnable()作为参数传入,目的是在随后执行contentStart()。
这里需要注意,虽然实现Runnable接口是java中两种实现多线程的方式之一(另一种是继承Thread类),但是Runnable本身和线程无关,只是一个拥有run()方法的Object。所以在prepareToStartBrowserProcess(...)
中,会在资源提取(ResourceExtractor)完成后执行contentStart()
。这里使用的类ResourceExtractor
其实是和浏览器对HTML5多语言的支持有关,暂且略过。
Native层
contentStart()
最终是通过JNI调用native层content/app/android/content_main.cc
中的Start(...)
。而之前设置ShellMainDelegate
正是用到了content_main.cc
中的SetContentMainDelegate(...)
。
在Start(...)
中,主要做了下面几件事:
使用前面创建和设置的
ShellMainDelegate
初始化g_content_main_delegate
,并初始化结构体ContentMainParams params
的成员变量delegate
。这里会用到Chromium的LazyInstance,它提供了一种延迟创建全局静态对象的方式,而且是完全的线程安全,
base/lazy_instance.h
对其有详细描述和示例。创建并初始化
Class ContentMainRunner(content/app/content_main_runner.cc)
的实例g_content_runner
,初始化函数Initialize(...)
参数正是前面创建的params
。运行
g_content_runner(ContentMainRunnerImpl)
。
在上面的过程中,Class ContentMainRunnerImpl
的Initialize(...)
里面有很多重要的步骤:
delegate_->BasicStartupComplete(…) 。
这里的delegate_就是
g_content_main_delegate
。BasicStartupComplete(…)
中会实例化ShellContentClient
,并通过Class ContentClient
的SetContentClient(…)
设置全局变量g_client(ShellContentClient)
。ContentClientInitializer::Set(process_type, delegate_)。
Class ContentClientInitializer
位于content_main_runner.cc
中,其Set(…)
函数分别调用g_content_main_delegate
的CreateContentBrowserClient()
,CreateContentGpuClient()
,CreateContentRendererClient()
和CreateContentUtilityClient()
,并分别初始化content_client
(其实就是前面的全局变量g_client
)的变量browser_(ShellContentBrowserClient)
,gpu_(ContentGpuClient)
,renderer_(ShellContentRendererClient)
和utility_(ShellContentUtilityClient)
。注意这里的前提条件
!define(CHROME_MULTIPLE_DLL_CHILD)
和!define(CHROME_MULTIPLE_DLL_BROWSER)
,这两个条件基于是否定义is_multi_dll_chrome
,而从build/config/chrome_build.gni
可以看到只有Windows平台上,才会定义。所以前提条件是满足的。运行
g_content_runner
执行的是ContentMainRunnerImpl
的函数Run()
,它会调用RunNamedProcessTypeMain(…)
来根据参数process_type
启动不同的进程,默认是空,启动的正式Browser进程。在进程启动之前,会先调用
ShellMainDelegate::RunProcess(…)
,它会创建BrowserMainRunner
,并作为参数进一步调用shell_browser_main.cc
的ShellBrowserMain(…)
,进而调用BrowserMainRunner
的Initialize(…)
。在这个
Initialize(…)
函数中,又会创建BrowserMainLoop
的实例,并对其依次进行如下调用:
其中,Init()
执行parts_.reset(GetContentClient()->browser()->CreateBrowserMainParts(parameters_))
,GetContentClient()
获得的就是前面提到的g_client
,所以GetContentClient()->browser()
得到的就是browser_(ShellContentBrowserClient)
,调用其CreateBrowserMainParts(…)
创建ShellBrowserMainParts
实例。
其他的函数调用,其实都是调用ShellBrowserMainParts
的函数,且都与GPU进程的启动相关,所以我们在后续文章继续分析。
回到ContentMainRunnerImpl
的函数RunNamedProcessTypeMain(...)
,最后会调用browser_main.cc
的BrowserMain()
,进而调用BrowserMainRunnerImpl
的Run()
等,启动Browser进程。
综上,我们可以画出UML图便于理解,如下: