[关闭]
@rogeryi 2015-01-05T10:41:52.000000Z 字数 9040 阅读 9528

Threading of Chromium Android WebView

Android WebView Threading

作者: 易旭昕 (@roger2yi)
说明: 访问 Cmd Markdown 版本可以获得最佳阅读体验

Chromium Android WebView 研究系列文章

  1. Debugging of Chromium Android WebView
  2. Threading of Chromium Android WebView
  3. Rendering of Chromium Android WebView
  4. WebView of Chromium Android WebView
  5. Event Handling of Chromium Android WebView
  6. Resources of Chromium Android WebView

本文参考了 Chromium 的这篇设计文档和 Android 4.4.3 的源码写成,主要描述了 Chromium 的线程机制,和 Chromium Android WebView 的线程架构,包括其中一些预定用途线程的作用。

首先要说明的是,Chromium Android WebView 和独立应用的 Chrome for Android,按照 Google 自己的定义,是两个不同的 Platform Configuration,它们虽然有很多共同之处,但是两者的进程/线程架构并不完全一样,实际上 Chromium Android WebView 跟 Chromium 其它的 Platform Configuration 差异比较大,除了进程/线程架构,其它方面比如渲染架构也有许多不同之处。因为本文描述的主要对象是 Chromium Android WebView(下文简称CAW),所以文中的内容在 Chrome for Android 上可能并不一样。比如,Chromium 从发布之初就一直使用多进程的架构,主进程就是所谓的 Browser 进程,而每打开一个网页,Chromium 就会再创建一个子进程 Render 进程负责该网页的加载,解析,排版,绘制,JavaScript 运行等任务,两个进程通过各自的 IO 线程进行 IPC 通讯,但是在 CAW 上,Chromium 使用的是单进程架构,Render 线程是在 Browser 进程中创建的。

常见的进程/线程名称

进程/线程 说明
Browser Process Browser 进程,也就是程序启动的主进程
Render Process Render 进程,Browser 进程在打开一个新网页时启动的子进程
GPU Process GPU 进程,允许访问 GPU 的进程
UI Thread Browser 进程的主线程
IO Thread IO 线程,用于 Browser 进程和其它子进程之间的 IPC 通讯,Browser 进程和每个子进程都会创建一个 IO 线程
Render Thread Render 线程,由 Render 进程创建,负责网页的加载,解析,排版,绘制,JavaScript 运行等任务,有时也称为 Main Thread,或者 WebKit/Blink Thread
Compositor Thread 合成线程,由 Render 进程创建,负责 Layer 树相关的操作,有时也称为 Impl Thread
Rasterize Thread 光栅化线程,由合成线程创建,负责 Layer Content 的光栅化
GPU Thread GPU 线程,负责最终的 GL 调用,完成合成,将网页内容绘制到窗口缓存上

Chromium 的线程机制

Chromium 有自己的一套线程,消息循环,线程通讯的机制。不同于早期版本的 Android WebView,在 CAW 上,Chromium 仍然使用了自己的这一套线程机制而不是使用 Android 本身的线程机制,它仅仅是做了一些必要的适配。

Chromium 的线程类 base::Thread 的定义 :

  1. class BASE_EXPORT Thread : PlatformThread::Delegate {
  2. public:
  3. ...
  4. MessageLoop* message_loop() const { return message_loop_; }
  5. }

每个线程都带有一个 MessageLoop,它类似于 Android 的 MessageQueue,Looper,Handler 的组合。

  1. class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
  2. public:
  3. ...
  4. void PostTask(const tracked_objects::Location& from_here,
  5. const Closure& task);
  6. }

MessageLoop 有几个 PostXXX 的方法,我们可以通过这些方法把一个任务放入 MessageLoop 里面,等待该 MessageLoop 所属的线程去执行,任务的类型是 Closure,它实际上是模板类 Callback 的一个类型定义。

  1. // Syntactic sugar to make Callbacks easier to declare since it
  2. // will be used in a lot of APIs with delayed execution.
  3. typedef Callback Closure;

Closure 定义了一个可供回调的函数对象,但是可以通过 Chromium 提供的 Bind 机制,把回调的函数,和该函数作用的对象(如果该函数是对象的一个成员方法),还有函数调用所需要的其它参数对象,通通绑定在一起构成一个闭包(简单说就是函数和它调用所需的上下文环境)。Closure 有些类似 Java 的 Runnable,而 PostXXX 方法则类似于 Android Handler 的 post(Runnable) 方法。所以 Chromium 的线程通讯就是线程 A 往线程 B 的 MessageLoop Post 一个任务,让这个任务在线程 B 里面执行,另外,Chromium 还有一个 PostTaskAndReply 的方法,可以在目标线程 B 执行完 Task 后,再在原线程 A 执行 Reply,让原线程获得任务结束的通知。

程序可以通过 MessageLoop 的 Proxy 对象 MessageLoopProxy 来间接持有一个 MessageLoop,这样更安全,不需要担心 MessageLoop 是否已经随着它所属线程的终止而被销毁。另外更重要的是 MessageLoopProxy 和线程池类型 SequencedWorkerPool 都实现了 TaskRunner 这个接口,对象可以通过 TaskRunner 来 Post 一个任务,这样的实现更灵活,调用者不需要区分自己的任务到底是某个特定的线程还是由一个线程池里面的任意线程来完成

Browser Threads

Browser 进程在内核初始化时(BrowserMainLoop::CreateStartupTasks),会创建一组预定用途的线程,它们被称为 Browser Thread,包括了 :

  1. // The main thread in the browser.
  2. UI,
  3. // This is the thread that interacts with the database.
  4. DB,
  5. // This is the thread that interacts with the file system.
  6. FILE,
  7. // Used for file system operations that block user interactions.
  8. // Responsiveness of this thread affect users.
  9. FILE_USER_BLOCKING,
  10. // Used to launch and terminate Chrome processes.
  11. PROCESS_LAUNCHER,
  12. // This is the thread to handle slow HTTP cache operations.
  13. CACHE,
  14. // This is the thread that processes IPC and network messages.
  15. IO,

当然其中的 UI 线程并不是 CAW 自己创建的,而是 Android 在启动 Activity 时创建的主线程,也是 Android 本身的 UI 线程。

CAW 会提高 UI 线程和 IO 线程的优先级到 Display 优先级(实际的值是-6,优先级比 android.os.THREAD_PRIORITY_DISPLAY = -4 的定义还要高),而其它线程维持默认的 Normal = 0 优先级。

  1. int BrowserMainLoop::BrowserThreadsStarted() {
  2. TRACE_EVENT0("startup", "BrowserMainLoop::BrowserThreadsStarted");
  3. ...
  4. #if defined(OS_ANDROID)
  5. // Up the priority of anything that touches with display tasks
  6. // (this thread is UI thread, and io_thread_ is for IPCs).
  7. io_thread_->SetPriority(base::kThreadPriority_Display);
  8. base::PlatformThread::SetThreadPriority(
  9. base::PlatformThread::CurrentHandle(),
  10. base::kThreadPriority_Display);
  11. #endif
  12. }

CAW 会保证上述线程生命周期的顺序性,越靠前的线程生命周期越长,简单说就是创建的顺序从 UI -> IO,销毁的顺序从 IO -> UI。如果 Browser 进程的某个线程希望往某个 BrowserThread(比如 IO)发送一个任务,它可以通过 BrowserThread 提供的静态方法 PostTask,PostDelayedTask 或者 PostTaskAndReply 。

  1. BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task);

除了上述预定义线程外,BrowserThread 另外还创建一个线程池用于执行阻塞 I/O 操作(Blocking Pool,里面有3个线程),它提供下面的 API 供调用 :

  1. static bool PostBlockingPoolTask(const tracked_objects::Location& from_here,
  2. const base::Closure& task);
  3. static bool PostBlockingPoolTaskAndReply(
  4. const tracked_objects::Location& from_here,
  5. const base::Closure& task,
  6. const base::Closure& reply);
  7. static bool PostBlockingPoolSequencedTask(
  8. const std::string& sequence_token_name,
  9. const tracked_objects::Location& from_here,
  10. const base::Closure& task);

在 BrowserMainLoop 完成所有启动时的初始化工作后,CAW 会禁止 UI 线程访问磁盘 I/O 和进入等待状态 (Thread Condition Wait),也就是说 UI 线程是不允许显式等待别的线程。

  1. int BrowserMainLoop::PreMainMessageLoopRun() {
  2. ...
  3. // If the UI thread blocks, the whole UI is unresponsive.
  4. // Do not allow disk IO from the UI thread.
  5. base::ThreadRestrictions::SetIOAllowed(false);
  6. base::ThreadRestrictions::DisallowWaiting();
  7. return result_code_;
  8. }

接下来会说明各个 Browser Thread 的作用。

1. UI 线程

UI 线程实际上是 Android 在 Activity 启动时创建的,它是一个 Java 线程,并且有 Android 自己的消息循环(MessageQueue,Looper),对于 CAW 来说,除了 UI 线程,其它线程都是自己创建的 Native 线程,这些 Native 线程是无法使用 Android 的消息机制进行通讯的,只能通过 Chromium 本身的消息通讯机制,CAW 还会为 UI 线程创建一个 MessageLoop,让 UI 线程和其它线程之间也可以使用 Chromium 的消息机制进行通讯。

UI 线程同时存在 Android 的消息循环(Java)和 Chromium 的消息循环(Native),而 Chromium 的消息循环是由 Android 的消息循环去驱动的。CAW 会注册一个 SystemMessageHandler 到 Android 的消息循环里面,然后通过方法 nativeDoRunLoopOnce 去调度 Chromium 的 Native 消息循环。

  1. class SystemMessageHandler extends Handler {
  2. // Native class pointer set by the constructor of the SharedClient native class.
  3. private long mMessagePumpDelegateNative = 0;
  4. private void setTimer() {
  5. sendEmptyMessage(TIMER_MESSAGE);
  6. }
  7. @Override
  8. public void handleMessage(Message msg) {
  9. nativeDoRunLoopOnce(mMessagePumpDelegateNative);
  10. }
  11. private native void nativeDoRunLoopOnce(long messagePumpDelegateNative);
  12. }

Native 部分的代码在 MessagePumpForUI (message_pump_android.h/cpp),当 UI 线程的 Native 消息循环接收到消息时,MessagePumpForUI 就会向 SystemMessageHandler 发起一个调度请求。

  1. void MessagePumpForUI::ScheduleWork() {
  2. DCHECK(!system_message_handler_obj_.is_null());
  3. JNIEnv* env = base::android::AttachCurrentThread();
  4. DCHECK(env);
  5. Java_SystemMessageHandler_setTimer(env,
  6. system_message_handler_obj_.obj());
  7. }

另外,在 CAW 里面,UI 线程同时也是 Compositor 线程,它跟 Render 线程之间通过 ThreadProxy 进行通讯,具体的细节我们在 Render 线程的部分再来描述。

2. DB 线程

DB 线程主要用于对 SQLite 数据库进行读写,在 CAW 里面会使用到这些线程的模块包括,Top 站点的纪录,Most Visited 最常访问站点的纪录,登录用户名/密码的纪录,表单数据的纪录,Cookie 的纪录等等,基本上是浏览器本身需要的数据库操作都会放到这个线程里面进行。(用于支持 WebApp 的 IndexedDB JS API,应该是由另外一个单独的 indexed db thread 来负责)

例如下面的 Top 站点纪录操作的代码:

  1. void TopSitesBackend::UpdateTopSites(const TopSitesDelta& delta) {
  2. BrowserThread::PostTask(
  3. BrowserThread::DB, FROM_HERE,
  4. base::Bind(&TopSitesBackend::UpdateTopSitesOnDBThread, this, delta));
  5. }
  6. void TopSitesBackend::UpdateTopSitesOnDBThread(const TopSitesDelta& delta) {
  7. ...
  8. }

另外,目前历史纪录还是使用自己的独立线程进行文件读写 (位于 HistoryService),但是未来有可能会合并到 DB 线程

3. FILE 线程

顾名思义,这个线程是负责文件读写的线程,比较典型的用途是下载,和一些配置文件的读取。

4. FILE_USER_BLOCKING 线程

这个线程看说明跟 FILE 线程的区别应该是后者是用于 UI 线程需要阻塞式地读取一个外部文件,需要尽快地响应,看到有用到的地方有 AppCache (appcache_storage_impl.cc),跟 Blocking Thread Pool 两者之间的差别和分工目前还不太清楚。

5. PROCESS_LAUNCHER 线程

用于启动子进程,在 CAW 里面应该没有作用。

6. CACHE 线程

负责资源磁盘缓存的读写,但是另外还有一个在 SimpleBackendImpl 的线程池(SimpleCacheWorker),两者之间的差别和分工目前还不太清楚。

7. IO 线程

IO 线程的名字容易误导,它实际上跟磁盘 I/O 无关,而是用于 Browser 进程跟其它子进程之间的 IPC 通讯,还有网络资源加载的调度,具体的细节我们在 Render 线程的部分再来描述。

Render Thread

Render 线程又称为 WebKit/Blink 线程,也就是我们常说的内核线程,它是在网页开始加载的时候创建的,跟其它 Platform Configuration 不同,CAW 不会真正创建 Render 子进程,Browser 进程同时也扮演了 Render 进程的角色,Render 线程是在 Browser 进程里面创建的,并且它只会创建一个 Render 线程,所有打开的网页都共用同一个 Render 线程,这点跟早期版本的 Android WebView 是一样的

虽然 CAW 不会创建真正的 Render 子进程,但是它仍然会创建 RenderProcessHostImpl 对象。

  1. bool RenderProcessHostImpl::Init() {
  2. ...
  3. if (run_renderer_in_process()) {
  4. in_process_renderer_.reset(g_renderer_main_thread_factory(channel_id));
  5. base::Thread::Options options;
  6. in_process_renderer_->StartWithOptions(options);
  7. g_in_process_thread = in_process_renderer_->message_loop();
  8. } else {
  9. ...
  10. }

全局函数指针 g_renderer_main_thread_factory 实际上指向函数 CreateInProcessRendererThread,它创建了 InProcessRendererThread 对象,并启动 Render 线程,Render 线程的优先级是默认的 Normal 优先级。

  1. InProcessRendererThread::InProcessRendererThread(const std::string& channel_id)
  2. : Thread("Chrome_InProcRendererThread"), channel_id_(channel_id) {
  3. }
  4. void InProcessRendererThread::Init() {
  5. render_process_.reset(new RenderProcessImpl());
  6. new RenderThreadImpl(channel_id_);
  7. }
  8. base::Thread* CreateInProcessRendererThread(const std::string& channel_id) {
  9. return new InProcessRendererThread(channel_id);
  10. }

InProcessRendererThread 在 Render 线程中完成初始化,它创建了 RenderProcessImpl 和 RenderThreadImpl 对象。RenderProcessImpl 的创建还会导致一个 Child IO 线程的创建,它被用于跟 IO 线程进行 IPC 通讯,并且线程优先级也被提高到跟 IO 线程一样。

在多进程架构里面, Compositor 线程是 Render 子进程里面的一个独立线程,而在 CAW 里面, UI 线程本身就是 Compositor 线程,这点跟早期版本的 Android WebView 仍然是一样的,所以无论是多进程还是单进程架构,Compositor 线程跟 Render 线程都是在同一个进程里面。

RenderThreadImpl 初始化时会获得 Compositor 线程的 MessageLoopProxy,如上所述,在 CAW 里面它实际上就是 UI 线程的 MessageLoopProxy。而 Render 线程在初始化 Compositor 的时候会使用这个 MessageLoopProxy 创建一个 ThreadProxy 对象,这个 ThreadProxy 对象会在 Render 线程和 Compositor 线程之间共享,它被用于 Render 线程和 Compositor 线程之间的线程通讯。

如上图所示,如果 UI 线程扮演的是 Browser 进程 UI 线程的角色,它和 Render 线程之间的通讯,比如键盘,鼠标,触屏事件的发送和结果的返回,是通过 IO 线程和 Child IO 线程之间的 IPC 通讯进行的,即使它们实际上在同一个进程;但是如果 UI 线程扮演的是 Render 进程 Compositor 线程的角色,它和 Render 线程之间的通讯,比如 Compositor 线程通知 Render 线程更新 Layer 树,Render 线程向 Compositor 线程提交新的 Layer 树,使用的是 Chromium 的线程通讯机制。了解这一点,我们就不会对 UI 线程和 Render 线程之间的不同通讯方式产生困惑。

另外,在 UI 线程跟 IO 线程,Render 线程跟 Child IO 线程之间也是使用线程通讯机制。

参考索引

  1. Threading
  2. GPU Architecture Roadmap
  3. How Chromium Displays Web Pages
  4. Multi-process Architecture
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注