前言
此文章是我最近在看的【WebKit 技术内幕】一书的一些理解和做的笔记。
而【WebKit 技术内幕】是基于 WebKit 的 Chromium 项目的讲解。
书接上文 浏览器内核之WebKit 架构与模块
1. Webkit 资源加载机制
网络和资源加载是网页的加载和渲染过程中的第一步,加载的资源包括以下内容:
在资源类的前面加上 “Cached” 字样,是因为效率问题而引入的缓存机制,所有对资源的请求都会先获取缓存中的信息, 以决定是否向服务器提出资源请求。
2. 资源缓存
资源的缓存机制是提高资源使用效率的有效方法。
它的基本思想是建立一个资源的缓存池。
当 WebKit 需要请求资源的时候,先从资源池中查找是否存在相应的资源。如果有,WebKit 则取出以便使用;如果没有,WebKit 创建一个新的 CachedResource 子类的对象,并发送真正的请求给服务器,WebKit 收到资源后将其设置到该资源类的对象中去,以便于缓存后下次使用。这里缓存指的是内存缓存,而不同于后面在网络栈部份的磁盘缓存。
WebKit 从资源池中查找资源的关键字是 URL, 因为标记资源唯一的特征就是资源的 URL 。这也意味着,假如两个资源有不同的 URL ,但是它们的内容完全一样,也被认为是两个不同的资源。其实,上面是个简单的示意图,真实的过程比这里要复杂,这其中涉及到了资源的生命周期和失效机制。
3. 资源加载器
按照加载器的类型来分,WebKit 总共有三种类型的加载器。
由于从网络获取资源是一个非常耗时的过程,通常一些资源的加载是异步执行的,也就是说网络资源的获取和加载不会阻碍当前 WebKit 的渲染过程,例如图片、CSS 文件。
当然,网页也存在某些特别的资源会阻碍主线程的渲染过程,例如 Javascript 代码文件。这会严重影响 WebKit 下载资源的效率。因为主线程被阻碍了,后面的解析工作没有办法继续往下进行,所以对于 HTML 网页中后面使用的资源也没有办法知道并发送下载请求。
这时候,WebKit 会这样:当前的主线程被阻碍时,WebKit 会启动另外一个线程去遍历后面的 HTMl 网页,收集需要的资源 URL,然后发送请求,这样就可以避免被阻碍。与此同时,WebKit 能够并发下载这些资源,甚至并发下载 JavaScript 代码资源。这种机制对于网页的加载提速很是明显。
4. 资源的生命周期
资源池中的生命周期是什么呢?资源池不能无限大,必须要用相应的机制来替换其中的资源,从而加入新的资源。资源池使用的机制其实很简单,就是采用 LRU(Lease Recent Used 最近最少使用)算法。
另外一方面,当一个资源加载后,通常它会被放入资源池,以便之后使用。问题是,WebKit 如何判断下次使用的时候是否需要更新该资源从而对服务器重新请求?因为服务器可能在某段时间之后更新了该资源。
考虑这样的场景,当用户打开网页后,他想刷新当前的页面。这种情况下,资源池会出现怎样的情况呢?是清除所有的资源,重新获得?还是直接利用当前的资源?都不是。对于某些资源,WebKit 需要直接重新发送请求,要求服务器将内容重新发送过来。但对于很多资源,WebKit 则可以利用 HTTP 协议减少网络负载。在 HTTP 协议的规范中对此有规定,浏览器可以发送消息确认是否需要更新,如果有,浏览器则重新获取该资源;否则就需要利用该资源。
WebKit 的做法是,首先判断资源是否在资源池中,如果是,那么发送一个 HTTP 请求给服务器,说明该资源在本地的一些信息,例如该资源什么时间修改的,服务器则根据该信息作判断,如果没有更新,服务器则发送回状态码 304 ,表明无需更新,那么直接利用资源池中原来的资源;否则。WebKit 申请下载最新的资源内容。
5. Chromium 多进程资源加载
资源的实际加载在各个 WebKit 移植中有不同的实现。Chromium 采用的是多进程的资源加载机制。
图4-11 描述了关于 Chromium 如何利用多进程架构来完成资源的加载,主要是多个 Render 进程和 Browser 进程之间的调用栈涉及的主要类。
Render 进程在网页的加载过程中需要获取资源,但是由于安全性(实际上,当沙箱模型打开的时候,Render 进程是没有权限去获取资源的)和效率上(资源共享等问题)的考虑,Render 进程的资源获取实际上是通过进程间通信将任务交给 Browser 进程来完成,Browser 进程有权限从网络或者本地获取资源。
在 Chromium 架构的 Renderer 进程中,ResourceHandleInternal 类通过 IPCResource-LoaderBridge 类同 Browser 进程通信。IPCResourceLoaderBridge 类继承自 ResourceLoaderBridge 类,其作用是负责发起请求的对象和回复结果的解释工作,实际消息的接收和派发交给 ResourceDispatcher 类来处理。
资源统一交由 Browser 进程来处理,这使得资源在不同网页间的共享变得很容易。因为每个 Renderer 进程某段时间内可能有多个请求,同时还有多个 Renderer 进程,Browser 进程需要处理大量的资源请求,这就需要一个处理这些请求的调度器,这就是 Chromium 中的 ResourceScheduler。
6. 网络栈
6.1 WebKit 的网络栈
上图4-13 是 “ent” 所包括的主要子目录,也是 Chromium 网络栈的主要模块。这里面除了一些基础的部分,例如 HTTP 协议。NDS 解析等模块,还包含了 Chromium 为了减少网络时间 而引入的新技术,例如 SPDY 、QUIC 等
图4-14 描述了从 URLRequest 类到 Socket 类之间的调用过程。以 HTTP 协议为例,图中列出了建立 TCP 的 socket 连接过程中涉及的类。
首先是 URLRequest 类被上层调用并启动请求的时候,它会根据 URL 的 “scheme” 来决定需要创建什么类型的请求。“scheme” 也就是 URL 的协议类型,例如 “http://”、“file://” ,也可以是自定义的 scheme ,例如 Android 系统的 “file://android_asset/”。 URLRequest 对创建的是一个 URLRequestJob 子类的一个对象,例如图中的 URLRequestJob 类。为了支持自定义的 scheme 处理方式, Chromium 使用工厂模式。
URLRequestJob 类和它的工厂类 URLRequestJobFactory 的管理工作都由 URLRequestJobManager 类负责。基本思路是,用户可以在该类中注册多个工厂,当有 URLRquest 请求时,先由工厂检查它是否需要处理该 “scheme” ,如果没有,工厂管理类继续交给下一个工厂类来处理。最后,如果没有任何工厂能够处理,Chromium 则交给内置的工厂来检查和处理是否为 “http://”、“ftp://”、或者 “file://” 等。
图 4-15 就是描述这些类的关系。