Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Chromium插件(Plugin)实例(Instance)创建过程分析

$
0
0

      Chromium在解析网页时,每遇到一个<embed>标签,就会创建一个Plugin Instance。一般来说,Plugin Instance是在Plugin进程中创建和运行的。一个Plugin Module对应一个Plugin进程,同时可以创建多个不同的Plugin Instance。前面我们已经分析Plugin Module的加载过程了,本文继续分析Plugin Instance的创建过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

      Plugin Instance在运行的过程中,需要调用Chromium提供的API。这些API是实现在Render进程中的。与此同时,Render进程也需要主动与Plugin Instance通信,例如当网页的<embed>标签发生变化时,需要向它对应的Plugin Instance发出通知。为了方便Plugin Instance与Render进程相互通信,每一个Plugin Instance在Render进程中都对应有一个Proxy。这些Proxy通过PepperPluginInstanceImpl类描述。

      Plugin Instance的创建是由运行在Render进程中的WebKit发起的。WebKit又是通过请求Chromium的Content层创建Plugin Instance的。Content层在创建Plugin Instance之前,先会检查它对应的Plugin Module是否已经加载。如果还没有加载,那么就会先加载。这个加载过程可以参考前面Chromium插件(Plugin)模块(Module)加载过程分析一文。接下来,Content层又会创建一个Plugin Instance Proxy,即一个PepperPluginInstanceImpl对象,然后再通过个PepperPluginInstanceImpl对象请求在相应的Plugin进程中创建一个Plugin Instance。这个过程如图1所示:


图1 Plugin Instance的创建过程

       在PepperPluginInstanceImpl对象的创建这程中,会初始化一系列的接口。以后PepperPluginInstanceImpl对象就是通这些接口与运行在Plugin进程的Plugin Instance通信的。其中的一个接口在创建Plugin Instance的过程中也会用到。这个接口称为PPP_INSTANCE_INTERFACE_1_1,它提供了一个DidCreate函数。通过这个函数,PepperPluginInstanceImpl对象就可以请求在指定的Plugin进程中创建一个Plugin Instance。

       接口PPP_INSTANCE_INTERFACE_1_1提供的函数DidCreate会向Plugin进程发送一个类型为PpapiMsg_PPPInstance_DidCreate的IPC消息。这个IPC消息携带了一个参数API_ID_INSTANCE,表示该消息是分发给一个PPP_Instance_Proxy对象处理。这个PPP_Instance_Proxy对象又会在Plugin进程中获得一个PPP_INSTANCE_INTERFACE_1_1接口,并且调用该接品提供的函数DidCreate。后者在执行的过程中,就会创建一个pp::Instance对象。这个pp::Instance对象就是用来在Plugin对象中描述一个Plugin Instance的。

       从前面Chromium的GPU进程启动过程分析Chromium插件(Plugin)模块(Module)加载过程分析这两篇文章可以知道,WebKit请求Chromium的Content层为<embed>标签创建了一个PepperWebPluginImpl对象之后,就会调用这个PepperWebPluginImpl对象的成员函数initialize执行初始化工作。在初始化的过程中,就会创建Plugin Instance。因此,接下来我们就从PepperWebPluginImpl类的成员函数initialize开始,分析在Plugin Instance的创建过程.

       PepperWebPluginImpl类的成员函数initialize的实现如下所示:

bool PepperWebPluginImpl::initialize(WebPluginContainer* container) {
  // The plugin delegate may have gone away.
  instance_ = init_data_->module->CreateInstance(
      init_data_->render_frame, container, init_data_->url);
  ......

  bool success = instance_->Initialize(
      init_data_->arg_names, init_data_->arg_values, full_frame_);
  ......

  return true;
}

       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_webplugin_impl.cc中。

       PepperWebPluginImpl类的成员变量init_data_指向的是一个InitData对象。这个InitData对象的成员变量module指向的是一个PluginModule对象。这个PluginModule对象用来在Render进程中描述一个Plugin Module,它的创建过程可以参考前面Chromium的GPU进程启动过程分析一文。

       有了上述PluginModule对象之后,PepperWebPluginImpl类的成员函数initialize就可以调用它的成员函数CreateInstance创建一个Plugin Instance Proxy,即一个PepperPluginInstanceImpl对象,并且接下来会调用这个PepperPluginInstanceImpl对象的成员函数Initialize执行进行初始化工作。在初始化的过程中,就会请求Plugin进程创建一个Plugin Instance。

       接下来我们先分析Plugin Instance Proxy的创建这程,也就是PluginModule类的成员函数CreateInstance的实现,如下所示:

PepperPluginInstanceImpl* PluginModule::CreateInstance(
    RenderFrameImpl* render_frame,
    blink::WebPluginContainer* container,
    const GURL& plugin_url) {
  PepperPluginInstanceImpl* instance = PepperPluginInstanceImpl::Create(
      render_frame, this, container, plugin_url);
  ......
  return instance;
}
      这个函数定义在文件external/chromium_org/content/renderer/pepper/plugin_module.cc中。

      PluginModule类的成员函数CreateInstance通过调用PepperPluginInstanceImpl类的静态成员函数Create创建一个PepperPluginInstanceImpl对象 ,如下所示:

PepperPluginInstanceImpl* PepperPluginInstanceImpl::Create(
    RenderFrameImpl* render_frame,
    PluginModule* module,
    WebPluginContainer* container,
    const GURL& plugin_url) {
  base::Callback<const void*(const char*)> get_plugin_interface_func =
      base::Bind(&PluginModule::GetPluginInterface, module);
  PPP_Instance_Combined* ppp_instance_combined =
      PPP_Instance_Combined::Create(get_plugin_interface_func);
  if (!ppp_instance_combined)
    return NULL;
  return new PepperPluginInstanceImpl(
      render_frame, module, ppp_instance_combined, container, plugin_url);
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

       PepperPluginInstanceImpl类的静态成员函数Create首先是将PluginModule类的成员函数GetPluginInterface封装在一个Callback中,然后再将这个Callback传递给PPP_Instance_Combined类的静态成员函数Create,用来创建一个PPP_Instance_Combined对象。有了这个PPP_Instance_Combined对象之后,就可以创建一个PepperPluginInstanceImpl对象了。

      接下来,我们首先分析上述PPP_Instance_Combined对象的创建过程,也就是PPP_Instance_Combined类的静态成员函数Create的实现,如下所示:

PPP_Instance_Combined* PPP_Instance_Combined::Create(
    base::Callback<const void*(const char*)> get_interface_func) {
  // Try 1.1.
  const void* ppp_instance = get_interface_func.Run(PPP_INSTANCE_INTERFACE_1_1);
  if (ppp_instance) {
    const PPP_Instance_1_1* ppp_instance_1_1 =
        static_cast<const PPP_Instance_1_1*>(ppp_instance);
    return new PPP_Instance_Combined(*ppp_instance_1_1);
  }
  // Failing that, try 1.0.
  ppp_instance = get_interface_func.Run(PPP_INSTANCE_INTERFACE_1_0);
  if (ppp_instance) {
    const PPP_Instance_1_0* ppp_instance_1_0 =
        static_cast<const PPP_Instance_1_0*>(ppp_instance);
    return new PPP_Instance_Combined(*ppp_instance_1_0);
  }
  // No supported PPP_Instance version found.
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       PPP_Instance_Combined类的静态成员函数Create首先尝试通过参数get_interface_func描述的Callback获得一个1.1版本的PPP_INSTANCE_INTERFACE接口。如果能成功获得该接口,那么就使用它来创建一个PPP_Instance_Combined对象返回给调用者。

       如果不能成功获得1.1版本的PPP_INSTANCE_INTERFACE接口,那么PPP_Instance_Combined类的静态成员函数Create接下来会尝试获得1.0版本的PPP_INSTANCE_INTERFACE接口。如果能成功获得该接口,那么同样会使用它来创建一个PPP_Instance_Combined对象返回给调用者。

       我们假设1.1版本的PPP_INSTANCE_INTERFACE接口能够成功获取,接下来我们就分析它的获得过程。从前面的分析可以知道,参数get_interface_func描述的Callback描述的实际上是PluginModule类的成员函数GetPluginInterface,因此,1.1版本的PPP_INSTANCE_INTERFACE接口实际上是通过调用PluginModule类的成员函数GetPluginInterface获得的,如下所示:

const void* PluginModule::GetPluginInterface(const char* name) const {
  if (host_dispatcher_wrapper_)
    return host_dispatcher_wrapper_->GetProxiedInterface(name);

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/plugin_module.cc中。

       PluginModule类的成员变量host_dispatcher_wrapper_指向的是一个HostDispatcherWrapper对象。这个HostDispatcherWrapper对象是在Plugin Module加载的过程中创建的,它内部封装了一个HostDispatcher对象,通过这个HostDispatcher对象可以与Plugin Module所加载在的Plugin进程通信。

       有了上述HostDispatcherWrapper对象之后,PluginModule类的成员函数GetPluginInterface就可以调用它的成员函数GetProxiedInterface获得版本为PPP_INSTANCE_INTERFACE接口,即PPP_INSTANCE_INTERFACE_1_1接口,如下所示:

const void* HostDispatcherWrapper::GetProxiedInterface(const char* name) {
  return dispatcher_->GetProxiedInterface(name);
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/host_dispatcher_wrapper.cc中。

       HostDispatcherWrapper类的成员变量dispatcher_指向的就是一个HostDispacther对象。HostDispatcherWrapper类的成员函数GetProxiedInterface通过调用这个HostDispacther对象的成员函数GetProxiedInterface获得PPP_INSTANCE_INTERFACE_1_1接口,如下所示:

const void* HostDispatcher::GetProxiedInterface(const std::string& iface_name) {
  const void* proxied_interface =
      InterfaceList::GetInstance()->GetInterfaceForPPP(iface_name);
  if (!proxied_interface)
    return NULL;  // Don't have a proxy for this interface, don't query further.

  PluginSupportedMap::iterator iter(plugin_supported_.find(iface_name));
  if (iter == plugin_supported_.end()) {
    // Need to query. Cache the result so we only do this once.
    bool supported = false;

    bool previous_reentrancy_value = allow_plugin_reentrancy_;
    allow_plugin_reentrancy_ = true;
    Send(new PpapiMsg_SupportsInterface(iface_name, &supported));
    allow_plugin_reentrancy_ = previous_reentrancy_value;

    std::pair<PluginSupportedMap::iterator, bool> iter_success_pair;
    iter_success_pair = plugin_supported_.insert(
        PluginSupportedMap::value_type(iface_name, supported));
    iter = iter_success_pair.first;
  }
  if (iter->second)
    return proxied_interface;
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/host_dispatcher.cc中。

       从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,Plugin进程中存在一个InterfaceList单例对象。这个InterfaceList单例对象在创建的过程中,会初始化一系列的接口。这些接口是提供给Plugin Instance调用的,以便Plugin Instance可以与它们在Render进程中的Proxy进行通信。

       同样,在Render进程中,也存在一个InterfaceList单例对象。这个InterfaceList单例对象在创建的过程中,也会初始化一系列的接口,不过这些接口是提供给Plugin Instance Proxy调用的,以便Plugin Instance Proxy可以与它们在Plugin进程中的Plugin Instance进行通信。

       HostDispacther对象的成员函数GetProxiedInterface所做的事情就是检查Render进程中的InterfaceList单例对象是否提供了PPP_INSTANCE_INTERFACE_1_1接口。如果有提供,并且Plugin进程支持该接口,那么就会将它返回给调用者。注意,检查Plugin进程是否支持某个接口,是通过向它发送一个类型为PpapiMsg_SupportsInterface的IPC消息实现的。这里我们假设Plugin进程支持PPP_INSTANCE_INTERFACE_1_1接口。

       接下来我们先分析Render进程中的InterfaceList单例对象在创建过程中提供的三个Plugin Instance Proxy可以调用的接口,它们在接下来一篇文章分析Plugin Instance的3D渲染过程时将会使用到,如下所示:

InterfaceList::InterfaceList() {
  memset(id_to_factory_, 0, sizeof(id_to_factory_));

  // Register the API factories for each of the API types. This calls AddProxy
  // for each InterfaceProxy type we support.
  #define PROXIED_API(api_name) \
      AddProxy(PROXY_API_ID(api_name), &PROXY_FACTORY_NAME(api_name));

  // Register each proxied interface by calling AddPPB for each supported
  // interface. Set current_required_permission to the appropriate value for
  // the value you want expanded by this macro.
  #define PROXIED_IFACE(iface_str, iface_struct) \
      AddPPB(iface_str, \
             INTERFACE_THUNK_NAME(iface_struct)(), \
             current_required_permission);

  {
    Permission current_required_permission = PERMISSION_NONE;
    ......
    #include "ppapi/thunk/interfaces_ppb_public_stable.h"
  }

  ......

  AddPPP(PPP_INSTANCE_INTERFACE_1_1,
         PPP_Instance_Proxy::GetInstanceInterface());

  ......
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/interface_list.cc。

       这三个接口分别是API_ID_PPB_INSTANCE、API_ID_PPB_GRAPHICS_3D和PPP_INSTANCE_INTERFACE_1_1。其中,API_ID_PPB_INSTANCE和API_ID_PPB_GRAPHICS_3D这两个接口是通过include文件interfaces_ppb_public_stable.h进行定义的,如下所示:

PROXIED_API(PPB_Graphics3D)
......
PROXIED_API(PPB_Instance)
       这两个接口定义在文件external/chromium_org/ppapi/thunk/interfaces_ppb_public_stable.h中。

       其中,接口API_ID_PPB_INSTANCE由语句PROXIED_API(PPB_Instance)定义。我们在前面Chromium插件(Plugin)模块(Module)加载过程分析一文已经分析过这个接口的定义了,因此这里不再复述。实现这个接口的是一个模板函数ProxyFactory<PPB_Instance_Proxy>。以后调用这个接口就相当于调用模板函数ProxyFactory<PPB_Instance_Proxy>,并且会在调用后得到一个PPB_Instance_Proxy对象。

       接口API_ID_PPB_GRAPHICS_3D由语句PROXIED_API(PPB_Graphics3D)定义。它的定义过程与接口API_ID_PPB_INSTANCE是一样的,因此,我们容易知道,实现这个接口的是另外一个模板函数ProxyFactory<PPB_Graphics3D_Proxy>。以后调用这个接口就相当于调用模板函数ProxyFactory<PPB_Graphics3D_Proxy>,并且会在调用后得到一个PPB_Graphics3D_Proxy对象。

       回到InterfaceList类的构造函数中,第三个接口PPP_INSTANCE_INTERFACE_1_1是由一个PPP_Instance_1_1对象实现的。这个PPP_Instance_1_1对象可以通过调用PPP_Instance_Proxy类的静态成员函数GetInstanceInterface获得,如下所示:

static const PPP_Instance_1_1 instance_interface = {
  &DidCreate,
  &DidDestroy,
  &DidChangeView,
  &DidChangeFocus,
  &HandleDocumentLoad
};

......

const PPP_Instance* PPP_Instance_Proxy::GetInstanceInterface() {
  return &instance_interface;
}
      这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

      从这里我们就可以看到,接口PPP_INSTANCE_INTERFACE_1_1提供给Plugin Instance Proxy使用的函数,例如函数DidCreate,是用来请求Plugin进程创建一个Plugin Instance的,接下来我们就会看到这个函数的使用过程。

      分析到这里,我们就可以知道,HostDispacther类的成员函数GetProxiedInterface通过调用Render进程中的InterfaceList单例对象的成员函数GetInterfaceForPPP是可以获得PPP_INSTANCE_INTERFACE_1_1接口的。这个接口会沿着调用过程返回给PPP_Instance_Combined类的静态成员函数Create。

      PPP_Instance_Combined类的静态成员函数Create获得了PPP_INSTANCE_INTERFACE_1_1接口之后,就会使用它来创建一个PPP_Instance_Combined对象,如下所示:

PPP_Instance_Combined::PPP_Instance_Combined(
    const PPP_Instance_1_1& instance_if)
    : instance_1_1_(instance_if), did_change_view_1_0_(NULL) {}
      这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

      参数instance_if描述的就是前面获得的PPP_INSTANCE_INTERFACE_1_1接口。这个接口实际上是一个PPP_Instance_1_1对象。这个PPP_Instance_1_1对象将会保存在PPP_Instance_Combined类的成员变量instance_1_1_中。

      这一步执行完成后,我们就获得了一个PPP_Instance_Combined对象。这个PPP_Instance_Combined对象将会返回给前面分析的PepperPluginInstanceImpl类的静态成员函数Create,后者将会使用前者创建一个PepperPluginInstanceImpl对象。这个PepperPluginInstanceImpl对象的创建过程如下所示:

PepperPluginInstanceImpl::PepperPluginInstanceImpl(
    RenderFrameImpl* render_frame,
    PluginModule* module,
    ppapi::PPP_Instance_Combined* instance_interface,
    WebPluginContainer* container,
    const GURL& plugin_url)
    : ......,
      module_(module),
      instance_interface_(instance_interface),
      ...... {
  ......

  RendererPpapiHostImpl* host_impl = module_->renderer_ppapi_host();
  resource_creation_ = host_impl->CreateInProcessResourceCreationAPI(this);

  ......
}

      这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

      PepperPluginInstanceImpl类的构造函数首先将参数module和instance_interface指向的PluginModule对象和PPP_Instance_Combined对象分别保存在成员变量module_和instance_interface_,接下来又会通过参数module指向的PluginModule对象获得一个RendererPpapiHostImpl对象。这个RendererPpapiHostImpl对象是用来在Render进程中描述一个Plugin进程的,它是在Plugin Module的加载过程中创建的,具体可以参考前面Chromium的Plugin进程启动过程分析一文。有了这个RendererPpapiHostImpl对象之后,PepperPluginInstanceImpl类的构造函数就调用它的成员函数CreateInProcessResourceCreationAPI创建一个资源创建接口,如下所示:

scoped_ptr<ppapi::thunk::ResourceCreationAPI>
RendererPpapiHostImpl::CreateInProcessResourceCreationAPI(
    PepperPluginInstanceImpl* instance) {
  return scoped_ptr<ppapi::thunk::ResourceCreationAPI>(
      new PepperInProcessResourceCreation(this, instance));
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/renderer_ppapi_host_impl.cc中。

       从这里可以看到,这个资源创建接口是通过一个PepperInProcessResourceCreation对象描述的。这个PepperInProcessResourceCreation对象返回给PepperPluginInstanceImpl类的构造函数之后,将会保存在后者的成员变量resource_creation_中。在接下来一篇文章分析Plugin的3D渲染过程时,我们就会看到这个接口的使用过程。

       这一步执行完成后,回到PepperWebPluginImpl类的成员函数initialize中,这时候它就创建了一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。接下来PepperWebPluginImpl类的成员函数initialize又会调用上述PepperPluginInstanceImpl对象的成员函数Initialize对其进行初始化。在初始化的过程中,就会请求目标Plugin进程创建一个Plugin Instance,如下所示:

bool PepperPluginInstanceImpl::Initialize(
    const std::vector<std::string>& arg_names,
    const std::vector<std::string>& arg_values,
    bool full_frame) {
  ......

  argn_ = arg_names;
  argv_ = arg_values;
  scoped_ptr<const char * []> argn_array(StringVectorToArgArray(argn_));
  scoped_ptr<const char * []> argv_array(StringVectorToArgArray(argv_));
  bool success = PP_ToBool(instance_interface_->DidCreate(
      pp_instance(), argn_.size(), argn_array.get(), argv_array.get()));
  ......

  return success;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

       从前面的分析可以知道,PepperPluginInstanceImpl类的成员变量instance_interface_指向的是一个PPP_Instance_Combined对象。PepperPluginInstanceImpl类的成员函数Initialize主要是调用这个PPP_Instance_Combined对象的成员函数DidCreate请求在目标Plugin进程中创建一个Plugin Instance,如下所示:

PP_Bool PPP_Instance_Combined::DidCreate(PP_Instance instance,
                                         uint32_t argc,
                                         const char* argn[],
                                         const char* argv[]) {
  return CallWhileUnlocked(instance_1_1_.DidCreate, instance, argc, argn, argv);
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       从前面的分析可以知道,PPP_Instance_Combined类的成员变量instance_1_1_指向的是一个PPP_Instance_1_1对象。这个PPP_Instance_1_1对象描述的是一个PPP_INSTANCE_INTERFACE_1_1接口。PPP_Instance_Combined类的成员函数DidCreate通过一个帮助函数CallWhilleUnlocked调用这个PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidCreate,以便请求目标Plugin进程创建一个Plugin Instance。

      从前面的分析还可以知道,上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidCreate实现在ppp_instance_proxy.cc文件中,如下所示:

PP_Bool DidCreate(PP_Instance instance,
                  uint32_t argc,
                  const char* argn[],
                  const char* argv[]) {
  std::vector<std::string> argn_vect;
  std::vector<std::string> argv_vect;
  for (uint32_t i = 0; i < argc; i++) {
    argn_vect.push_back(std::string(argn[i]));
    argv_vect.push_back(std::string(argv[i]));
  }

  PP_Bool result = PP_FALSE;
  HostDispatcher::GetForInstance(instance)->Send(
      new PpapiMsg_PPPInstance_DidCreate(API_ID_PPP_INSTANCE, instance,
                                         argn_vect, argv_vect, &result));
  return result;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       函数DidCreate主要就是向目标Plugin进程发送一个类型为PpapiMsg_PPPInstance_DidCreate的IPC消息。这个IPC消息的Routing ID指定为API_ID_PPP_INSTANCE,表示它发送到目标Plugin进程后,要交给一个ID为API_ID_PPP_INSTANCE的PPAPI接口处理。

       目标Plugin进程可以通过参数instance描述的一个PP_Instance对象获得。在Render进程中,每一个Plugin Instance Proxy都关联有一个PP_Instance对象。因此,通过这个PP_Instance对象就可以找到它对应的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。每一个PepperPluginInstanceImpl对象又对应有一个Plugin Moudle。该Plugin Module所加载在的Plugin进程即为目标进程。知道了目标Plugin进程之后,就可以通过之前它在启动时与Render进程建立的Plugin通道向它发送IPC消息了。

       从前面Chromium的Plugin进程启动过程分析一文可以知道,每一个Plugin进程都存在一个PluginDispatcher对象。Plugin进程将会通过这个PluginDispatcher对象的成员函数OnMessageReceived接收Render进程发送过来的IPC消息。这意味着前面从Render进程发送过来的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息是通过PluginDispatcher类的成员函数OnMessageReceived接收的。

       PluginDispatcher类的成员函数OnMessageReceived是从父类Dispatcher继承下来的,它的实现如下所示:

bool Dispatcher::OnMessageReceived(const IPC::Message& msg) {
  ......

  InterfaceProxy* proxy = GetInterfaceProxy(
      static_cast<ApiID>(msg.routing_id()));
  ......

  return proxy->OnMessageReceived(msg);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/dispatcher.cc中。

       从前面的分析可以知道,此时参数msg指向的Message对象描述的是一个类型为PpapiMsg_PPPInstance_DidCreate的IPC消息,该消息的Routing ID为API_ID_PPP_INSTANCE。这个Routing ID描述的实际上是一个接口ID,Dispatcher类的成员函数OnMessageReceived通过调用另外一个成员函数GetInterfaceProxy可以获得该接口,如下所示:

InterfaceProxy* Dispatcher::GetInterfaceProxy(ApiID id) {
  InterfaceProxy* proxy = proxies_[id].get();
  if (!proxy) {
    // Handle the first time for a given API by creating the proxy for it.
    InterfaceProxy::Factory factory =
        InterfaceList::GetInstance()->GetFactoryForID(id);
    ......
    proxy = factory(this);
    DCHECK(proxy);
    proxies_[id].reset(proxy);
  }
  return proxy;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/dispatcher.cc中。

       Dispatcher类的成员函数GetInterfaceProxy首先在成员变量proxies_描述的数组中检查是否存在一个与ID为参数id的接口。如果存在,那么就直接将它返回给调用者。如果不存在,那么就会通过调用Plugin进程中的一个InterfaceList单例对象的成员函数GetFactoryForID检查它内部是否存在该接口。如果存在,那么就会获得一个类型为InterfaceProxy::Factory的函数。调用该函数将会获得一个InterfaceProxy对象。这个InterfaceProxy对象描述的就是一个ID为参数id的接口。

       Dispatcher类的成员函数GetInterfaceProxy在将获得的PPAPI接口返回给调用者之前,还会将其缓存在成员变量proxies_描述的数组中,以便以后可以直接获得,而不用通过Plugin进程中的InterfaceList单例对象获得。

       接下来,我们继续分析InterfaceList类的成员函数GetFactoryForID的实现,以便了解它返回的InterfaceProxy::Factory函数的实现,如下所示:

InterfaceProxy::Factory InterfaceList::GetFactoryForID(ApiID id) const {
  int index = static_cast<int>(id);
  ......
  return id_to_factory_[index];
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/interface_list.cc中。

       从前面的分析可以知道,此时参数id的值等于API_ID_PPP_INSTANCE。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,Plugin进程在加载Plugin Module的时候,会在InterfaceList类的成员变量id_to_factory_描述的数组中注册一个ID为API_ID_PPP_INSTANCE的模板函数ProxyFactory<PPP_Instance_Proxy>。因此,这时候InterfaceList类的成员函数GetFactoryForID将会返回模板函数ProxyFactory<PPP_Instance_Proxy>给调用者,也就是Dispatcher类的成员函数GetInterfaceProxy。

       Dispatcher类的成员函数GetInterfaceProxy获得了模板函数ProxyFactory<PPP_Instance_Proxy>之后,就会调用它创建一个PPP_Instance_Proxy对象。这个PPP_Instance_Proxy对象以后就会负责处理Routing ID为API_ID_PPP_INSTANCE的IPC消息。

       PPP_Instance_Proxy对象在创建的过程中,将会获得一个PPP_INSTANCE_INTERFACE_1_1接口。这个PPP_INSTANCE_INTERFACE_1_1接口在处理类型为PpapiMsg_PPPInstance_DidCreate的IPC消息的过程中将会使用到。

       接下来,我们就继续分析PPP_Instance_Proxy对象的创建过程,也就是它的构造函数的实现,以便了解它获得PPP_INSTANCE_INTERFACE_1_1接口的过程,如下所示:

PPP_Instance_Proxy::PPP_Instance_Proxy(Dispatcher* dispatcher)
    : InterfaceProxy(dispatcher) {
  if (dispatcher->IsPlugin()) {
    ......
    combined_interface_.reset(PPP_Instance_Combined::Create(
        base::Bind(dispatcher->local_get_interface())));
  }
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       从前面的调用过程可以知道,参数dispatcher指向的实际上是一个PluginDispatcher对象。调用这个PluginDispatcher对象的成员函数isPlugin得到的返回值为true。这时候PPP_Instance_Proxy类的构造函数会继续调用这个PluginDispatcher对象的成员函数local_get_interface获得一个PPP_GetInterface接口。这个接口是从Plugin Module导出来的。它的获取过程可以参考前面Chromium的Plugin进程启动过程分析一文。

       获得了从Plugin Module导出来的PPP_GetInterface接口之后,PPP_Instance_Proxy类的构造函数通过调用PPP_Instance_Combined类的静态成员函数Create将该接口封装在一个PPP_Instance_Combined对象中,如下所示:

PPP_Instance_Combined* PPP_Instance_Combined::Create(
    base::Callback<const void*(const char*)> get_interface_func) {
  // Try 1.1.
  const void* ppp_instance = get_interface_func.Run(PPP_INSTANCE_INTERFACE_1_1);
  if (ppp_instance) {
    const PPP_Instance_1_1* ppp_instance_1_1 =
        static_cast<const PPP_Instance_1_1*>(ppp_instance);
    return new PPP_Instance_Combined(*ppp_instance_1_1);
  }
  // Failing that, try 1.0.
  ppp_instance = get_interface_func.Run(PPP_INSTANCE_INTERFACE_1_0);
  if (ppp_instance) {
    const PPP_Instance_1_0* ppp_instance_1_0 =
        static_cast<const PPP_Instance_1_0*>(ppp_instance);
    return new PPP_Instance_Combined(*ppp_instance_1_0);
  }
  // No supported PPP_Instance version found.
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       前面我们已经分析过PPP_Instance_Combined类的静态成员函数Create的实现了。不过,这时候参数get_interface_func描述的是一个从Plugin Module导出来的PPP_GetInterface接口。从前面的分析可以知道,PPP_Instance_Combined类的静态成员函数Create最终会通过这个PPP_GetInterface接口获得另外一个类型为PPP_INSTANCE_INTERFACE_1_1的接口。

       Plugin Module导出来的PPP_GetInterface接口的实现如下所示:

PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
  if (g_module_singleton)
    return g_module_singleton->GetPluginInterface(interface_name);
  ......
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/ppp_entrypoints.cc中。

       从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,g_module_singleton是一个全局变量,它指向的是一个pp::Module对象。这个pp::Module对象描述的就是在当前Plugin进程中加载的Plugin Module。对于我们在前面Chromium插件(Plugin)机制简要介绍和学习计划一文中提到的GLES2 Example来说,这个pp::Module对象的实际类型为GLES2DemoModule。

       PPP_GetInterface接口主要就是调用上述pp::Module对象的成员函数GetPluginInterface获得与参数interface_name对应的接口。从前面的分析可以知道,此时参数interface_name的值等于PPP_INSTANCE_INTERFACE_1_1,也就是此时PPP_GetInterface接口是通过调用pp::Module类的成员函数GetPluginInterface获得PPP_INSTANCE_INTERFACE_1_1接口的,如下所示:

const void* Module::GetPluginInterface(const char* interface_name) {
  ......
  if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
    return &instance_interface;
  ......

  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/module.cc中。

       PPP_INSTANCE_INTERFACE是一个宏,它的定义为:

#define PPP_INSTANCE_INTERFACE PPP_INSTANCE_INTERFACE_1_1
       这个宏定义在文件external/chromium_org/ppap/c/ppp_instance.h

       回到pp::Module类的成员函数GetPluginInterface中,由于此时参数interface_name的值等于PPP_INSTANCE_INTERFACE_1_1,因此这时候pp::Module类的成员函数GetPluginInterface会将全局变量instance_interface描述的一个PPP_Instance对象给调用者。这个PPP_Instance对象的定义如下所示:

static PPP_Instance instance_interface = {
  &Instance_DidCreate,
  &Instance_DidDestroy,
  &Instance_DidChangeView,
  &Instance_DidChangeFocus,
  &Instance_HandleDocumentLoad
};
       这个PPP_Instance对象定义在文件external/chromium_org/ppapi/cpp/module.cc中。

       从这里就可以看到,Plugin进程中的PPP_INSTANCE_INTERFACE_1_1接口的实现。例如,它的成员变量DidCreate是一个函数指针,指向的函数为Instance_DidCreate,它是用来在Plugin进程中创建一个Plugin Instance的。

       这一步执行完成后,回到PPP_Instance_Combined类的静态成员函数Create中,这时候它就会将上述PPP_Instance对象封装在一个PPP_Instance_Combined对象中,并且将该PPP_Instance_Combined对象返回给调用者,即PPP_Instance_Proxy类的构造函数的实现,后者再将该PPP_Instance_Combined对象保存在成员变量combined_interface_中。

       这样,我们就分析完成了Dispatcher类的成员函数GetInterfaceProxy通过模板函数ProxyFactory<PPP_Instance_Proxy>创建PPP_Instance_Proxy对象的过程。这个PPP_Instance_Proxy对象会返回给Dispatcher类的另外一个成员函数OnMessageReceived,后者会将前面从Render进程发送过来的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息分发给该PPP_Instance_Proxy对象的成员函数OnMessageReceived处理,如下所示:

bool PPP_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) {
  ......

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPP_Instance_Proxy, msg)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPInstance_DidCreate,
                        OnPluginMsgDidCreate)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       PPP_Instance_Proxy类的成员函数OnMessageReceived将类型为PpapiMsg_PPPInstance_DidCreate的IPC消息分发给成员函数OnPluginMsgDidCreate处理,如下所示:

void PPP_Instance_Proxy::OnPluginMsgDidCreate(
    PP_Instance instance,
    const std::vector<std::string>& argn,
    const std::vector<std::string>& argv,
    PP_Bool* result) {
  *result = PP_FALSE;
  ......

  // Make sure the arrays always have at least one element so we can take the
  // address below.
  std::vector<const char*> argn_array(
      std::max(static_cast<size_t>(1), argn.size()));
  std::vector<const char*> argv_array(
      std::max(static_cast<size_t>(1), argn.size()));
  for (size_t i = 0; i < argn.size(); i++) {
    argn_array[i] = argn[i].c_str();
    argv_array[i] = argv[i].c_str();
  }

  DCHECK(combined_interface_.get());
  *result = combined_interface_->DidCreate(instance,
                                           static_cast<uint32_t>(argn.size()),
                                           &argn_array[0], &argv_array[0]);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       从前面的分析可以知道,PPP_Instance_Proxy类的成员变量combined_interface_指向的一个PPP_Instance_Combined对象。PPP_Instance_Proxy类的成员函数OnPluginMsgDidCreate主要是调用这个PPP_Instance_Combined对象的成员函数DidCreate在当前进程(Plugin进程)中创建一个Plugin Instance,如下所示:

PP_Bool PPP_Instance_Combined::DidCreate(PP_Instance instance,
                                         uint32_t argc,
                                         const char* argn[],
                                         const char* argv[]) {
  return CallWhileUnlocked(instance_1_1_.DidCreate, instance, argc, argn, argv);
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       从前面的分析可以知道,PPP_Instance_Combined类的成员变量instance_1_1_指向的是一个PPP_Instance对象。这个PPP_Instance对象的成员变量DidCreate是一个函数指针,它指向的函数为Instance_DidCreate。PPP_Instance_Combined类的成员函数DidCreate主要是调用这个函数来创建一个Plugin Instance。因此,接下来我们继续分析函数Instance_DidCreate的实现,如下所示:

PP_Bool Instance_DidCreate(PP_Instance pp_instance,
                           uint32_t argc,
                           const char* argn[],
                           const char* argv[]) {
  Module* module_singleton = Module::Get();
  if (!module_singleton)
    return PP_FALSE;

  Instance* instance = module_singleton->CreateInstance(pp_instance);
  if (!instance)
    return PP_FALSE;
  module_singleton->current_instances_[pp_instance] = instance;
  return PP_FromBool(instance->Init(argc, argn, argv));
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/module.cc中。

       函数Instance_DidCreate首先调用Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,这个pp::Module单例对象描述的就是在当前Plugin进程中加载的Plugin Module。有了这个pp::Module对象之后,就可以调用它的成员函数CreateInstance创建一个Plugin Instance,即一个pp::Instance对象。

       参数pp_instance的类型为PP_Instance。PP_Instance的实际类型为int32_t,也就是它描述的是一个有符号整数。这个有符号整数指定为当前创建的Plugin Instance的ID。与此同时,创建出来的Plugin Instance将会以它的ID为键值,保存在一个std::map中。这个std::map由上述获得的pp::Module单例对象的成员变量current_instances_描述。因此,通过这个std::map,我们就可以知道一个Plugin Module创建了多少个Plugin Instance。

       我们在开发一个Plugin的时候,会自定义一个pp::Module类。例如,在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example,它自定义的pp::Module类为GLES2DemoModule。自定义的 GLES2DemoModule类是从pp::Module类继承下来的,并且会重写成员函数CreateInstance。这意味着前面所创建的Plugin Instance的实际类型由自定义的pp::Module类决定的。

       例如,GLES2DemoModule类的成员函数CreateInstance创建的Plugin Instance的实际类型为GLES2DemoInstance,如下所示:

class GLES2DemoModule : public pp::Module {
 public:
  ......

  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new GLES2DemoInstance(instance, this);
  }
};
       这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

       至此,Chromium的Render进程就请求目标Plugin进程创建了一个Plugin Instance。这个Plugin Instance的实际类型由开发者定义,只要它是从pp::Instance类继承下来即可。同时,在Render进程当中,存在一个对应的Plugin Instance Proxy。这个Plugin Instance Proxy是通过PepperPluginInstanceImpl类描述的。以后在Render进程中加载的网页需要使用Plugin Instance的功能时,就可以通过Plugin Instance Proxy实现,而在Plugin进程中创建的Plugin Instance需要使用Chromium提供的功能时,可以通过Chromium提供的接口(Browser Interface)实现。

       在接下来一篇文章中,我们将以GLES2 Example为例,分析Plugin Instance通过Chromium提供的3D接口渲染自己的UI的过程。通过这个过程,我们就可以看到Plugin Instance与网页的交互过程,从而更好的理解Chromium的Plugin机制。敬请关注!更多的信息也可以关注老罗的新浪微薄:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/11/7 0:59:29 原文链接
阅读:32649 评论:2 查看评论

Chromium插件(Plugin)执行3D渲染的过程分析

$
0
0

       Chromium为网页的<embed>标签创建了Plugin之后,Plugin就负责渲染<embed>标签的内容。Chromium为Plugin提供了OpenGL接口,使得Plugin可在网页上渲染3D内容。当然,我们也可通过WebGL接口在网页上渲染3D内容。不过,前者渲染效率会更高些,因为它是Native接口,后者是JavaScript接口。本文接下来就详细分析Plugin执行3D渲染的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       我们分析Plugin执行3D渲染的过程,更多的是为了理解Chromium与Plugin的交互过程,包括Chromium操作Plugin的过程,以及Plugin调用Chromium提供的接口的过程。接下来我们就以Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,分析Plugin执行3D渲染的过程。

       从前面Chromium网页加载过程简要介绍和学习计划这个系列的文章可以知道,WebKit会将网页抽象为一个DOM Tree。网页中的每一个<embed>标签在这个DOM Tree中都会有一个对应的节点。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章又可以知道,网页的DOM Tree又会被Chromium转化为一个CC Layer Tree。其中,DOM Tree中的<embed>节点将对应于CC Layer Tree中的一个Texture Layer,如图1所示:


图1 DOM Tree中的<embed>标签与CC Layer Tree中的Texture Layer

       Plugin在调用Chromium提供的OpenGL接口的时候,实际上是将<embed>标签的内容渲染在它在CC Layer Tree中对应的Texture Layer上。当Chromium对网页的CC Layer Tree进行绘制的时候,它内部的Texture Layer的内容就会显示在屏幕上。Texture Layer描述的实际上是一个纹理。在前面Chromium硬件加速渲染的UI合成过程分析一文中,我们提到,网页的canvas标签在CC Layer Tree同样是对应有一个Texture Layer。因此,<embed>标签对应的Texture Layer与canvas标签对应的Texture Layer显示在屏幕上的过程是一样的。这一点可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。

       在Android平台上,Chromium提供给Plugin调用的OpenGL接口称为PPB_OPENGLES2_INTERFACE接口。PPB_OPENGLES2_INTERFACE接口提供了一系列的函数,每一个函数都对应于一个glXXX接口,如图2所示:


图2 Plugin调用OpenGL接口的过程

       在调用PPB_OPENGLES2_INTERFACE接口提供的函数时,GLES2Implementation类的相应成员函数会被调用。例如,当PPB_OPENGLES2_INTERFACE接口提供的函数ActiveTexture被调用时,GLES2Implementation类的成员函数ActiveTexture。GLES2Implementation类的成员函数会将要执行的GPU命令写入到一个缓冲区中去,然后通过一个PpapiCommandBufferProxy对象通知Render进程执行缓冲区中的GPU命令。Render进程又会通过一个CommandBufferProxyImpl对象将要执行的GPU命令转发给GPU进程中的一个GpuCommandBufferStub处理。这意味Plugin最终是通过GPU进程执行GPU命令的。关于GLES2Implementation、CommandBufferProxyImpl和GpuCommandBufferStub这三类执行GPU命令的过程,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       Plugin在通过PPB_OPENGLES2_INTERFACE接口执行OpenGL函数(GPU命令)之前,首先要初始化一个OpenGL环境。这个初始化操作发生在Plugin第一次知道自己的视图大小时,也就是知道它对应的<embed>标签的视图大小时。初始化过程如图3所示:


图3 Plugin的OpenGL环境初始化过程

       Plugin的视图大小是由WebKit计算出来的。计算出来之后,WebKit会通过运行在Render进程中的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,向运行Plugin进程中的Plugin Instance发出通知。Plugin Instance获得了这个通知之后,就可以初始化一个OpenGL环境了。

       Plugin Instance Proxy是通过调用PPP_INSTANCE_INTERFACE_1_1接口提供的一个函数DidChangeView向Plugin Instance发出通知的,后者又是通过向目标Plugin进程发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息发出该通知的。

       类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息携带了一个参数PpapiMsg_PPPInstance_DidChangeView。这个参数描述的是一个Routing ID,表示Plugin进程要将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息交给一个PPP_Instance_Proxy对象处理。这个PPP_Instance_Proxy对象获得该消息后,又会在当前Plugin进程中获得一个PPP_INSTANCE_INTERFACE_1_1接口,并且调用该接口提供的函数DidChangeView。该函数会找到目标Plugin Instance,并且调用它的成员函数DidChangeView。这样,Plugin Instance就可以初始化OpenGL环境了。以我们在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,它的成员函数DidChangeView就通过调用另外一个成员函数InitGL初始化OpenGL环境的。

       Plugin在初始化OpenGL环境的过程中,有两件重要的事情要做。第一件事情是创建一个OpenGL上下文,过程如图4所示:

  

图4 Plugin的OpenGL上文创建过程

       在Plugin进程中,OpenGL上下文通过Graphics3D类描述。因此,创建OpenGL上下文意味着是创建一个Graphics3D对象。这个Graphics3D对象在创建的过程中,会调用PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的一个函数Create。该函数又会通过一个APP_ID_RESOURCE_CREATION接口向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息。在Plugin进程中,APP_ID_RESOURCE_CREATION接口是通过一个ResourceCreationProxy对象实现的,因此,Plugin进程实际上是通过ResourceCreationProxy类向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的。

       Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息时,会指定一个参数APP_ID_PPB_GRAPHICS_3D,表示Render进程在接收到该消息后,要将其分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象又会通过调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw创建一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象在Render进程描述的就是一个OpenGL上下文。

       Plugin在初始化OpenGL环境的过程中做的第二件事情就是将刚刚创建出来的OpenGL上下文指定为当前要使用的OpenGL上下文。这个过程称为OpenGL上下文绑定,如图5所示:


图5 Plugin绑定OpenGL上下文的过程

       Plugin是通过调用PPB_INSTANCE_INTERFACE_1_0接口提供的函数BindGraphics进行OpenGL上下文绑定的。该函数又会通过一个APP_ID_PPB_INSTANCE接口向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息。在Plugin进程中,APP_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的,因此,Plugin进程实际上是通过PPB_Instance_Proxy类向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息的。

       Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息时,会指定一个参数APP_ID_PPB_INSTANCE,表示Render进程在接收到该消息后,要将其分发给一个PPB_Instance_Proxy对象处理。这个PPB_Instance_Proxy对象又会找到目标Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象,并且调用该PepperPluginInstanceImpl对象的成员函数BindGraphics。

       PepperPluginInstanceImpl类的成员函数BindGraphics在执行的过程中,会将指定的OpenGL上下文,也就是前面创建一个PPB_Graphics3D_Impl对象标记为被绑定,这样它接下来就会作为Plugin当前使用的OpenGL上下文了。

       OpenGL环境初始化完成之后,Plugin就可以使用图2所示的PPB_OPENGLES2_INTERFACE接口来执行3D渲染了。一帧渲染完毕,Plugin需要交换当前使用的OpenGL上下文的前后两个缓冲区,也就是执行一个SwapBuffers操作。这个操作的执行过程如图6所示:


图6 Plugin执行SwapBuffers操作的过程

      前面提到,在Plugin进程中,OpenGL上下文是通过一个Graphics3D对象描述的。这个Graphics3D对象可以通过PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers完成SwapBuffers操作。

      PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers在执行的过程中,会向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息。这个消息携带了一个参数API_ID_PPB_GRAPHICS_3D,表示Render进程需要将该消息分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象会找到Plugin当前绑定的OpenGL上下文,也就是一个PPB_Graphics3D_Impl对象,并且调用该PPB_Graphics3D_Impl对象的成员函数DoSwapBuffers。这时候就可以完成一个SwapBuffers操作,从而也完成一个帧的渲染流程。

      接下来,我们就从WebKit通知Plugin视图大小开始,分析Plugin执行3D渲染的完整流程。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,WebKit为<embed>标签创建了一个Plugin Instance之后,会将其放在一个由WebPluginContainerImpl类描述的Plugin View中。当<embed>标签的视图大小发生变化时,WebPluginContainerImpl类的成员函数reportGeometry就会被调用,它的实现如下所示:

void WebPluginContainerImpl::reportGeometry()
{
    ......

    IntRect windowRect, clipRect;
    Vector<IntRect> cutOutRects;
    calculateGeometry(frameRect(), windowRect, clipRect, cutOutRects);

    m_webPlugin->updateGeometry(windowRect, clipRect, cutOutRects, isVisible());

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp中。

       WebPluginContainerImpl类的成员函数reportGeometry首先是调用成员函数calulateGeometry计算<embed>标签的视图大小,然后再将计算得到的信息告知Content层,这是通过成员变量m_webPlugin指向的一个PepperWebPluginImpl对象的成员函数updateGeometry实现的。这个PepperWebPluginImpl对象的创建过程可以参考前面Chromium的Plugin进程启动过程分析一文。

       接下来我们继续分析PepperWebPluginImpl类的成员函数updateGeometry的实现,以便了解Render进程通知Plugin它描述的<embed>标签的大小的过程,如下所示:

void PepperWebPluginImpl::updateGeometry(
    const WebRect& window_rect,
    const WebRect& clip_rect,
    const WebVector<WebRect>& cut_outs_rects,
    bool is_visible) {
  plugin_rect_ = window_rect;
  if (!instance_->FlashIsFullscreenOrPending()) {
    std::vector<gfx::Rect> cut_outs;
    for (size_t i = 0; i < cut_outs_rects.size(); ++i)
      cut_outs.push_back(cut_outs_rects[i]);
    instance_->ViewChanged(plugin_rect_, clip_rect, cut_outs);
  }
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_webplugin_impl.cc中。

       PepperWebPluginImpl类的成员变量instance_指向的是一个PepperPluginInstanceImpl对象。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,这个PepperPluginInstanceImpl对象是用来在Render进程中描述一个Plugin Instance Proxy的。

       PepperWebPluginImpl类的成员函数updateGeometry首先调用上述PepperPluginInstanceImpl对象的成员函数FlashIsFullscreenOrPending判断当前正在处理的Plugin是否是一个Flash Plugin。如果是一个Flash Plugin,并且它当前处于全屏状态或者即将进入全屏状态,那么PepperWebPluginImpl类的成员函数updateGeometry就不会通知Plugin它描述的<embed>标签的视图大小发生了变化。

       我们假设当前正在处理的Plugin不是一个Flash Plugin。这时候PepperWebPluginImpl类的成员函数updateGeometry就会调用上述PepperPluginInstanceImpl对象的成员函数ViewChanged通知Plugin它描述的<embed>标签的视图大小发生了变化。

       PepperPluginInstanceImpl类的成员函数ViewChanged的实现如下所示:

void PepperPluginInstanceImpl::ViewChanged(
    const gfx::Rect& position,
    const gfx::Rect& clip,
    const std::vector<gfx::Rect>& cut_outs_rects) {
  ......

  view_data_.rect = PP_FromGfxRect(position);
  view_data_.clip_rect = PP_FromGfxRect(clip);
  ......

  SendDidChangeView();
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

       PepperPluginInstanceImpl类的成员函数ViewChanged首先将当前正在处理的Plugin描述的<embed>标签的视图大小信息保存在成员变量view_data_描述的一个ppapi::ViewData对象中,然后再调用另外一个成员函数SendDidChangeView向运行在Plugin进程中的Plugin Instance发送一个视图大小变化通知。

       PepperPluginInstanceImpl类的成员函数SendDidChangeView的实现如下所示:

void PepperPluginInstanceImpl::SendDidChangeView() {
  ......

  ScopedPPResource resource(
      ScopedPPResource::PassRef(),
      (new PPB_View_Shared(ppapi::OBJECT_IS_IMPL, pp_instance(), view_data_))
          ->GetReference());
  ......

  if (instance_interface_) {
    instance_interface_->DidChangeView(
        pp_instance(), resource, &view_data_.rect, &view_data_.clip_rect);
  }
}

       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

       PepperPluginInstanceImpl类的成员函数SendDidChangeView首先是将成员变量view_data_描述的ppapi::ViewData对象封装在一个PPB_View_Shared对象。从前面的分析可以知道,被封装的ppapi::ViewData对象描述的当前正在处理的Plugin描述的<embed>标签的视图大小信息。封装得到的PPB_View_Shared对象接下来会传递给PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView使用。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PepperPluginInstanceImpl类的成员变量instance_interface_指向的是一个PPP_Instance_Combined对象。这个PPP_Instance_Combined对象描述的是一个PPP_INSTANCE_INTERFACE_1_1接口。运行在Render进程中的Plugin Instance Proxy可以通过这个接口与运行在Plugin进程中的Plugin Instance通信。

       PepperPluginInstanceImpl类的成员函数SendDidChangeView主要就是调用上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView通知运行在Plugin进程中的Plugin Instance,它描述的<embed>标签的视图大小发生了变化,也就是通过调用成员变量instance_interface_指向的PPP_Instance_Combined对象的成员函数DidChangeView进行通知。

       PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:

void PPP_Instance_Combined::DidChangeView(PP_Instance instance,
                                          PP_Resource view_changed_resource,
                                          const struct PP_Rect* position,
                                          const struct PP_Rect* clip) {
  if (instance_1_1_.DidChangeView) {
    CallWhileUnlocked(
        instance_1_1_.DidChangeView, instance, view_changed_resource);
  } 

  ......
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_描述的是一个PPP_Instance_1_1对象。这个PPP_Instance_1_1对象描述的就是上述的PPP_INSTANCE_INTERFACE_1_1接口。PPP_Instance_Combined类的成员函数DidChangeView通过一个帮助函数CallWhilleUnlocked调用这个PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView,以便向运行在Plugin进程中的Plugin Instance发出一个视图大小变化通知。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView实现在ppp_instance_proxy.cc文件中,如下所示:

void DidChangeView(PP_Instance instance, PP_Resource view_resource) {
  HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance);

  EnterResourceNoLock<PPB_View_API> enter_view(view_resource, false);
  ......

  dispatcher->Send(new PpapiMsg_PPPInstance_DidChangeView(
      API_ID_PPP_INSTANCE, instance, enter_view.object()->GetData(),
      flash_fullscreen));
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       参数instance的类型为PP_Instance。PP_Instance描述的实际上是一个32位的有符号整数。这个32位的有符号整数是用来描述一个Plugin的ID。通过这个ID,运行在Render进程中的Plugin Instance Proxy与运行在Plugin进程中的Plugin Instance就能一一对应起来的。

       函数DidChangeView首先通过调用HostDispatcher类的静态成员函数GetForInstance获得一个HostDispatcher对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,每一个Plugin进程在Render进程中都有一个对应的HostDispatcher对象。这个HostDispatcher对象就是用来与它对应的Plugin进程通信的。给出一个PP_Instance,HostDispatcher类的静态成员函数GetForInstance就可以获得这个PP_Instance描述的Plugin Instance所运行在的Plugin进程对应的HostDispatcher对象。

       获得了目标Plugin进程对应的HostDispatcher对象之后,就可以向它发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息。这个IPC消息携带了四个参数:

       1. API_ID_PPP_INSTANCE,要求Plugin进程将该IPC消息分发给API_ID_PPP_INSTANCE接口处理。

       2. instance,表示目标Plugin Instance。

       3. ppapi::ViewData,表示目标Plugin Instance的当前视图大小。

       4. flash_fullscreen,表示目标Plugin Instance是否处于全屏状态。

       其中,第3个参数描述的ppapi::ViewData对象来自于前面在PepperPluginInstanceImpl类的成员函数SendDidChangeView中封装的PPB_View_Shared对象。这个PPB_View_Shared对象对象是通过函数DidChangeView的第2个参数view_resource传递进来的。

       函数DidChangeView首先将上述PPB_View_Shared对象封装在一个EnterResourceNoLock<PPB_View_API>对象中。接下来通过调用这个EnterResourceNoLock<PPB_View_API>对象的成员函数object就可以获得它封装的PPB_View_Shared对象。再调用这个PPB_View_Shared对象的成员函数GetData即可以获得它内部封装的一个ppapi::ViewData对象。这个ppapi::ViewData对象内部保存了目标Plugin Instance的当前视图大小信息,因此可以作为上述IPC消息的第3个参数。

       从前面Chromium的Plugin进程启动过程分析一文可以知道,每一个Plugin进程都存在一个PluginDispatcher对象。Plugin进程将会通过这个PluginDispatcher对象的成员函数OnMessageReceived接收Render进程发送过来的IPC消息。这意味着前面从Render进程发送过来的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息是通过PluginDispatcher类的成员函数OnMessageReceived接收的。

       PluginDispatcher类的成员函数OnMessageReceived是从父类Dispatcher继承下来的,它的实现如下所示:

bool Dispatcher::OnMessageReceived(const IPC::Message& msg) {  
  ......  
  
  InterfaceProxy* proxy = GetInterfaceProxy(  
      static_cast<ApiID>(msg.routing_id()));  
  ......  
  
  return proxy->OnMessageReceived(msg);  
}  
       这个函数定义在文件external/chromium_org/ppapi/proxy/dispatcher.cc中。

       从前面的分析可以知道,此时参数msg指向的Message对象描述的是一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息,该消息的Routing ID为API_ID_PPP_INSTANCE,表示要将该消息分发给一个API_ID_PPP_INSTANCE接口处理。这个API_ID_PPP_INSTANCE接口可以通过调用调用另外一个成员函数GetInterfaceProxy获得。

       在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过Dispatcher类的成员函数GetInterfaceProxy的实现,因此这里不再复述。再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPP_INSTANCE接口是由一个PPP_Instance_Proxy对象实现的,Dispatcher类的成员函数GetInterfaceProxy接下来会将参数msg描述的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给它处理,也就是调用它的成员函数OnMessageReceived。

       PPP_Instance_Proxy类的成员函数OnMessageReceived的实现如下所示:

bool PPP_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) {
  ......

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPP_Instance_Proxy, msg)
    ......
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPInstance_DidChangeView,
                        OnPluginMsgDidChangeView)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       从这里可以看到,PPP_Instance_Proxy类的成员函数OnMessageReceived将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给另外一个成员函数OnPluginMsgDidChangeView处理,如下所示:

void PPP_Instance_Proxy::OnPluginMsgDidChangeView(
    PP_Instance instance,
    const ViewData& new_data,
    PP_Bool flash_fullscreen) {
  ......

  combined_interface_->DidChangeView(instance, resource,
                                     &new_data.rect,
                                     &new_data.clip_rect);
}

       这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Proxy类的成员变量combined_interface_指向的是一个PPP_Instance_Combined对象。PPP_Instance_Proxy类的成员函数OnPluginMsgDidChangeView主要是调用这个PPP_Instance_Combined对象的成员函数DidChangeView通知参数instance描述的Plugin Instance,它的视图大小发生了变化。

       PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:

void PPP_Instance_Combined::DidChangeView(PP_Instance instance,
                                          PP_Resource view_changed_resource,
                                          const struct PP_Rect* position,
                                          const struct PP_Rect* clip) {
  if (instance_1_1_.DidChangeView) {
    CallWhileUnlocked(
        instance_1_1_.DidChangeView, instance, view_changed_resource);
  } 
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_指向的是一个PPP_Instance对象。这个PPP_Instance对象的成员变量DidChangeView是一个函数指针,它指向的函数为Instance_DidChangeView。PPP_Instance_Combined类的成员函数DidCreate主要是调用这个函数通知参数instance描述的Plugin Instance,它的视图大小发生了变化。

       函数Instance_DidChangeView的实现,如下所示:

void Instance_DidChangeView(PP_Instance pp_instance,
                            PP_Resource view_resource) {
  Module* module_singleton = Module::Get();
  ......
  Instance* instance = module_singleton->InstanceForPPInstance(pp_instance);
  ......
  instance->DidChangeView(View(view_resource));
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/module.cc中。

       函数Instance_DidChangeView首先调用Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,这个pp::Module单例对象描述的就是在当前Plugin进程中加载的Plugin Module。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,一个Plugin Module创建的所有Plugin Instance都会以它们的ID为键值,保存在一个std::map中。因此,函数Instance_DidChangeView可以在这个std::map中找到与参数pp_instance对应的Plugin Instance,即一个pp::Instance对象。这个通过调用前面获得的pp::Module单例对象的成员函数InstanceForPPInstance实现的。获得了目标pp::Instance对象之后,就可以调用它的成员函数DidChangeView,以便通知它视图大小发生了变化。

       我们在开发一个Plugin的时候,会自定义一个pp::Instance类。例如,在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example,它自定义的pp::Instance类为GLES2DemoInstance。自定义的GLES2DemoInstance类是从pp::Instance类继承下来的,并且会重写成员函数DidChangeView。这意味着接下来GLES2DemoInstance类的成员函数DidChangeView会被调用。

       GLES2DemoInstance类的成员函数DidChangeView的实现如下所示:

void GLES2DemoInstance::DidChangeView(
    const pp::Rect& position, const pp::Rect& clip_ignored) {
  ......
  plugin_size_ = position.size();

  // Initialize graphics.
  InitGL(0);
}
       这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

       参数position描述的一个pp::Rect对象记录了当前正在处理的Plugin Instance的当前视图大小。GLES2DemoInstance类的成员函数DidChangeView首先将这个视图大小记录在成员变量plugin_size_中,接下来又调用另外一个成员函数InitGL初始化一个OpenGL环境。

       GLES2DemoInstance类的成员函数InitGL的实现如下所示:

void GLES2DemoInstance::InitGL(int32_t result) {
  ......

  if (context_) {
    context_->ResizeBuffers(plugin_size_.width(), plugin_size_.height());
    return;
  }
  int32_t context_attributes[] = {
    PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
    PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8,
    PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8,
    PP_GRAPHICS3DATTRIB_RED_SIZE, 8,
    PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0,
    PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0,
    PP_GRAPHICS3DATTRIB_SAMPLES, 0,
    PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
    PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(),
    PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(),
    PP_GRAPHICS3DATTRIB_NONE,
  };
  context_ = new pp::Graphics3D(this, context_attributes);
  ......
  assert(BindGraphics(*context_));

  ......

  FlickerAndPaint(0, true);
}
       这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

       初始化Plugin的OpenGL环境需要执行两个操作:

       1. 创建一个OpenGL上下文。这个OpenGL上下文用一个pp::Graphics3D对象描述。

       2. 将创建出来的OpenGL上下文与Plugin进行绑定,也就是将它指定为Plugin当前使用的OpenGL上下文。这可以通过调用父类pp::Instance继承下来的成员函数BindGraphics来完成。

       GLES2DemoInstance类的成员变量context_就是用来描述Plugin当前使用的OpenGL上下文。如果这个OpenGL上下文还没有创建,那么GLES2DemoInstance类的成员函数InitGL就会根据Plugin当前的视图大小进行创建。否则的话,就只会改变这个OpenGL上下文描述的绘图缓冲区的大小。

       完成以上两个操作之后,GLES2DemoInstance类的成员函数InitGL就会调用另外一个成员函数FlickerAndPaint渲染Plugin的视图。渲染出来的内容最后就会合成在网页的UI中显示出来。

       接下来我们先分析OpenGL上下文的创建过程,也就是一个pp::Graphics3D对象的创建过程,我们pp::Graphics3D类的构造函数开始分析,它的实现如下所示:

Graphics3D::Graphics3D(const InstanceHandle& instance,
                       const int32_t attrib_list[]) {
  if (has_interface<PPB_Graphics3D_1_0>()) {
    PassRefFromConstructor(get_interface<PPB_Graphics3D_1_0>()->Create(
        instance.pp_instance(), 0, attrib_list));
  }
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。

       pp::Graphics3D类的构造函数首行调用模板函数has_interface<PPB_Graphics3D_1_0>检查在当前Plugin进程中加载的Plugin Module是否支持PPB_GRAPHICS_3D_INTERFACE_1_0。如果支持的话,那么就会再调用另外一个模板函数get_interface<PPB_Graphics3D_1_0>获得这个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,就可以调用它提供的函数Create请求Render进程创建一个OpenGL上下文了。

       从后面的分析我们可以知道,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数Create创建出来的OpenGL上下文用一个proxy::proxy::Graphics3D对象描述,不过它返回给调用者的是分配给proxy::proxy::Graphics3D对象的一个资源ID。pp::Graphics3D类的构造函数会调用从父类pp::Resource继承下来的成员函数PassRefFromConstructor将这个资源ID保存在成员变量pp_resource_中,如下所示:

void Resource::PassRefFromConstructor(PP_Resource resource) {
  PP_DCHECK(!pp_resource_);
  pp_resource_ = resource;
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/resource.cc中。

       以后通过调用pp::Resource类的成员函数pp_resource即可以获得这个资源ID,如下所示:

class Resource {
 public:
  ......

  PP_Resource pp_resource() const { return pp_resource_; }

  ......
};
       这个函数定义在文件external/chromium_org/ppapi/cpp/resource.h中。

       回到 pp::Graphics3D类的构造函数中,接下来我们首先分析PPB_GRAPHICS_3D_INTERFACE_1_0接口的获取过程,也就是模板函数get_interface<PPB_Graphics3D_1_0>的实现,如下所示:

template <typename T> inline T const* get_interface() {
  static T const* funcs = reinterpret_cast<T const*>(
      pp::Module::Get()->GetBrowserInterface(interface_name<T>()));
  return funcs;
}
       这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。

       这里的模板参数T为PPB_Graphics3D_1_0,展开后得到模板函数get_interface<PPB_Graphics3D_1_0>的具体实现为:

inline PPB_Graphics3D_1_0 const* get_interface() {
  static PPB_Graphics3D_1_0 const* funcs = reinterpret_cast<PPB_Graphics3D_1_0 const*>(
      pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Graphics3D_1_0>()));
  return funcs;
}
       它首先会调用另外一个模板函数interface_name<PPB_Graphics3D_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。

       我们首先分析模板函数interface_name<PPB_Graphics3D_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:

template <> const char* interface_name<PPB_Graphics3D_1_0>() {
  return PPB_GRAPHICS_3D_INTERFACE_1_0;
}
       这个模板函数定义在文件ppapi/cpp/graphics_3d.cc中。

       从这里可以看到,要获取的接口名称为PPB_GRAPHICS_3D_INTERFACE_1_0,也就我们要获取的是PPB_GRAPHICS_3D_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。

       在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_GRAPHICS_3D_INTERFACE_1_0接口是由一个PPB_Graphics3D_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:

const PPB_Graphics3D_1_0 g_ppb_graphics3d_thunk_1_0 = {  
  &GetAttribMaxValue,  
  &Create,  
  &IsGraphics3D,  
  &GetAttribs,  
  &SetAttribs,  
  &GetError,  
  &ResizeBuffers,  
  &SwapBuffers  
};  
      这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

      这个PPB_Graphics3D_1_0对象的成员变量Create是一个函数指针。这个函数指针指向了函数Create。这个函数也是定义在文件ppb_graphics_3d_thunk.cc中。

      回到Graphics3D类的构造函数中,现在我们就可以知道,它实际上是通过调用上述函数Create请求Render进程为当前正在处理的Plugin创建一个OpenGL上下文的,如下所示:

PP_Resource Create(PP_Instance instance,
                   PP_Resource share_context,
                   const int32_t attrib_list[]) {
  ......
  EnterResourceCreation enter(instance);
  ......
  return enter.functions()->CreateGraphics3D(instance,
                                             share_context,
                                             attrib_list);
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

       OpenGL上下文属于一种资源。在Plugin中,创建资源要使用到接口API_ID_RESOURCE_CREATION。函数Create通过构造一个EnterResourceCreation对象来封装对接口API_ID_RESOURCE_CREATION的调用。封装过程如下所示:

EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
    : EnterBase(),
      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

       在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。注意,PluginGlobals类是从PpapiGlobals继承下来的。

       获得上述PluginGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

thunk::ResourceCreationAPI* PluginGlobals::GetResourceCreationAPI(
    PP_Instance instance) {
  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
  if (dispatcher)
    return dispatcher->GetResourceCreationAPI();
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。

       从前面Chromium的Plugin进程启动过程分析一文可以知道,在Plugin进程中加载的Plugin Module对应有一个PluginDispatcher对象。这个PluginDispatcher对象与运行在Render进程中的HostDispatcher对象对应。所有属于该Plugin Module的Instance都使用相同的PluginDispatcher对象与Render进程通信。

       PluginGlobals类的成员函数GetResourceCreationAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,就可以调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

thunk::ResourceCreationAPI* PluginDispatcher::GetResourceCreationAPI() {
  return static_cast<ResourceCreationProxy*>(
      GetInterfaceProxy(API_ID_RESOURCE_CREATION));
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。

       PluginDispatcher类的成员函数GetResourceCreationAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_RESOURCE_CREATION接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的,在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过它的实现了。

       结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_RESOURCE_CREATION接口被指定为ResourceCreationProxy类的静态成员函数Create,Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个ResourceCreationProxy对象,如下所示:

InterfaceProxy* ResourceCreationProxy::Create(Dispatcher* dispatcher) {
  return new ResourceCreationProxy(dispatcher);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。

       参数dispatcher指向的是一个PluginDispatcher对象。这个PluginDispatcher对象就是在前面分析的PluginGlobals类的成员函数GetResourceCreationAPI中获取的PluginDispatcher对象。ResourceCreationProxy类的静态成员函数Create使用该PluginDispatcher对象创建了一个ResourceCreationProxy对象,并且返回给最初的调用者,也就是EnterResourceCreation类的构造函数。EnterResourceCreation类的构造函数又会将这个ResourceCreationProxy对象保存在成员变量functions_中。

       这一步执行完成后,回到前面分析的函数Create中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个ResourceCreationProxy对象。

       函数Create接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的ResourceCreationProxy对象。有了这个ResourceCreationProxy对象之后,就可以调用它的成员函数CreateGraphics3D请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文了。

       ResourceCreationProxy类的成员函数CreateGraphics3D的实现如下所示:

PP_Resource ResourceCreationProxy::CreateGraphics3D(
    PP_Instance instance,
    PP_Resource share_context,
    const int32_t* attrib_list) {
  return PPB_Graphics3D_Proxy::CreateProxyResource(
      instance, share_context, attrib_list);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。

       ResourceCreationProxy类的成员函数CreateGraphics3D调用PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文,如下所示:

PP_Resource PPB_Graphics3D_Proxy::CreateProxyResource(
    PP_Instance instance,
    PP_Resource share_context,
    const int32_t* attrib_list) {
  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
  ......

  HostResource share_host;
  gpu::gles2::GLES2Implementation* share_gles2 = NULL;
  if (share_context != 0) {
    EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
    if (enter.failed())
      return PP_ERROR_BADARGUMENT;

    PPB_Graphics3D_Shared* share_graphics =
        static_cast<PPB_Graphics3D_Shared*>(enter.object());
    share_host = share_graphics->host_resource();
    share_gles2 = share_graphics->gles2_impl();
  }

  std::vector<int32_t> attribs;
  if (attrib_list) {
    for (const int32_t* attr = attrib_list;
         attr[0] != PP_GRAPHICS3DATTRIB_NONE;
         attr += 2) {
      attribs.push_back(attr[0]);
      attribs.push_back(attr[1]);
    }
  }
  attribs.push_back(PP_GRAPHICS3DATTRIB_NONE);

  HostResource result;
  dispatcher->Send(new PpapiHostMsg_PPBGraphics3D_Create(
      API_ID_PPB_GRAPHICS_3D, instance, share_host, attribs, &result));
  ......

  scoped_refptr<Graphics3D> graphics_3d(new Graphics3D(result));
  if (!graphics_3d->Init(share_gles2))
    return 0;
  return graphics_3d->GetReference();
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

       PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的一个PluginDispatcher对象。后面将会通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息,用来请求Render进程创建一个OpenGL上下文了。

       类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息传递了4个参数给Render进程,分别是:

       1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。

       2. instance,描述要创建OpenGL上下文的Plugin Instance。

       3. share_host,描述另外一个OpenGL上下文,新创建的OpenGL上下文要与它在同一个共享组中。

       4. attribs,描述新创建的OpenGL上下文应具有的属性。

       Render进程根据要求为目标Plugin Instance创建了一个OpenGL上下文之后,会为该OpenGL上下文分配一个ID,并且会将该ID返回给Plugin进程。Plugin进程再将这个ID封装在一个HostResource对象,然后将这个HostResource对象返回给PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource。

       PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再根据获得的HostResource对象创建一个ppapi::proxy::Graphics3D对象,并且调用它的成员函数Init对它进行初始化,相当于是对新创建的OpenGL上下文进行初始化。初始化完成后,就可以进行使用了。

       新创建的Graphics3D对象初始化完成之后,PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再调用它的成员函数GetReference获得一个类型为PP_Resource的引用。以后通过这个引用就可以使用该Graphics3D对象,也就是新创建的OpenGL上下文。

       接下来,我们首先分析Render进程为目标Plugin Instance创建OpenGL上下文的过程,即处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,然后再分析新创建的OpenGL上下文在Plugin进程的初始化过程。

       前面提到,Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的。

       类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息在Render进程中的分发过程类似我们在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程中的分发过程,因此这里不再详细描述。

       接下来我们继续分析PPB_Graphics3D_Proxy类的成员函数OnMessageReceived处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,如下所示:

bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
#if !defined(OS_NACL)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_Create,
                        OnMsgCreate)
    ......
#endif  // !defined(OS_NACL)

    ......
    IPC_MESSAGE_UNHANDLED(handled = false)

  IPC_END_MESSAGE_MAP()
  // FIXME(brettw) handle bad messages!
  return handled;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

       从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给另外一个成员函数OnMsgCreate处理,如下所示:

void PPB_Graphics3D_Proxy::OnMsgCreate(PP_Instance instance,
                                       HostResource share_context,
                                       const std::vector<int32_t>& attribs,
                                       HostResource* result) {
  ......

  thunk::EnterResourceCreation enter(instance);

  if (enter.succeeded()) {
    result->SetHostResource(
      instance,
      enter.functions()->CreateGraphics3DRaw(instance,
                                             share_context.host_resource(),
                                             &attribs.front()));
  }
}
      这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

      参数instance要描述的是要创建OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Graphics3D_Proxy类的成员函数OnMsgCreate首先是使用它构造一个EnterResourceCreation对象。前面提到,EnterResourceCreation类是用来封装资源创建接口API_ID_RESOURCE_CREATION的。因此,有了前面构造的EnterResourceCreation对象之后,PPB_Graphics3D_Proxy类的成员函数OnMsgCreate就可以使用API_ID_RESOURCE_CREATION接口来为参数instance描述的Plugin Instance创建一个OpenGL上下文了。

       EnterResourceCreation对象在Render进程的构造过程与在Plugin进程的构造过程是不一样的。前面我们已经分析过EnterResourceCreation对象在Plugin进程的构造过程,接下来我们继续分析EnterResourceCreation对象在Render进程的构造过程,以便了解Render进程是如何实现API_ID_RESOURCE_CREATION接口的。

       我们从EnterResourceCreation类的构造函数开始分析EnterResourceCreation对象在Render进程的构造过程,它的实现如下所示:

EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
    : EnterBase(),
      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

       在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。HostGlobals类与前面提到的PluginGlobals类一样,都是从PpapiGlobals类继承下来的。

       获得上述HostGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

ppapi::thunk::ResourceCreationAPI* HostGlobals::GetResourceCreationAPI(
    PP_Instance pp_instance) {
  PepperPluginInstanceImpl* instance = GetInstance(pp_instance);
  if (!instance)
    return NULL;
  return &instance->resource_creation();
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。

       HostGlobals类的成员函数GetResourceCreationAPI首先调用成员函数GetInstance获得参数pp_instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。有了这个PepperPluginInstanceImpl对象之后,就可以调用它的成员函数resource_creation获得一个API_ID_RESOURCE_CREATION接口,如下所示:

class CONTENT_EXPORT PepperPluginInstanceImpl
    : public base::RefCounted<PepperPluginInstanceImpl>,
      public NON_EXPORTED_BASE(PepperPluginInstance),
      public ppapi::PPB_Instance_Shared,
      public NON_EXPORTED_BASE(cc::TextureLayerClient),
      public RenderFrameObserver {
 public:
  ......

  ppapi::thunk::ResourceCreationAPI& resource_creation() {
    return *resource_creation_.get();
  }

  ......

 private:
  ......

  scoped_ptr<ppapi::thunk::ResourceCreationAPI> resource_creation_;

  ......
};
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.h中。

       PepperPluginInstanceImpl类的成员变量resource_creation_指向的是一个PepperInProcessResourceCreation对象。PepperPluginInstanceImpl类的成员函数resource_creation将这个PepperInProcessResourceCreation对象返回给调用者。这意味在Render进程中,API_ID_RESOURCE_CREATION接口是通过PepperInProcessResourceCreation类实现的。

       这一步执行完成后,回到前面分析的PPB_Graphics3D_Proxy类的成员函数OnMsgCreate中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。

       PPB_Graphics3D_Proxy类的成员函数OnMsgCreate接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的PepperInProcessResourceCreation对象。有了这个PepperInProcessResourceCreation对象之后,就可以调用它的成员函数CreateGraphics3DRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文。

       PepperInProcessResourceCreation类的成员函数CreateGraphics3DRaw是从父类ResourceCreationImpl继承下来的,它的实现如下所示:

PP_Resource ResourceCreationImpl::CreateGraphics3DRaw(
    PP_Instance instance,
    PP_Resource share_context,
    const int32_t* attrib_list) {
  return PPB_Graphics3D_Impl::CreateRaw(instance, share_context, attrib_list);
}
      这个函数定义在文件external/chromium_org/content/renderer/pepper/resource_creation_impl.cc中。

      ResourceCreationImpl类的成员函数CreateGraphics3DRaw调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文,如下所示:

PP_Resource PPB_Graphics3D_Impl::CreateRaw(PP_Instance instance,
                                           PP_Resource share_context,
                                           const int32_t* attrib_list) {
  PPB_Graphics3D_API* share_api = NULL;
  if (share_context) {
    EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
    if (enter.failed())
      return 0;
    share_api = enter.object();
  }
  scoped_refptr<PPB_Graphics3D_Impl> graphics_3d(
      new PPB_Graphics3D_Impl(instance));
  if (!graphics_3d->InitRaw(share_api, attrib_list))
    return 0;
  return graphics_3d->GetReference();
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

       在Render进程中,Plugin使用的OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。因此,PPB_Graphics3D_Impl类的静态成员函数CreateRaw会创建一个PPB_Graphics3D_Impl对象,并且调用它的成员函数InitRaw对它进行初始化,也就是对它描述的OpenGL上下文进行初始化。初始化完成后,就会获得它的一个PP_Resource引用。这个引用将会返回给Plugin进程。Plugin进程以后就可以通过这个引用来使用前面创建出来的OpenGL上下文了。

       接下来我们继续分析Render进程为Plugin创建的OpenGL上下文的初始化过程,也就是PPB_Graphics3D_Impl类的成员函数InitRaw的实现,如下所示:

bool PPB_Graphics3D_Impl::InitRaw(PPB_Graphics3D_API* share_context,
                                  const int32_t* attrib_list) {
  ......

  RenderThreadImpl* render_thread = RenderThreadImpl::current();
  ......

  channel_ = render_thread->EstablishGpuChannelSync(
      CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE);
  ......

  command_buffer_ = channel_->CreateOffscreenCommandBuffer(
      surface_size, share_buffer, attribs, GURL::EmptyGURL(), gpu_preference);
  ......

  return true;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

       PPB_Graphics3D_Impl类的成员函数InitRaw首先调用RenderThreadImpl类的静态成员函数current获得一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的是当前Render进程的Render线程。有了这个RenderThreadImpl对象之后,就可以调用它的成员函数EstablishGpuChannelSync创建一个GPU通道,也就是一个GpuChannelHost对象。这个GPU通道的详细创建过程,可以参考前面Chromium的GPU进程启动过程分析一文。

       PPB_Graphics3D_Impl类的成员函数InitRaw接下来又会调用前面创建出来的GpuChannelHost对象的成员函数CreateOffscreenCommandBuffer请求GPU进程为其创建一个离屏渲染类型的OpenGL上下文。这个离屏渲染类型的OpenGL上下文是通过一个CommandBufferProxyImpl对象描述的。Render进程请求GPU进程创建OpenGL上下文的过程,可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文。以后Plugin执行GPU命令时,将会作用在上述离屏渲染的OpenGL上下文中。

       这一步执行完成后,Render进程就为Plugin创建了一个OpenGL上下文。这个OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。这个PPB_Graphics3D_Impl对象会被分配一个ID,并且这个ID会返回给Plugin进程。如前所述,Plugin进程获得了这个ID之后,就会将其封装在一个Graphics3D对象中。这个Graphics3D对象是用来Plugin进程描述一个OpenGL上下文的。

       回到前面分析的PPB_Graphics3D_Proxy类的成员函数CreateProxyResource中,这时候它就获得了一个OpenGL上下文之后。这个OpenGL上下文在Plugin进程中同样需要进行初始化。如前所述,这是通过调用ppapi::proxy::Graphics3D类的成员函数Init实现的。接下我们就继续分析Plugin进程初始化一个OpenGL上下文的过程,也就是分析ppapi::proxy::Graphics3D类的成员函数Init的实现,如下所示:

bool Graphics3D::Init(gpu::gles2::GLES2Implementation* share_gles2) {
  ......

  command_buffer_.reset(
      new PpapiCommandBufferProxy(host_resource(), dispatcher));

  return CreateGLES2Impl(kCommandBufferSize, kTransferBufferSize,
                         share_gles2);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

       ppapi::proxy::Graphics3D类的成员函数Init首先会创建一个PpapiCommandBufferProxy对象。这个PpapiCommandBufferProxy对象是用来与前面在Render进程中创建的CommandBufferProxyImpl对象通信的,也就是前者会将提交给它的GPU命令转发给后者处理,后者又会将接收到的GPU命令发送给GPU进程执行。

       ppapi::proxy::Graphics3D类的成员函数Init接下来又会调用成员函数CreateGLES2Impl根据上述PpapiCommandBufferProxy对象创建一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,Plugin就可以调用它提供的OpenGL函数执行3D渲染了。

       ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl是从父类PPB_Graphics3D_Shared继承下来的,它的实现如下所示:

bool PPB_Graphics3D_Shared::CreateGLES2Impl(
    int32 command_buffer_size,
    int32 transfer_buffer_size,
    gpu::gles2::GLES2Implementation* share_gles2) {
  gpu::CommandBuffer* command_buffer = GetCommandBuffer();
  DCHECK(command_buffer);

  // Create the GLES2 helper, which writes the command buffer protocol.
  gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer));
  if (!gles2_helper_->Initialize(command_buffer_size))
    return false;

  // Create a transfer buffer used to copy resources between the renderer
  // process and the GPU process.
  const int32 kMinTransferBufferSize = 256 * 1024;
  const int32 kMaxTransferBufferSize = 16 * 1024 * 1024;
  transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get()));
  ......

  // Create the object exposing the OpenGL API.
  gles2_impl_.reset(new gpu::gles2::GLES2Implementation(
      gles2_helper_.get(),
      share_gles2 ? share_gles2->share_group() : NULL,
      transfer_buffer_.get(),
      bind_creates_resources,
      lose_context_when_out_of_memory,
      GetGpuControl()));

  if (!gles2_impl_->Initialize(
           transfer_buffer_size,
           kMinTransferBufferSize,
           std::max(kMaxTransferBufferSize, transfer_buffer_size),
           gpu::gles2::GLES2Implementation::kNoLimit)) {
    return false;
  }

  ......

  return true;
}

       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc。

       在Chromium中,Command Buffer GL接口是通过GLES2Implementation类描述的。因此,PPB_Graphics3D_Shared类的成员函数CreateGLES2Impl将会创建一个GLES2Implementation对象,并且保存在成员变量gles2_impl_中。

       创建这个GLES2Implementation对象需要一个Command Buffer和一个Transfer Buffer。前者通过一个GLES2CmdHelper对象描述,后者通过一个TransferBuffer对象描述的。在创建GLES2CmdHelper对象的过程中,又需要用到前面创建的一个PpapiCommandBufferProxy对象,以便可以将Plugin要执行的GPU命令转发给Render进程处理。这个PpapiCommandBufferProxy对象可以通过调用PPB_Graphics3D_Shared类的成员函数GetCommandBuffer获得。关于Command Buffer GL接口的创建和使用,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数InitGL中,这时候它就创建了一个OpenGL上下文,并且这个OpenGL上下文已经初始化完成。接下来,GLES2DemoInstance类的成员函数InitGL会将前面创建出来的OpenGL上下文绑定到当前正在处理的Plugin Instance中去。执行了这个操作之后,Plugin Instance才可以在该OpenGL上下文中执行GPU命令。

       给一个Plugin Instance绑定OpenGL上下文是通过调用pp::Instance类的成员函数BindGraphics实现的,如下所示:

bool Instance::BindGraphics(const Graphics3D& graphics) {
  if (!has_interface<PPB_Instance_1_0>())
    return false;
  return PP_ToBool(get_interface<PPB_Instance_1_0>()->BindGraphics(
      pp_instance(), graphics.pp_resource()));
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。

       pp::Instance类的成员函数BindGraphics需要通过PPB_INSTANCE_INTERFACE_1_0接口为当前正在处理的Plugin Instance绑定一个OpenGL上下文。这个OpenGL上下文由参数graphics指向的一个Graphics3D对象描述。

       因此,pp::Instance类的成员函数BindGraphics首先会检查当前的Plugin进程是否支持PPB_INSTANCE_INTERFACE_1_0接口。如果支持的话,那么就会通过一个模板函数get_interface<PPB_Instance_1_0>获得该PPB_INSTANCE_INTERFACE_1_0接口,如下所示:

template <typename T> inline T const* get_interface() {
  static T const* funcs = reinterpret_cast<T const*>(
      pp::Module::Get()->GetBrowserInterface(interface_name<T>()));
  return funcs;
}
       这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。

       这里的模板参数T为PPB_Instance_1_0,展开后得到模板函数get_interface<PPB_Instance_1_0>的具体实现为:

inline PPB_Instance_1_0 const* get_interface() {
  static PPB_Instance_1_0 const* funcs = reinterpret_cast<PPB_Instance_1_0 const*>(
      pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Instance_1_0>()));
  return funcs;
}
       它首先会调用另外一个模板函数interface_name<PPB_Instance_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。

      我们首先分析模板函数interface_name<PPB_Instance_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:

template <> const char* interface_name<PPB_Instance_1_0>() {
  return PPB_INSTANCE_INTERFACE_1_0;
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。

       从这里可以看到,要获取的接口名称为PPB_INSTANCE_INTERFACE_1_0,也就我们要获取的是PPB_INSTANCE_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。

       在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_INSTANCE_INTERFACE_1_0接口是由一个PPB_Instance_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:

const PPB_Instance_1_0 g_ppb_instance_thunk_1_0 = {
  &BindGraphics,
  &IsFullFrame
};
       这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。

       这个PPB_Instance_1_0对象的成员变量BindGraphics是一个函数指针。这个函数指针指向了函数BindGraphics。这个函数也是定义在文件ppb_instance_thunk.cc中。

       回到pp::Instance类的成员函数BindGraphics中,现在我们就可以知道,它实际上是通过调用上述函数BindGraphics请求Render进程为当前正在处理的Plugin绑定一个OpenGL上下文的,如下所示:

PP_Bool BindGraphics(PP_Instance instance, PP_Resource device) {
  ......
  EnterInstance enter(instance);
  ......
  return enter.functions()->BindGraphics(instance, device);
}

       这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。

       函数BindGraphics需要通过另外一个接口API_ID_PPB_INSTANCE向Render进程发送一个IPC消息,以便请求后者为参数instance描述的Plugin Instance绑定另外一个参数device描述的OpenGL上下文。

       函数BindGraphics是通过EnterInstance类来使用接口API_ID_RESOURCE_CREATION,因此它首先会构造一个EnterInstance对象,如下所示:

EnterInstance::EnterInstance(PP_Instance instance)
    : EnterBase(),
      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

       前面提到,在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。获得了这个PluginGlobals单例对象之后,EnterInstance类的构造函数就调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:

thunk::PPB_Instance_API* PluginGlobals::GetInstanceAPI(PP_Instance instance) {
  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
  if (dispatcher)
    return dispatcher->GetInstanceAPI();
  return NULL;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。

       PluginGlobals类的成员函数GetInstanceAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得一个与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,再调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:

thunk::PPB_Instance_API* PluginDispatcher::GetInstanceAPI() {
  return static_cast<PPB_Instance_Proxy*>(
      GetInterfaceProxy(API_ID_PPB_INSTANCE));
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。

       PluginDispatcher类的成员函数GetInstanceAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_PPB_INSTANCE接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的。Dispatcher类的成员函数GetInterfaceProxy的实现,可以参考前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中。

       再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPB_INSTANCE接口被指定为模板函数ProxyFactory<PPB_Instance_Proxy>。Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个PPB_Instance_Proxy对象。这意味着在Plugin进程中,API_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的。这个PPB_Instance_Proxy对象会返回给EnterInstance类的构造函数。

       回到前面分析的函数BindGraphics,这时候就它构造了一个EnterInstance对象,并且这个EnterInstance对象的成员变量functions_指向了一个PPB_Instance_Proxy对象。这个PPB_Instance_Proxy对象可以通过调用上述构造的EnterInstance对象的成员函数functions获得。有了这个PPB_Instance_Proxy对象之后,函数BindGraphics就可以调用它的成员函数BindGraphics向Render进程发送一个IPC消息,以便请求它为当前正在处理的Plugin绑定一个OpenGL上下文,如下所示:

PP_Bool PPB_Instance_Proxy::BindGraphics(PP_Instance instance,
                                         PP_Resource device) {
  // If device is 0, pass a null HostResource. This signals the host to unbind
  // all devices.
  HostResource host_resource;
  PP_Resource pp_resource = 0;
  if (device) {
    Resource* resource =
        PpapiGlobals::Get()->GetResourceTracker()->GetResource(device);
    if (!resource || resource->pp_instance() != instance)
      return PP_FALSE;
    host_resource = resource->host_resource();
    pp_resource = resource->pp_resource();
  } else {
    // Passing 0 means unbinding all devices.
    dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
        API_ID_PPB_INSTANCE, instance, 0));
    return PP_TRUE;
  }

  // We need to pass different resource to Graphics 2D and 3D right now.  Once
  // 3D is migrated to the new design, we should be able to unify this.
  EnterResourceNoLock<PPB_Compositor_API> enter_compositor(device, false);
  EnterResourceNoLock<PPB_Graphics2D_API> enter_2d(device, false);
  EnterResourceNoLock<PPB_Graphics3D_API> enter_3d(device, false);
  if (enter_compositor.succeeded() || enter_2d.succeeded()) {
    dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
        API_ID_PPB_INSTANCE, instance, pp_resource));
    return PP_TRUE;
  } else if (enter_3d.succeeded()) {
    dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
        API_ID_PPB_INSTANCE, instance, host_resource.host_resource()));
    return PP_TRUE;
  }
  return PP_FALSE;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

       PPB_Instance_Proxy类的成员函数BindGraphics首先判断参数device的值是否不等于NULL。如果不等于NULL,那么它描述的就是一个OpenGL上下文,并且这个OpenGL上下文要与参数instance描述的Plugin Instance进行绑定。否则的话,就说明要将参数instance描述的Plugin Instance当前使用的OpenGL上下文设置为NULL。

       在我们这个情景中,参数device的值不等于NULL。在这种情况下,PPB_Instance_Proxy类的成员函数BindGraphics首先会根据这个参数获得一个Resource对象。这个Resource对象描述的就是一个OpenGL上下文。这个OpenGL上下文可能是用来执行2D渲染的,也可能是用来执行3D渲染的。PPB_Instance_Proxy类的成员函数BindGraphics会对这两种情况进行区别,不过最终都会向Render进程发送一个类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息。这个IPC消息携带了3个参数:

       1. API_ID_PPB_INSTANCE,要求Render进程将该IPC消息分发给API_ID_PPB_INSTANCE接口处理。

       2. instance,表示目标Plugin Instance。

       3. 通过参数device获得一个PP_Resource对象,描述的是一个要与目标Plugin Instance进行绑定的OpenGL上下文。

       Render进程接收类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息的过程与前面分析的类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息是类似的,只不过前者最后会分发给API_ID_PPB_INSTANCE接口处理,而后者会分发给API_ID_PPB_GRAPHICS_3D接口处理。它们在Render进程的分发过程都可以参考在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程的分发过程。

       从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,在Render进程中,API_ID_PPB_INSTANCE接口是由一个PPB_Instance_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给该PPB_Instance_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

bool PPB_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) {
  ......

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPB_Instance_Proxy, msg)
#if !defined(OS_NACL)
    ......
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBInstance_BindGraphics,
                        OnHostMsgBindGraphics)
    ......
#endif  // !defined(OS_NACL)

    ......

    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
      这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

      从这里可以看到,PPB_Instance_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给另外一个成员函数OnHostMsgBindGraphics处理,如下所示:

void PPB_Instance_Proxy::OnHostMsgBindGraphics(PP_Instance instance,
                                               PP_Resource device) {
  // Note that we ignroe the return value here. Otherwise, this would need to
  // be a slow sync call, and the plugin side of the proxy will have already
  // validated the resources, so we shouldn't see errors here that weren't
  // already caught.
  EnterInstanceNoLock enter(instance);
  if (enter.succeeded())
    enter.functions()->BindGraphics(instance, device);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

       参数instance要描述的是要绑定OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Instance_Proxy类的成员函数OnHostMsgBindGraphics首先是使用它构造一个EnterInstanceNoLock对象。这个EnterInstanceNoLock对象是用来封装一个Instance API的。这个Instance API可以用来给一个Plugin Instance Proxy绑定一个OpenGL上下文。

       接下来我们从EnterInstanceNoLock类的构造函数开始分析一个EnterInstanceNoLock对象的构造过程,如下所示:

EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
    : EnterBase(),
      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

       前面提到,在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。获得了这个HostGlobals单例对象之后,EnterInstanceNoLock类的构造函数就调用它的成员函数GetInstanceAPI获得一个Instance API,如下所示:

ppapi::thunk::PPB_Instance_API* HostGlobals::GetInstanceAPI(
    PP_Instance instance) {
  // The InstanceAPI is just implemented by the PluginInstance object.
  return GetInstance(instance);
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。

       HostGlobals类的成员函数GetInstanceAPI调用另外一个成员函数GetInstance获得参数instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,如下所示:

PepperPluginInstanceImpl* HostGlobals::GetInstance(PP_Instance instance) {
  ......
  InstanceMap::iterator found = instance_map_.find(instance);
  if (found == instance_map_.end())
    return NULL;
  return found->second;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc。

       在Render进程中,每一个Plugin Instance Proxy都会以分配给它的ID保存在HostGlobals类的成员变量instance_map_描述的一个std::map中。因此,给出一个ID,就可以在这个std::map中找到它对应的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。

       在Render进程中,Instance API是通过PPB_Instance_API类描述的。PepperPluginInstanceImpl类又是从PPB_Instance_API类继承下来的。因此,一个PepperPluginInstanceImpl对象可以当作一个Instance API使用。这个PepperPluginInstanceImpl对象将会返回给EnterInstanceNoLock类的构造函数,并且保存在EnterInstanceNoLock类的成员变量functions_中。

       这一步执行完成后,回到前面分析的PPB_Instance_Proxy类的成员函数OnMessageReceived中。这时候就它构造了一个EnterInstanceNoLock对象,并且这个EnterInstanceNoLock对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。注意,这个PepperPluginInstanceImpl对象在用作Instance API的同时,也是即将要绑定OpenGL上下文的Plugin Instance Proxy。这个绑定操作是通过调用它的成员函数BindGraphics实现的,如下所示:

PP_Bool PepperPluginInstanceImpl::BindGraphics(PP_Instance instance,
                                               PP_Resource device) {
  ......

  scoped_refptr<ppapi::Resource> old_graphics = bound_graphics_3d_.get();
  if (bound_graphics_3d_.get()) {
    bound_graphics_3d_->BindToInstance(false);
    bound_graphics_3d_ = NULL;
  }
  if (bound_graphics_2d_platform_) {
    bound_graphics_2d_platform_->BindToInstance(NULL);
    bound_graphics_2d_platform_ = NULL;
  }
  if (bound_compositor_) {
    bound_compositor_->BindToInstance(NULL);
    bound_compositor_ = NULL;
  }

  ......

  const ppapi::host::PpapiHost* ppapi_host =
      RendererPpapiHost::GetForPPInstance(instance)->GetPpapiHost();
  ppapi::host::ResourceHost* host = ppapi_host->GetResourceHost(device);
  PepperGraphics2DHost* graphics_2d = NULL;
  PepperCompositorHost* compositor = NULL;
  if (host) {
    if (host->IsGraphics2DHost()) {
      graphics_2d = static_cast<PepperGraphics2DHost*>(host);
    } else if (host->IsCompositorHost()) {
      compositor = static_cast<PepperCompositorHost*>(host);
    } 
    ......
  }

  EnterResourceNoLock enter_3d(device, false);
  PPB_Graphics3D_Impl* graphics_3d =
      enter_3d.succeeded()
          ? static_cast(enter_3d.object())
          : NULL;
  
  if (compositor) {
    if (compositor->BindToInstance(this)) {
      bound_compositor_ = compositor;
      ......
      return PP_TRUE;
    }
  } else if (graphics_2d) {
    if (graphics_2d->BindToInstance(this)) {
      bound_graphics_2d_platform_ = graphics_2d;
      ......
      return PP_TRUE;
    }
  } else if (graphics_3d) {
    // Make sure graphics can only be bound to the instance it is
    // associated with.
    if (graphics_3d->pp_instance() == pp_instance() &&
        graphics_3d->BindToInstance(true)) {
      bound_graphics_3d_ = graphics_3d;
      ......
      return PP_TRUE;
    }
  }

  // The instance cannot be bound or the device is not a valid resource type.
  return PP_FALSE;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cpp。

       Plugin Instance不仅可以将UI渲染在一个2D上下文或者一个3D上下文中,还可以渲染在一个Compositor上下文中。一个2D上下文和一个3D上下文描述的都只是一个Layer,而一个Compositor上下文可以描述多个Layer。也就是说,Plugin Instance可以通过Compositor上下文将自己的UI划分为多个Layer进行渲染。Chromium提供了一个PPB_COMPOSITOR_INTERFACE_0_1接口,Plugin Instance可以通过这个接口为自己创建一个Compositor上下文。

       其中,2D上下文通过一个PepperGraphics2DHost类描述,3D上下文通过PPB_Graphics3D_Impl类描述,而Compositor上下文通过PepperCompositorHost类描述。PepperPluginInstanceImpl类分别通过bound_graphics_2d_platform_、bound_graphics_3d_和bound_compositor_三个成员变量描述上述三种不同的绘图上下文。

       PepperPluginInstanceImpl类的成员函数BindGraphics主要做的工作就是将参数device描述的绘图上下文绑定为当前正在处理的Plugin Instance Proxy的绘图上下文。如果当前正在处理的Plugin Instance Proxy以前绑定过其它的绘图上下文,那么PepperPluginInstanceImpl类的成员函数BindGraphics就会先解除对它们的绑定。

       PepperPluginInstanceImpl类的成员函数BindGraphics接下来再判断参数device描述的绘图上下文是2D上下文、3D上下文,还是Compositor上下文,并且获取它所对应的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象,然后保存在对应的成员变量中。这样,当前正在处理的Plugin Instance Proxy就知道了自己最新绑定的绘图上下文是什么。最后,这些获得的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象的成员函数BindToInstance也会被调用,表示它们当前处于被绑定状态。

       在我们这个情景中,参数device描述的绘图上下文是一个3D上下文,也就是一个3D的OpenGL上下文。因此, PepperPluginInstanceImpl类的成员函数BindGraphics最终会获得一个PPB_Graphics3D_Impl对象,并且保存在成员变量bound_graphics_3d_中。最后,这个PPB_Graphics3D_Impl对象的成员函数BindToInstance会被调用,表示它当前处于被绑定的状态,如下所示:

bool PPB_Graphics3D_Impl::BindToInstance(bool bind) {
  bound_to_instance_ = bind;
  return true;
}
      这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

      从前面的调用过程可以知道,参数bind的值等于true。这时候PPB_Graphics3D_Impl类的成员变量bound_to_instance_的值也会被设置为true,表示当前正在处理的PPB_Graphics3D_Impl对象描述的3D上下文处于被绑定状态。

      这一步执行完成后,GLES2DemoInstance类就通过PPB_GRAPHICS_3D_INTERFACE_1_0和PPB_INSTANCE_INTERFACE_1_0这两个接口创建和绑定了一个OpenGL上下文,也就是初始化好一个OpenGL环境。接下来,GLES2DemoInstance类就可以通过PPB_OPENGLES2_INTERFACE接口进行3D渲染了。这个PPB_OPENGLES2_INTERFACE接口是在GLES2DemoInstance类的构造函数中获取的,如下所示:

GLES2DemoInstance::GLES2DemoInstance(PP_Instance instance, pp::Module* module)
    : pp::Instance(instance), pp::Graphics3DClient(this),
      callback_factory_(this),
      module_(module),
      context_(NULL),
      fullscreen_(false) {
  assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>(
      module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE))));
  ......
}

       这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

       GLES2DemoInstance类的构造函数是通过调用参数module指向的一个pp::Module对象的成员函数GetBrowserInterface获取PPB_OPENGLES2_INTERFACE接口的。在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_OPENGLES2_INTERFACE接口是由一个PPB_OpenGLES2对象实现的。这个PPB_OpenGLES2对象的定义如下所示:

  static const struct PPB_OpenGLES2 ppb_opengles2 = {  
      &ActiveTexture,                       &AttachShader,  
      &BindAttribLocation,                  &BindBuffer,  
      &BindFramebuffer,                     &BindRenderbuffer,  
      &BindTexture,                         &BlendColor,  
      &BlendEquation,                       &BlendEquationSeparate,  
      &BlendFunc,                           &BlendFuncSeparate,  
      &BufferData,                          &BufferSubData,  
      &CheckFramebufferStatus,              &Clear,  
      ......  
      &UseProgram,                          &ValidateProgram,  
      &VertexAttrib1f,                      &VertexAttrib1fv,  
      &VertexAttrib2f,                      &VertexAttrib2fv,  
      &VertexAttrib3f,                      &VertexAttrib3fv,  
      &VertexAttrib4f,                      &VertexAttrib4fv,  
      &VertexAttribPointer,                 &Viewport}; 
       这个对象定义在文件这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

       从这里我们就可以看到PPB_OPENGLES2_INTERFACE接口提供的函数,例如函数Clear,就相当于是OpenGL函数glClear。

       有了这个PPB_OPENGLES2_INTERFACE接口之后,GLES2DemoInstance类就会在成员函数FlickerAndPaint中调用它提供的函数进行3D渲染,如下所示:

void GLES2DemoInstance::FlickerAndPaint(int32_t result, bool paint_blue) {
  ......

  float r = paint_blue ? 0 : 1;
  float g = 0;
  float b = paint_blue ? 1 : 0;
  float a = 0.75;
  gles2_if_->ClearColor(context_->pp_resource(), r, g, b, a);
  gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT);
  ......

  context_->SwapBuffers(cb);
  ......
}
       这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

       GLES2DemoInstance类的成员函数FlickerAndPaint执行的3D渲染很简单,仅仅是调用PPB_OPENGLES2_INTERFACE接口提供的函数ClearColor和Clear绘制一个背景色,相当于是调用了OpenGL函数glClearColor和glClear绘制一个背景色。在实际开发中,我们可以调用PPB_OPENGLES2_INTERFACE接口提供的其它函数完成更复杂的3D渲染效果。

       从前面的分析可以知道,GLES2DemoInstance类的成员变量context_指向的是一个pp::Graphics3D对象。这个Graphics3D描述的是一个OpenGL上下文。每完成一帧渲染,GLES2DemoInstance类的成员函数FlickerAndPaint都需要调用这个pp::Graphics3D对象的成员函数SwapBuffers,用来交换当前使用的OpenGL上下文的前后两个缓冲区,相当于是调用了EGL函数eglSwapBuffers。

       接下来,我们就以PPB_OPENGLES2_INTERFACE接口提供的函数Clear的执行过程为例,分析Plugin是如何通过PPB_OPENGLES2_INTERFACE接口执行3D渲染操作的,它的实现如下所示:

void Clear(PP_Resource context_id, GLbitfield mask) {
  Enter3D enter(context_id, true);
  if (enter.succeeded()) {
    ToGles2Impl(&enter)->Clear(mask);
  }
}
      这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

      Enter3D定义为一个thunk::EnterResource<thunk::PPB_Graphics3D_API>类,如下所示:

typedef thunk::EnterResource<thunk::PPB_Graphics3D_API> Enter3D;
      这个类定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

      回到函数Clear中, 它的参数context_id描述的是一个OpenGL上下文资源ID。函数Clear首先将这个资源ID封装在一个EnterResource<thunk::PPB_Graphics3D_API>对象中,如下所示:

template<typename ResourceT, bool lock_on_entry = true>
class EnterResource
    : public subtle::LockOnEntry<lock_on_entry>,  // Must be first; see above.
      public subtle::EnterBase {
 public:
  EnterResource(PP_Resource resource, bool report_error)
      : EnterBase(resource) {
    Init(resource, report_error);
  }
  ......
};
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。

       EnterResource<thunk::PPB_Graphics3D_API>类的构造函数首先会调用父类EnterBase的构造函数。在调用的时候,会将参数resource描述的资源ID传递进去。EnterBase的构造函数通过这个资源ID获得对应的资源对象,保存在成员变量resource_中,如下所示:

EnterBase::EnterBase(PP_Resource resource)
    : resource_(GetResource(resource)),
      retval_(PP_OK) {
  PpapiGlobals::Get()->MarkPluginIsActive();
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

       从前面的分析可以知道,这个资源对象实际上就是一个ppapi::proxy::Graphics3D对象。与此同时,EnterBase的构造函数会通过调用Plugin进程的一个PluginGlobals单例对象的成员函数MarkPluginIsActive将当前正在执行3D渲染操作的Plugin标记为Active状态。

       回到EnterResource<thunk::PPB_Graphics3D_API>类的构造函数中,它接下来又会调用另外一个成员函数Init执行其它的初始化工作,如下所示:

template<typename ResourceT, bool lock_on_entry = true>
class EnterResource
    : public subtle::LockOnEntry<lock_on_entry>,  // Must be first; see above.
      public subtle::EnterBase {
 public:
  .......

  ResourceT* object() { return object_; }
  ......

 private:
  void Init(PP_Resource resource, bool report_error) {
    if (resource_)
      object_ = resource_->GetAs<ResourceT>();
    ......
  }

  ResourceT* object_;

  ......
};
       这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。

       EnterResource<thunk::PPB_Graphics3D_API>类的成员函数Init主要是将从父类EnterBase继承下来的成员变量resource_指向的ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象,并且保存在成员变量object_中。这个thunk::PPB_Graphics3D_API对象以后可以通过调用EnterResource<thunk::PPB_Graphics3D_API>类的成员函数object获得。

       注意,ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的,ppapi::PPB_Graphics3D_Shared类又是从thunk::PPB_Graphics3D_API类继承下来的,因此,我们可以将ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象。

       这一步执行完成后,回到前面分析的函数函数Clear中,这时候它就构造了一个EnterResource<thunk::PPB_Graphics3D_API>对象,并且这个EnterResource<thunk::PPB_Graphics3D_API>对象的成员变量object_指向了一个thunk::PPB_Graphics3D_API对象。函数Clear接下来又会调用另外一个函数ToGles2Impl从前面构造的EnterResource<thunk::PPB_Graphics3D_API>对象中获得一个GLES2Implementation对象,如下所示:

gpu::gles2::GLES2Implementation* ToGles2Impl(Enter3D* enter) {
  ......
  return static_cast<PPB_Graphics3D_Shared*>(enter->object())->gles2_impl();
}
       这个函数定义在文件ternal/chromium_org/ppapi$ vi shared_impl/ppb_opengles2_shared.cc中。

       函数ToGles2Impl首先调用参数enter指向的EnterResource<thunk::PPB_Graphics3D_API>对象的成员函数object获得它的成员变量object_所指向的一个thunk::PPB_Graphics3D_API对象。

       前面提到,由于EnterResource<thunk::PPB_Graphics3D_API>类的成员变量object_指向的实际上是一个ppapi::proxy::Graphics3D对象,并且ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的。因此函数ToGles2Impl又可以将它获得的thunk::PPB_Graphics3D_API对象转换为一个ppapi::PPB_Graphics3D_Shared对象。

       有了这个ppapi::PPB_Graphics3D_Shared对象之后,函数ToGles2Impl就可以调用它的成员函数gles2_impl获得它内部维护的一个GLES2Implementation对象。这个GLES2Implementation对象就是在前面分析的ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl中创建的。

       函数ToGles2Impl最后会将获得的GLES2Implementation对象返回给函数Clear。函数Clear获得了这个GLES2Implementation对象之后,就会调用它的成员函数Clear给参数context_id描述的OpenGL上下文设置一个背景色。

       从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,当我们调用GLES2Implementation类的成员函数执行GPU命令时,这些GPU命令实际上只是写入到了一个Command Buffer中。这个Command Buffer在合适的时候将会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行它里面的GPU命令。

       从前面的分析可以知道,为Plugin创建的GLES2Implementation对象所用的Command Buffer是通过一个PpapiCommandBufferProxy对象描述的。当这个WaitForGetOffsetInRange对象的成员函数WaitForGetOffsetInRange被调用的时候,它实际上只是向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息,如下所示:

void PpapiCommandBufferProxy::WaitForTokenInRange(int32 start, int32 end) {
  ......

  bool success;
  gpu::CommandBuffer::State state;
  if (Send(new PpapiHostMsg_PPBGraphics3D_WaitForTokenInRange(
          ppapi::API_ID_PPB_GRAPHICS_3D,
          resource_,
          start,
          end,
          &state,
          &success)))
    UpdateState(state, success);
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppapi_command_buffer_proxy.cc中。

       这个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息携带了一个参数ppapi::API_ID_PPB_GRAPHICS_3D,表示Render进程接收到这个IPC消息之后,要分发一个API_ID_PPB_GRAPHICS_3D接口处理。

       前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
#if !defined(OS_NACL)
    ......
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange,
                        OnMsgWaitForGetOffsetInRange)
    ......
#endif  // !defined(OS_NACL)

    ......
    IPC_MESSAGE_UNHANDLED(handled = false)

  IPC_END_MESSAGE_MAP()
  // FIXME(brettw) handle bad messages!
  return handled;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。

       从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给另外一个成员函数OnMsgWaitForGetOffsetInRange处理,如下所示:

void PPB_Graphics3D_Proxy::OnMsgWaitForGetOffsetInRange(
    const HostResource& context,
    int32 start,
    int32 end,
    gpu::CommandBuffer::State* state,
    bool* success) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  ......
  *state = enter.object()->WaitForGetOffsetInRange(start, end);
  *success = true;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。

       从前面的分析可以知道,参数context描述的OpenGL上下文在Render进程中用一个PPB_Graphics3D_Impl对象描述。PPB_Graphics3D_Proxy类的成员函数OnMsgWaitForGetOffsetInRange会通过构造一个EnterHostFromHostResource<PPB_Graphics3D_API>对象来获得这个PPB_Graphics3D_Impl对象,并且会调用这个PPB_Graphics3D_Impl对象的成员函数WaitForGetOffsetInRange,用来通知它将Plugin写入在Command Buffer中的GPU命令发送给GPU进程处理。

        PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange的实现如下所示:

gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForGetOffsetInRange(
    int32_t start,
    int32_t end) {
  GetCommandBuffer()->WaitForGetOffsetInRange(start, end);
  return GetCommandBuffer()->GetLastState();
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

       PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange首先调用成员函数GetCommandBuffer获得一个CommandBuffer对象,如下所示:

gpu::CommandBuffer* PPB_Graphics3D_Impl::GetCommandBuffer() {
  return command_buffer_;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

       PPB_Graphics3D_Impl类的成员函数GetCommandBuffer返回的是成员变量command_buffer_指向的一个CommandBuffer对象。从前面的分析可以知道,这个CommandBuffer对象实现上是一个CommandBufferProxyImpl对象,它是在前面分析的PPB_Graphics3D_Impl类的成员函数InitRaw中创建的。

       回到PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange中,它获得了一个CommandBufferProxyImpl对象之后,就会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行Plugin写入在Command Buffer中的GPU命令。这个通知过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数FlickerAndPaint中,它通过PPB_OPENGLES2_INTERFACE接口提供的函数渲染好一帧后,就会调用其成员变量context_指向的一个pp::Graphics3D对象的成员函数SwapBuffers交换Plugin当前使用的OpenGL上下文的前后两个缓冲区,也就是相当于是执行EGL函数eglSwapBuffers。

       接下来,我们就从pp::Graphics3D类的成员函数SwapBuffers开始,分析Plugin交换当前使用的OpenGL上下文的前后两个缓冲区的过程,如下所示:

int32_t Graphics3D::SwapBuffers(const CompletionCallback& cc) {
  ......

  return get_interface<PPB_Graphics3D_1_0>()->SwapBuffers(
      pp_resource(),
      cc.pp_completion_callback());
}
       这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。

       pp::Graphics3D类的成员函数SwapBuffers首先调用我们前面分析过的模板函数get_interface<PPB_Graphics3D_1_0>获得一个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,再调用它提供的函数SwapBuffers请求Render进程交换正在处理的pp::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

       PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers的实现如下所示:

int32_t SwapBuffers(PP_Resource context,
                    struct PP_CompletionCallback callback) {
  ......
  EnterResource<PPB_Graphics3D_API> enter(context, callback, true);
  if (enter.failed())
    return enter.retval();
  return enter.SetResult(enter.object()->SwapBuffers(enter.callback()));
}
       这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

       与前面分析的PPB_OPENGLES2_INTERFACE接口提供的函数Clear类似,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers也是通过构造一个EnterResource<thunk::PPB_Graphics3D_API>对象来获得参数context描述的一个OpenGL上下文资源对象,也就是一个ppapi::proxy::Graphics3D对象。有了这个ppapi::proxy::Graphics3D对象之后,就可以调用它的成员函数SwapBuffers请求Render进程交换它描述的OpenGL上下文的前后缓冲区了。

       ppapi::proxy::Graphics3D类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的,它的实现如下所示:

int32_t PPB_Graphics3D_Shared::SwapBuffers(
    scoped_refptr<TrackedCallback> callback) {
  ......
  return DoSwapBuffers();
}
       这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc中。

       ppapi::proxy::Graphics3D类的成员函数SwapBuffers又会调用由子类实现的成员函数DoSwapBuffers请求Render进程交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

       在我们这个情景中,这个子类即为ppapi::proxy::Graphics3D,因此接下来ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers会被调用,它的实现如下所示:

int32 Graphics3D::DoSwapBuffers() {
  gles2_impl()->SwapBuffers();
  IPC::Message* msg = new PpapiHostMsg_PPBGraphics3D_SwapBuffers(
      API_ID_PPB_GRAPHICS_3D, host_resource());
  msg->set_unblock(true);
  PluginDispatcher::GetForResource(this)->Send(msg);

  return PP_OK_COMPLETIONPENDING;
}
      这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

      ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers首先是通过调用成员函数gles2_impl获得前面为Plugin创建的一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,就可以调用它的成员函数SwapBuffers向Command Buffer写入一个gles2::cmds::SwapBuffers命令。GPU进程在执行这个命令的时候,就会交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

      ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers接下来又会调用PluginDispatcher类的静态成员函数GetForResource获得与当前正在处理的Plugin对应的一个PluginDispatcher对象,并且通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息,用来通知Render进程更新当前正在处理的Plugin在网页上的视图。这个IPC消息包含两个参数:

      1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。

      2. 通过调用成员函数host_resource获得的一个HostResource对象,这个HostResource对象描述的就是要交换前面缓冲区的OpenGL上下文。

      前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
#if !defined(OS_NACL)
    ......
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_SwapBuffers,
                        OnMsgSwapBuffers)
    ......
#endif  // !defined(OS_NACL)

    ......
    IPC_MESSAGE_UNHANDLED(handled = false)

  IPC_END_MESSAGE_MAP()
  // FIXME(brettw) handle bad messages!
  return handled;
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

       从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给另外一个成员函数OnMsgSwapBuffers处理,如下所示:

void PPB_Graphics3D_Proxy::OnMsgSwapBuffers(const HostResource& context) {
  EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API> enter(
      context, callback_factory_,
      &PPB_Graphics3D_Proxy::SendSwapBuffersACKToPlugin, context);
  if (enter.succeeded())
    enter.SetResult(enter.object()->SwapBuffers(enter.callback()));
}
       这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

       参数context描述的是一个要交换前后缓冲区的OpenGL上下文。从前面的分析可以知道,在Render进程中,这个OpenGL上下文是通过一个PPB_Graphics3D_Impl对象描述的。PPB_Graphics3D_Proxy类的成员函数OnMsgSwapBuffers通过构造一个EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API>来获得这个PPB_Graphics3D_Impl对象,并且调用这个PPB_Graphics3D_Impl对象的成员函数SwapBuffers更新当前正在处理的Plugin在网页上的视图。

       PPB_Graphics3D_Impl类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的。前面我们已经分析过ppapi::PPB_Graphics3D_Shared类的成员函数SwapBuffers的实现了,它最终会调用由子类实现的成员函数DoSwapBuffers,即PPB_Graphics3D_Impl类的成员函数DoSwapBuffers。

       PPB_Graphics3D_Impl类的成员函数DoSwapBuffers的实现如下所示:

int32 PPB_Graphics3D_Impl::DoSwapBuffers() {
  ......

  if (bound_to_instance_) {
    // If we are bound to the instance, we need to ask the compositor
    // to commit our backing texture so that the graphics appears on the page.
    // When the backing texture will be committed we get notified via
    // ViewFlushedPaint().
    //
    // Don't need to check for NULL from GetPluginInstance since when we're
    // bound, we know our instance is valid.
    HostGlobals::Get()->GetInstance(pp_instance())->CommitBackingTexture();
    commit_pending_ = true;
  } 

  ......

  return PP_OK_COMPLETIONPENDING;
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

       从前面的分析可以知道,当前正在处理的PPB_Graphics3D_Impl对象描述的OpenGL上下文此时处于绑定状态,也就是它的成员变量bound_to_instance_的值等于true。在这种情况下,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers首先会通过HostGlobals类的静态成员函数Get获得当前Render进程中的一个HostGlobals单例对象。有了这个HostGlobals单例对象之后,就可以调用它的成员函数GetInstance获得与当前正在处理的PPB_Graphics3D_Impl对象进行绑定的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。

      有了上述PepperPluginInstanceImpl对象之后,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers就可以调用它的成员函数CommitBackingTexture更新它在网页上的视图,如下所示:

void PepperPluginInstanceImpl::CommitBackingTexture() {
  ......
  gpu::Mailbox mailbox;
  uint32 sync_point = 0;
  bound_graphics_3d_->GetBackingMailbox(&mailbox, &sync_point);
  ......
  texture_layer_->SetTextureMailboxWithoutReleaseCallback(
      cc::TextureMailbox(mailbox, GL_TEXTURE_2D, sync_point));
  texture_layer_->SetNeedsDisplay();
}
       这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc。 

       从前面的分析可以知道,PepperPluginInstanceImpl类的成员变量bound_graphics_3d_指向的是一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象描述的是一个OpenGL上下文。这个OpenGL上下文就是当前正在处理的PepperPluginInstanceImpl对象绑定的OpenGL上下文。

       PepperPluginInstanceImpl类的另外一个成员变量texture_layer_指向的是一个cc::TextureLayer对象。前面提到,网页中的每一个<embed>标签在网页的CC Layer Tree中都对应有一个Texture Layer。这个Texture Layer就是通过上述cc::TextureLayer对象描述的。

       有了上述PPB_Graphics3D_Impl对象之后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture就可以调用它的成员函数GetBackingMailbox获得已经被交换到后端的缓冲区。这个缓冲区实际上是一个纹理缓冲区。这个纹理缓冲区将会作为<embed>标签的内容,因此PepperPluginInstanceImpl类的成员函数CommitBackingTexture就会将它设置上述cc::TextureLayer对象所要绘制的内容。这是通过调用该cc::TextureLayer对象的成员函数SetTextureMailboxWithoutReleaseCallback实现的。

       最后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture调用上述cc::TextureLayer对象的成员函数SetNeedsDisplay将自己标记为需要更新。这样在下一个VSync信号到来的时候,前面设置为它的内容的纹理缓冲区就会被渲染和合成在网页的UI上。这个渲染和合成过程可以参考前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章。

       这样,我们就分析完成了Plugin执行3D渲染的过程,也就是Plugin为<embed>标签绘制3D视图的过程,这是通过调用Chromium提供的PPB_OPENGLES2_INTERFACE接口完成的。从这个过程我们就可以看到Plugin和Chromium的交互过程,也就是它们可以互相调用对方提供的接口,从而使得Plugin可以像我们在前面Chromium扩展(Extension)机制简要介绍和学习计划这个系列的文章提到的Extension一样,增强网页的功能。

       至此,我们也分析完成了Chromium的Plugin机制。重新学习可以参考前面Chromium插件(Plugin)机制简要介绍和学习计划一文。更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/11/14 1:00:15 原文链接
阅读:35207 评论:4 查看评论

Android WebView简要介绍和学习计划

$
0
0

       我们通常会在App的UI中嵌入WebView,用来实现某些功能的动态更新。在4.4版本之前,Android WebView基于WebKit实现。不过,在4.4版本之后,Android WebView就换成基于Chromium的实现了。基于Chromium实现,使得WebView可以更快更流畅地显示网页。本文接下来就介绍Android WebView基于Chromium的实现原理,以及制定学习计划。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       通过前面几个系列文章的学习,我们知道,Chromium的实现是相当复杂的。这种复杂可以体现在编译出来的动态库的大小上,带符号版本1.3G左右,去掉符号后还有27M左右。编译过AOSP源码的都知道,在整个编译过程中,Chromium库占用了大部分的时间,尤其是在生成上述1.3G文件时,电脑几乎卡住了。因此,在使用Chromium实现WebView时,第一个要解决的问题就是它的动态库加载问题。

       Android系统的动态库是ELF文件结构。ELF文件由ELF Header、Program Header Table、Section以及Section Header Table组成。ELF Header位于文件的开头,它同时描述了Program Header Table和Section Header Table在文件中的偏移位置。Section Header Table描述了动态库由哪些Section组成,典型的Section有.text、.data、.bss和.rodata等。这些Section描述的要么是程序代码,要么是程序数据。Program Header Table描述了动态库由哪些Segment组成。一个Segment又是由一个或者若干个Section组成的。Section信息是在程序链接期间用到的,而Segment信息是在程序加载期间用到的。关于ELF文件格式的更详细描述,可以参考Executable and Linkable Format

       对于动态库来说,它的程序代码是只读的。这意味着一个动态库不管被多少个进程加载,它的程序代码都是只有一份。但是,动态库的程序数据,链接器在加载的时候,会为每一个进程都独立加载一份。对于Chromium动态库来说,它的程序代码占据了95%左右的大小,也就是25.65M左右。剩下的5%,也就是1.35M,是程序数据。假设有N个App使用了WebView,并且这个N个App的进程都存在系统中,那么系统就需要为Chromium动态库分配(25.65 + 1.35 × N)M内存。这意味着,N越大,Chromium动态库就占用越多的系统内存。

       在Chromium动态库1.35M的程序数据中,大概有1.28M在加载后经过一次重定位操作之后就不会发生变化。这些数据包含C++虚函数表,以及所有指针类型的常量。它们会被链接器放在一个称为GNU_RELRO Section中,如图1所示:


图1 App进程间不共享GNU_RELRO Section

       如果我们将Chromium动态库的GNU_RELRO Section看成是普通的程序数据,那么Android系统就需要为每一个使用了WebView的App进程都分配1.28M的内存。这将会造成内存浪费。如果我们能App进程之间共享Chromium动态库的GNU_RELRO Section,那么不管有多少个App进程使用了WebView,它占用的内存大小都是1.28M,如图2所示:


图2 App进程间共享GNU_RELRO Section

       这是有可能做到的,毕竟它与程序代码类似,在运行期间都是只读的。不同的地方在于,程序代码在加载后自始至终都不用修改,而GNU_RELRO Section的内容在重定位期间,是需要进行一次修改的,之后才是只读的。但是修改的时候,Linux的COW(Copy On Write)机制就会使得它不能再在各个App进程之间实现共享。

       为了使得Chromium动态库能在不同的App进程之间共享,Android系统执行了以下操作:

       1. Zygote进程在启动的过程中,为Chromium动态库保留了一段足够加载它的虚拟地址内间。我们假设这个虚拟地址空间的起始地址为gReservedAddress,大小为gReservedSize。

       2. System进程在启动的过程中,会请求Zygote进程fork一个子进程,并且在上述保留的虚拟地址空间[gReservedAddress, gReservedAddress + gReservedSize)中加载Chromium动态库。Chromium动态库的Program Header指定了它的GNU_RELRO Section的加载位置。这个位置是相对基地址gReservedAddress的一个偏移量。我们假设这个偏移量为gRelroOffset。上述子进程完成Chromium动态库的GNU_RELRO Section的重定位操作之后,会将它的内容写入到一个RELRO文件中去。

       3. App进程在创建WebView的时候,Android系统也会在上述保留的虚拟地址空间[gReservedAddress, gReservedAddress + gReservedSize)中加载Chromium动态库,并且会直接将第2步得到的RELRO文件内存映射到虚拟地址空间[gReservedAddress + gRelroOffset,  gReservedAddress + gRelroOffset + 1.28)去。

       关于Zygote进程、System进程和App进程的启动过程,可以参考Android系统进程Zygote启动过程的源代码分析Android应用程序进程启动过程的源代码分析这两篇文章。

       Android 5.0的Linker提供了一个新的动态库加载函数android_dlopen_ext。这个函数不仅可以将一个动态库加载在指定的虚拟地址空间中,还可以在该动态库重定位操作完成后,将其GNU_RELRO Section的内容写入到指定的RELRO文件中去,同时还可以在加载一个动态库时,使用指定的RELRO文件内存映射为它的GNU_RELRO Section。因此,上述的第2步和第3步可以实现。函数android_dlopen_ext还有另外一个强大的功能,它可以从一个指定的文件描述符中读入要加载的动态库内容。通常我们是通过文件路径指定要加载的动态库,有了函数android_dlopen_ext之后,我们就不再受限于从本地文件加载动态库了。

       接下来,我们进一步分析为什么上述3个操作可以使得Chromium动态库能在不同的App进程之间共享。

       首先,第2步的子进程、第3步的App进程都是由第1步的Zygote进程fork出来的,因此它们都具有一段保留的虚拟地址空间[gReservedAddress, gReservedAddress + gReservedSize)。

       其次,第2步的子进程和第3步的App进程,都是将Chromium动态库加载在相同的虚拟地址空间中,因此,可以使用前者生成的RELRO文件内存映射为后者的GNU_RELRO Section。

       第三,所有使用了WebView的App进程,都将相同的RELRO文件内存映射为自己加载的Chromium动态库的GNU_RELRO Section,因此就实现了共享。

       App进程加载了Chromium动态库之后,就可以启动Chromium渲染引擎了。Chromium渲染引擎实际上是由Browser进程、Render进程和GPU进程组成的。其中,Browser进程负责将网页的UI合成在屏幕上,Render进程负责加载和渲染网页的UI,GPU进程负责执行Browser进程和Render进程发出的GPU命令。由于Chromium也支持单进程架构(在这种情况下,它的Browser进程、Render进程和GPU进程都是通过线程模拟的),因此接下来我们将它的Browser进程、Render进程和GPU进程统称为Browser端、Render端和GPU端。相应地,启动Chromium渲染引擎,就是要启动它的Browser端、Render端和GPU端。

       对于Android WebView来说,它启动Chromium渲染引擎的过程如图3所示:


图3 Android WebView启动Chromium渲染引擎的过程

       当我们在App的UI中嵌入一个WebView时,WebView内部会创建一个WebViewChromium对象。从名字就可以看出,WebViewChromium是基于Chromium实现的WebView。Chromium里面有一个android_webview模块。这个模块提供了两个类AwBrowserProcess和AwContents,分别用来封装Chromium的Content层提供的两个类BrowserStartupController和ContentViewCore。

       从前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文可以知道,通过Chromium的Content层提供的BrowserStartupController类,可以启动Chromium的Browser端。不过,对于Android WebView来说,它并没有单独的Browser进程,它的Browser端是通过App进程的主线程(即UI线程)实现的。      

       Chromium的Content层会通过BrowserStartupController类在App进程的UI线程中启动一个Browser Main Loop。以后需要请求Chromium的Browser端执行某一个操作时,就向这个Browser Main Loop发送一个Task即可。这个Browser Main Loop最终会在App进程的UI线程中执行请求的Task。

       从前面Chromium网页Frame Tree创建过程分析一文可以知道,通过Chromium的Content层提供的ContentViewCore类,可以创建一个Render进程,并且在这个Render进程中加载指定的网页。不过,对于Android WebView来说,它是一个单进程架构,也就是它没有单独的Render进程用来加载网页。这时候ContentViewCore类将会创建一个线程来模拟Render进程。也就是说,Android WebView的Render端是通过在App进程中创建的一个线程实现的。这个线程称为In-Process Renderer Thread。

       现在,Android WebView具有Browser端和Render端了,它还需要有一个GPU端。从前面Chromium的GPU进程启动过程分析一文可以知道,在Android平台上,Chromium的GPU端是通过在Browser进程中创建一个GPU线程实现的。不过,对于Android WebView来说,它并没有一个单独的GPU线程。那么,Chromium的GPU命令由谁来执行呢?

       我们知道,从Android 3.0开始,App的UI就支持硬件加速渲染了,也就是支持通过GPU来渲染。到了Android 4.0,App的UI默认就是硬件加速渲染了。这时候App的UI线程就是一个OpenGL线程,也就是它可以用来执行GPU命令。再到Android 5.0,App的UI线程只负责收集UI渲染操作,也就是构建一个Display List。保存在这个Display List中的操作,最终会交给一个Render Thread执行。Render Thread又是通过GPU来执行这些渲染操作的。这时候App的UI线程不再是一个OpenGL线程,Render Thread才是。Android WebView的GPU命令就是由这个Render Thread执行的。当然,在Android 4.4时,Android WebView的GPU命令还是由App的UI线程执行的。这里我们只讨论Android 5.0的情况,具体可以参考Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章。

       Chromium在android_webview模块中提供了一个DeferredGpuCommandService服务。当Android WebView的Render端需要通过GPU命令绘制网页UI时,它就会通过DeferredGpuCommandService服务提供的RequestProcessGL接口向App的Display List增加一个类型为DrawFunctorOp的操作。当Display List从UI线程同步给Render Thread的时候,它里面包含的DrawFunctorOp操作就会被执行。这时候Android WebView的Render端请求的GPU命令就会在App的Render Thread中得到执行了。

       Android WebView的Browser端在合成网页UI时,是运行在App的Render Thread中的。这时候它仍然是通过DeferredGpuCommandService服务提供的RequestProcessGL接口请求执行GPU命令。不过,DeferredGpuCommandService会检测到它当前运行在App的Render Thread中,因此,就会直接执行它请求的GPU命令。

       Chromium为Android WebView独特的GPU命令执行方式(既不是在单独的GPU进程中执行,也不是在单独的GPU线程中执行)提供了一个称为In-Process Command Buffer GL的接口。顾名思义,In-Process Command Buffer GL与Command Buffer GL接口一样,要执行的GPU命令都是先写入到一个Command Buffer中。然而,这个Command Buffer会提交给App的Render Thread进程执行,过程如图4所示:


图4 Android WebView通过In-Process Command Buffer GL接口执行GPU命令的过程

       In-Process Command Buffer GL接口与前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文分析的Command Buffer GL接口一样,都是通过GLES2Implementation类描述的,区别在于前者通过一个InProcessCommandBuffer对象执行GPU命令,而后者通过一个CommandBufferProxyImpl对象执行GPU命令。更具体来说,就是CommandBufferProxyImpl对象会将要执行的GPU命令提交给Chromium的GPU进程/线程处理,而InProcessCommandBuffer对象会将要执行的GPU命令提交给App的Render Thread处理。

       GLES2Implementation类是通过InProcessCommandBuffer类的成员函数Flush请求它处理已经写入在Command Buffer中的GPU命令的。InProcessCommandBuffer类的成员函数Flush又会请求前面提到的DeferredGpuCommandService服务调度一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread,意思就是说要在App的Render Thread线程中调用InProcessCommandBuffer类的成员函数FlushOnGpuThread。InProcessCommandBuffer类的成员函数FlushOnGpuThread在调用的过程中,就会执行已经写入在Command Buffer中的GPU命令。

       DeferredGpuCommandService服务接收到调度Task的请求之后,会判断当前线程是否允许执行GL操作,实际上就是判断当前线程是否就是App的Render Thread。如果是的话,那么就会直接调用请求调度的Task绑定的InProcessCommandBuffer类的成员函数FlushOnGpuThread。这种情况发生在Browser端合成网页UI的过程中。Browser端合成网页UI的操作是由App的Render Thread主动发起的,因此这个合成操作就可以在当前线程中直接执行。

       另一方面,DeferredGpuCommandService服务接收到调度Task的请求之后,如果发现当前不允许执行GL操作,那么它就会将该Task保存在内部一个Task队列中,并且通过调用Java层的DrawGLFunctor类的成员函数requestDrawGL请求App的UI线程调度执行一个GL操作。这种情况发生在两个场景中。

       第一个场景发生在前面描述的Render端绘制网页UI的过程中。绘制网页UI相当于就是绘制Android WebView的UI。Android WebView属于App的UI里面一部分,因此它的绘制操作是由App的UI线程发起的。Android WebView在App的UI线程中接收到绘制的请求之后,它就会要求Render端的Compositor线程绘制网页的UI。这个Compositor线程不是一个OpenGL线程,它不能直接执行GL的操作,因此就要请求App的UI线程调度执行GL操作。

       从前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章可以知道,App的UI线程主要是负责收集当前UI的渲染操作,并且通过一个DisplayListRenderer将这些渲染操作写入到一个Display List中去。这个Display List最终会同步到App的Render Thread中去。App的Render Thread获得了这个Display List之后,就会通过调用相应的OpenGL函数执行这些渲染操作,也就是通过GPU来执行这些渲染操作。这个过程称为Replay(重放) Display List。重放完成之后,就可以生成App的UI了。

       DrawGLFunctor类的成员函数requestDrawGL请求App的UI线程调度执行的GL操作是一个特殊的渲染操作,它对应的是一个GL函数,而一般的渲染操作是指绘制一个Circle、Rect或者Bitmap等基本操作。GL函数在执行期间,可以执行任意的GPU命令。这个GL操作封装在一个DrawGLFunctor对象。App的UI线程又会进一步将这个rawGLFunctor对象封装成一个DrawFunctionOp操作。这个DrawFunctionOp操作会通过DisplayListRenderer类的成员函数callDrawGLFunction写入到App UI的Display List中。当这个Display List被App的Render Thread重放时,它里面包含的DrawFunctionOp操作就会被OpenGLRenderer类的成员函数callDrawGLFunction执行。

       OpenGLRenderer类的成员函数callDrawGLFunction在执行DrawFunctionOp操作时,就会通知与它关联的DrawGLFunctor对象。这个DrawGLFunctor对象又会调用Chromium的android_webview模块提供的一个GL函数。这个GL函数又会找到一个HardwareRenderer对象。这个HardwareRenderer对象是Android WebView的Browser端用来合成网页的UI的。有了这个HardwareRenderer对象之后,上述GL函数就会调用它的成员函数DrawGL,以便请求前面提到的DeferredGpuCommandService服务执行保存在它内部的Task,这是通过调用它的成员函数PerformIdleTask实现的。

       DeferredGpuCommandService类的成员函数PerformIdleTask会依次执行保存在内部Task队列的每一个Task。从前面的分析可以知道,这时候Task队列中存在一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread。因此,这时候InProcessCommandBuffer类的成员函数FlushOnGpuThread就会在当前线程中被调用,也就是在App的Render Thread中被调用。

       前面提到,InProcessCommandBuffer类的成员函数FlushOnGpuThread在调用的过程中,会执行之前通过In-Process Command Buffer GL接口写入在Command Buffer中的GPU命令。InProcessCommandBuffer类的成员函数FlushOnGpuThread又是通过内部的一个GpuScheduler对象和一个GLES2Decoder对象执行这些GPU命令的。其中,GpuScheduler对象负责将GPU命令调度在它们对应的OpenGL上下文中执行,而GLES2Decoder对象负责将GPU命令翻译成OpenGL函数执行。关于GpuScheduler类和GLES2Decoder类执行GPU命令的过程,可以参考前面前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       第二个场景发生Render端光栅化网页UI的过程中。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,Render端通过GPU光栅化网页UI的操作也是发生在Compositor线程中的。这时候它请求App的Render Thread执行GPU命令的大概流程相同,也是将要执行的GPU操作封装在一个DrawGLFunctor对象中。不过,这个DrawGLFunctor对象不会进一步被封装成DrawFunctionOp操作写入到App UI的Display List中,而是直接被App的UI线程放入到App的Render Thread中,App的Render Thread再通知它执行所请求的GPU操作。DrawGLFunctor对象获得执行GPU操作的通知后,后流的流程就与前面描述的第一个场景一致了。

       通过以上描述的方式,Android WebView的Browser端和Render端就可以执行任意的GPU命令了。其中,Render端执行GPU命令是为了绘制网页UI,而Browser端执行GPU命令是为了将Render端渲染出来的网页UI合成在App的UI中,以便最后可以显示在屏幕中。这个过程就是Android WebView硬件加速渲染网页的过程,如图5所示:


图5 Android WebView硬件加速渲染网页过程

       从前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章可以知道,App从接收到VSync信号开始渲染一帧UI。两个VSync信号时间间隔由屏幕刷新频率决定。一般的屏幕刷新频率是60fps,这意味着每一个VSync信号到来时,App有16ms的时间绘制自己的UI。

       这16ms时间又划分为三个阶段。第一阶段用来构造App UI的Display List。这个构造工作由App的UI线程执行,表现为各个需要更新的View的成员函数onDraw会被调用。第二阶段App的UI线程会将前面构造的Display List同步给Render Thread。这个同步操作由App的Render Thread执行,同时App的UI线程会被阻塞,直到同步操作完成为止。第三阶段是绘制App UI的Display List。这个绘制操作由App的Render Thread执行。

       由于Android WebView是嵌入在App的UI里面的,因此它每一帧的渲染也是按照上述三个阶段进行的。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,网页的UI最终是通过一个CC Layer Tree描述的,也就是Android WebView的Render端会创建一个CC Layer Tree来描述它正在加载的网页的UI。与此同时,Android WebView还会为Render端创建一个Synchronous Compositor,用来将网页的UI渲染在一个Synchronous Compositor Output Surface上。渲染得到的最终结果通过一个Compositor Frame描述。这意味网页的每一帧都是通过一个Compositor Frame描述的。

       Android WebView的Browser端负责合成Render端渲染出来的UI。为了完成这个合成操作,它同样会创建一个CC Layer Tree。这个CC Layer Tree只有两个节点,一个是根节点,另外一个是根节点的子节点,称为Delegated Renderer Layer。这个Delegated Renderer Layer的内容来自于Render端的渲染结果,也就是一个Compositor Frame。与此同时,Android WebView会为Browser端创建一个Hardware Renderer,用来将它的CC Layer Tree渲染在一个Parent Output Surface上,实际上就是将网页的UI合成在App的窗口上。

       在第一阶段,Android WebView的成员函数onDraw会在App的UI线程中被调用。在调用期间,它又会调用为Render端创建的Synchronous Compositor的成员函数DemandDrawHw,用来渲染Render端的CC Layer Tree。渲染完成后,为Render端创建的Synchronous Compositor Output Surface的成员函数SwapBuffers就会被调用,并且交给它一个Compositor Frame。这个Compositor Frame描述的就是网页当前的UI,它最终会被保存在一个SharedRendererState对象中。这个SharedRendererState对象是用来描述网页的状态的。

       在第二阶段,保存在上述SharedRendererState对象中的Compositor Frame会被提取出来,并且同步给Browser端的CC Layer Tree,也就是设置为该CC Layer Tree的Delegated Renderer Layer的内容。

       在第三阶段,Android WebView为Browser端创建的Hardware Renderer的成员函数DrawGL会在App的Render Thread中被调用,用来渲染Browser端的CC Layer Tree。渲染完成后,为Browser端创建的Parent Output Surface的成员函数SwapBuffers就会被调用,这时候它就会将得到的网页UI绘制在App的窗口上。

       以上就是Android WebView以硬件加速方式将网页UI渲染在App窗口的过程。当然,Android WebView也可用软件方式将网页UI渲染在App窗口。不过,在这个系列的文章中,我们只要关注硬件加速渲染方式,因为这种渲染方式效率会更高,而且Android系统从4.0版本之后,默认已经是使用硬件加速方式渲染App的UI了。

       接下来,我们就结合源码,按照以下四个情景,详细分析Android WebView硬件加速渲染网页UI的过程:

       1. Android WebView加载Chromium动态库的过程

       2. Android WebView启动Chromium渲染引擎的过程

       3. Android WebView执行OpenGL命令的过程

       4. Android WebView硬件加速渲染网页UI的过程

       分析了这四个情景之后,我们就可以对Android WebView的实现有深刻的认识了。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/11/21 0:59:03 原文链接
阅读:43785 评论:16 查看评论

Android WebView加载Chromium动态库的过程分析

$
0
0

       Chromium动态库的体积比较大,有27M左右,其中程序段和数据段分别占据25.65M和1.35M。如果按照通常方式加载Chromium动态库,那么当有N个正在运行的App使用WebView时,系统需要为Chromium动态库分配的内存为(25.65 + N x 1.35)M。这是非常可观的。为此,Android使用了特殊的方式加载Chromium动态库。本文接下来就详细分析这种特殊的加载方式。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       为什么当有N个正在运行的App使用WebView时,系统需要为Chromium动态库分配的内存为(25.65 + N x 1.35)M呢?这是由于动态库的程序段是只读的,可以在多个进程之间进行共享,但是数据段一般是可读可写的,不能共享。在1.35M的数据段中,有1.28M在Chromium动态库加载完成后就是只读的。这1.28M数据包含有C++虚函数表,以及指针类型的常量等,它们在编译的时候会放在一个称为GNU_RELRO的Section中,如图1所示:


图1 App进程间不共享GNU_RELRO Section

       如果我们将该GNU_RELRO Section看作是一般的数据段,那么系统就需要为每一个使用了WebView的App进程都分配一段1.28M大小的内存空间。前面说到,这1.28M数据在Chromium动态库加载完成后就是只读的,那么有没有办法让它像程序段一样,在多个App进程之间共享呢?

       只要能满足一个条件,那么答案就是肯定的。这个条件就是所有的App进程都在相同的虚拟地址空间加载Chromium动态库。在这种情况下,就可以在系统启动的过程中,创建一个临时进程,并且在这个进程中加载Chromium动态库。假设Chromium动态库的加载地址为Base Address。加载完成后,将Chromium动态库的GNU_RELRO Section的内容Dump出来,并且写入到一个文件中去。

       以后App进程加载Chromium动态库时,都将Chromium动态库加载地址Base Address上,并且使用内存映射的方式将前面Dump出来的GNU_RELRO文件代替Chromium动态库的GNU_RELRO Section。这样就可以实现在多个App进程之间共享Chromium动态库的GNU_RELRO Section了,如图2所示:


图2 App进程间共享GNU_RELRO Section

       从前面Android应用程序进程启动过程的源代码分析一文可以知道,所有的App进程都是由Zygote进程fork出来的,因此,要让所有的App进程都在相同的虚拟地址空间加载Chromium动态库的最佳方法就是在Zygote进程的地址空间中预留一块地址。

       还有两个问题需要解决。第一个问题是要将加载后的Chromium动态库的GNU_RELRO Section的内容Dump出来,并且写入到一个文件中去。第二个问题是要让所有的App进程将Chromium动态加载在指定位置,并且可以使用指定的文件来代替它的GNU_RELRO Section。这两个问题都可以通过Android在5.0版本提供的一个动态库加载函数android_dlopen_ext解决。

       接下来,我们就结合源码,分析Zygote进程是如何为App进程预留地址加载Chromium动态库的,以及App进程如何使用新的动态库加载函数android_dlopen_ext加载Chromium动态库的。

       从前面Android系统进程Zygote启动过程的源代码分析一文可以知道,Zygote进程在Java层的入口函数为ZygoteInit类的静态成员函数main,它的实现如下所示:

public class ZygoteInit {
    ......

    public static void main(String argv[]) {
        try {
            .......

            preload();
            .......

            if (startSystemServer) {
                startSystemServer(abiList, socketName);
            }

            ......
            runSelectLoop(abiList);

            ......
        } catch (MethodAndArgsCaller caller) {
            ......
        } catch (RuntimeException ex) {
            ......
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

       ZygoteInit类的静态成员函数main在启动System进程以及使得Zygote进程进入运行状态之前,首先会调用另外一个静态成员函数preload预加载资源。这些预加载的资源以后就可以在App进程之间进行共享。

       ZygoteInit类的静态成员函数preload在预加载资源的过程中,就会为Chromium动态库预保留加载地址,如下所示:

public class ZygoteInit {
    ......

    static void preload() {
        ......
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        ......
    }

    ......
}

       这个函数定义在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

       ZygoteInit类的静态成员函数preload是通过调用WebViewFactory类的静态成员函数prepareWebViewInZygote为Chromium动态库预保留加载地址的,后者的实现如下所示:

public final class WebViewFactory {
    ......

    public static void prepareWebViewInZygote() {
        try {
            System.loadLibrary("webviewchromium_loader");
            long addressSpaceToReserve =
                    SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                    CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
            sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);

            ......
        } catch (Throwable t) {
            ......
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数prepareWebViewInZygote首先会加载一个名称为“webviewchromium_loader”的动态库,接下来又会获得需要为Chromium动态库预留的地址空间大小addressSpaceToReserve。知道了要预留的地址空间的大小之后,WebViewFactory类的静态成员函数prepareWebViewInZygote就会调用另外一个静态成员函数nativeReserveAddressSpace为Chromium动态库预留地址空间。

       WebViewFactory类的静态成员函数nativeReserveAddressSpace是一个JNI方法,它在C++层对应的函数为ReserveAddressSpace。这个函数实现在上述名称为“webviewchromium_loader”的动态库中,如下所示:

jboolean ReserveAddressSpace(JNIEnv*, jclass, jlong size) {
  return DoReserveAddressSpace(size);
}
       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       函数ReserveAddressSpace调用另外一个函数DoReserveAddressSpace预留大小为size的地址空间,如下所示:

void* gReservedAddress = NULL;
size_t gReservedSize = 0;

jboolean DoReserveAddressSpace(jlong size) {
  size_t vsize = static_cast<size_t>(size);

  void* addr = mmap(NULL, vsize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  ......

  gReservedAddress = addr;
  gReservedSize = vsize;
  ......

  return JNI_TRUE;
}
       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       函数DoReserveAddressSpace是通过系统接口mmap预留指定大小的地址空间的。同时,预留出来的地址空间的起始地址和大小分别记录在全局变量gReservedAddress和gReservedSize中。以后Chromium动态库就可以加载在该地址空间中。

       为Chromium动态库预留好加载地址之后,Android系统接下来要做的一件事情就是请求Zygote进程启动一个临时进程。这个临时进程将会在上述预留的地址空间中加载Chromium动态库。这个Chromium动态库在加载的过程中,它的GNU_RELRO Section会被重定位。重定位完成之后,就可以将它的内容Dump到一个文件中去。这个文件以后就会以内存映射的方式代替在App进程中加载的Chromium动态库的GNU_RELRO Section。

       请求Zygote进程启动临时进程的操作是由System进程完成的。从前面Android系统进程Zygote启动过程的源代码分析一文可以知道,System进程是由Zygote进程启动的,它在Java层的入口函数为SystemServer的静态成员函数main,它的实现如下所示:

public final class SystemServer {
    ......

    public static void main(String[] args) {
        new SystemServer().run();
    }

    ......
}
      这个函数定义在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

      SystemServer的静态成员函数main首先是创建一个SystemServer对象,然后调用这个SystemServer对象的成员函数run在System进程中启动各种系统服务,如下所示:

public final class SystemServer {
    ......

    private void run() {
        ......

        // Start services.
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            ......
        }

        ......

        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    ......
}

       这个函数定义在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

       SystemServer类的成员函数run首先会调用成员函数startBootstrapServices启动Bootstrap类型的系统服务。这些系统服务包括Activity Manager Service、Power Manager Service、Package Manager Service和Display Manager Service等。

       SystemServer类的成员函数run接下来又会调用成员函数startCoreServices启动Core类型的系统服务。这些系统服务包括Lights Service、Battery Service和Usage Stats Service等。

       SystemServer类的成员函数run再接下来还会调用成员函数startOtherServices启动其它的系统服务。这些其它的系统服务包括Account Manager Service、Network Management Service和Window Manager Service等。

       SystemServer类的成员函数startOtherServices启动完成其它的系统服务之后,就会创建一个Runnable。这个Runnable会在Activity Manger Service启动完成后执行,如下所示:

public final class SystemServer {
    ......

    private void startOtherServices() {
        final Context context = mSystemContext;
        AccountManagerService accountManager = null;
        ContentService contentService = null;
        VibratorService vibrator = null;
        IAlarmManager alarm = null;
        MountService mountService = null;
        NetworkManagementService networkManagement = null;
        NetworkStatsService networkStats = null;
        NetworkPolicyManagerService networkPolicy = null;
        ConnectivityService connectivity = null;
        NetworkScoreService networkScore = null;
        NsdService serviceDiscovery= null;
        WindowManagerService wm = null;
        BluetoothManagerService bluetooth = null;
        UsbService usb = null;
        SerialService serial = null;
        NetworkTimeUpdateService networkTimeUpdater = null;
        CommonTimeManagementService commonTimeMgmtService = null;
        InputManagerService inputManager = null;
        TelephonyRegistry telephonyRegistry = null;
        ConsumerIrService consumerIr = null;
        AudioService audioService = null;
        MmsServiceBroker mmsService = null;
        ......

       mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
                ......

                WebViewFactory.prepareWebViewInSystemServer();

                ......
            }
        });
    }

    ......
}
       这个函数定义在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

       这个Runnable在执行的时候,会调用WebViewFactory类的静态成员函数prepareWebViewInSystemServer请求Zygote进程启动一个临时的进程,用来加载Chromium动态库,并且将完成重定位操作后的GNU_RELRO Section的内容Dump到一个文件中去。

       WebViewFactory类的静态成员函数prepareWebViewInSystemServer的实现如下所示:

public final class WebViewFactory {
    ......

    public static void prepareWebViewInSystemServer() {
        String[] nativePaths = null;
        try {
            nativePaths = getWebViewNativeLibraryPaths();
        } catch (Throwable t) {
            // Log and discard errors at this stage as we must not crash the system server.
            Log.e(LOGTAG, "error preparing webview native library", t);
        }
        prepareWebViewInSystemServer(nativePaths);
    }

    ......
}

       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数prepareWebViewInSystemServer首先调用另外一个静态成员函数getWebViewNativeLibraryPaths获得Chromium动态库的文件路径,如下所示:

public final class WebViewFactory {
    ......

    private static String[] getWebViewNativeLibraryPaths()
            throws PackageManager.NameNotFoundException {
        final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";

        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
        ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);

        String path32;
        String path64;
        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
            // Multi-arch case.
            if (primaryArchIs64bit) {
                // Primary arch: 64-bit, secondary: 32-bit.
                path64 = ai.nativeLibraryDir;
                path32 = ai.secondaryNativeLibraryDir;
            } else {
                // Primary arch: 32-bit, secondary: 64-bit.
                path64 = ai.secondaryNativeLibraryDir;
                path32 = ai.nativeLibraryDir;
            }
        } else if (primaryArchIs64bit) {
            // Single-arch 64-bit.
            path64 = ai.nativeLibraryDir;
            path32 = "";
        } else {
            // Single-arch 32-bit.
            path32 = ai.nativeLibraryDir;
            path64 = "";
        }
        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
        return new String[] { path32, path64 };
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       Android系统将Chromium动态库打包在一个WebView Package中。这个WebView Package也是一个APK,它的Package Name可以通过调用WebViewFactory类的静态成员函数getWebViewPackageName获得,如下所示:

public final class WebViewFactory {
    ......

    public static String getWebViewPackageName() {
        return AppGlobals.getInitialApplication().getString(
                com.android.internal.R.string.config_webViewPackageName);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数getWebViewPackageName又是通过系统字符串资源com.android.internal.R.string.config_webViewPackageName获得WebView Package的Package Name。

       系统字符串资源com.android.internal.R.string.config_webViewPackageName的定义如下所示:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
  ......

  <!-- Package name providing WebView implementation. -->
    <string name="config_webViewPackageName" translatable="false">com.android.webview</string>

  ......
</resources>
       这个字符串资源定义在文件frameworks/base/core/res/res/values/config.xml中。

       从这里就可以看到,WebView Package这个APK的Package Name定义为"com.android.webview"。通过查找源码,可以发现,这个APK实现在目录frameworks/webview/chromium中。

       回到WebViewFactory类的静态成员函数getWebViewNativeLibraryPaths中,它知道了WebView Package这个APK的Package Name之后,就可以通过Package Manager Service获得它的安装信息。Package Manager Service负责安装系统上所有的APK,因此通过它可以获得任意一个APK的安装信息。关于Package Manager Service,可以参考Android应用程序安装过程源代码分析这篇文章。

       获得了WebView Package这个APK的安装信息之后,就可以知道它用来保存动态库的目录。Chromium动态库就是保存在这个目录下的。因此,WebViewFactory类的静态成员函数getWebViewNativeLibraryPaths最终可以获得Chromium动态库的文件路径。注意,获得Chromium动态库的文件路径可能有两个。一个是32位版本的,另外一个是64位版本的。

       为什么要将Chromium动态库打包在一个WebView Package中呢?而不是像其它的系统动态库一样,直接放在/system/lib目录下。原因为了方便以后升级WebView。例如,Chromium有了新的版本之后,就可以将它打包在一个更高版本的WebView Package中。当用户升级WebView Package的时候,就获得了基于新版本Chromium实现的WebView了。

       这一步执行完成后,回到WebViewFactory类的静态成员函数prepareWebViewInSystemServer中,这时候它就获得了Chromium动态库的文件路径。接下来,它继续调用另外一个静态成员函数prepareWebViewInSystemServer请求Zygote进程启动一个临时的进程,用来加载Chromium动态库,以便将完成重定位操作后的GNU_RELRO Section的内容Dump到一个文件中去。

       WebViewFactory类的静态成员函数prepareWebViewInSystemServer的实现如下所示:

public final class WebViewFactory {

    ......

    private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
        if (DEBUG) Log.v(LOGTAG, "creating relro files");

        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
        // unexpected values will be handled there to ensure that we trigger notifying any process
        // waiting on relreo creation.
        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
            createRelroFile(false /* is64Bit */, nativeLibraryPaths);
        }

        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
            createRelroFile(true /* is64Bit */, nativeLibraryPaths);
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数prepareWebViewInSystemServer根据系统对32位和64位的支持情况,调用另外一个静态成员函数createRelroFile相应地创建32位和64位的Chromium GNU_RELRO Section文件。

       WebViewFactory类的静态成员函数createRelroFile的实现如下所示:

public final class WebViewFactory {
    ......

    private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
        ......

        try {
            ......

            int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
                    RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
                    Process.SHARED_RELRO_UID, crashHandler);
            ......
        } catch (Throwable t) {
            ......
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数createRelroFile请求系统的Activity Manager Service启动一个Isolated Process。这个Isolated Process的启动过程与App进程是一样的,只不过它是一个被隔离的进程,没有自己的权限。

       从前面Android应用程序进程启动过程的源代码分析一文可以知道,App进程最终是由Zygote进程fork出来的,并且它在Java层的入口函数为ActivityThread类的静态成员函数main。这个入口函数是可以指定的。WebViewFactory类的静态成员函数createRelroFile将请求启动的Isolated Process的入口函数指定为RelroFileCreator类的静态成员函数main。

       这意味着,当上述Isolated Process启动起来之后,RelroFileCreator类的静态成员函数main就会被调用,如下所示:

public final class WebViewFactory {
    ......

    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
            "/data/misc/shared_relro/libwebviewchromium32.relro";
    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
            "/data/misc/shared_relro/libwebviewchromium64.relro";
    ......

    private static class RelroFileCreator {
        // Called in an unprivileged child process to create the relro file.
        public static void main(String[] args) {
            ......
            try{
                ......
                result = nativeCreateRelroFile(args[0] /* path32 */,
                                               args[1] /* path64 */,
                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
                ......
            } finally {
                ......
            }
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       前面获得的Chromium动态库文件路径会通过参数args传递进来。有了Chromium动态库文件路径之后,RelroFileCreator类的静态成员函数main就会调用另外一个静态成员函数nativeCreateRelroFile对它进行加载。加载完成后,RelroFileCreator类的静态成员函数nativeCreateRelroFile会将Chromium动态库已经完成重定位操作的Chromium GNU_RELRO Section写入到指定的文件中去。对于32位的Chromium动态库来说,它的GNU_RELRO Section会被写入到文件/data/misc/shared_relro/libwebviewchromium32.relro中,而对于64位的Chromium动态库来说,它的GNU_RELRO Section会被写入到文件/data/misc/shared_relro/libwebviewchromium64.relro中。

       RelroFileCreator类的静态成员函数nativeCreateRelroFile是一个JNI方法,它由C++层的函数CreateRelroFile实现,如下所示:

jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64,
                         jstring relro32, jstring relro64) {
#ifdef __LP64__
  jstring lib = lib64;
  jstring relro = relro64;
  (void)lib32; (void)relro32;
#else
  jstring lib = lib32;
  jstring relro = relro32;
  (void)lib64; (void)relro64;
#endif
  jboolean ret = JNI_FALSE;
  const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
  if (lib_utf8 != NULL) {
    const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
    if (relro_utf8 != NULL) {
      ret = DoCreateRelroFile(lib_utf8, relro_utf8);
      env->ReleaseStringUTFChars(relro, relro_utf8);
    }
    env->ReleaseStringUTFChars(lib, lib_utf8);
  }
  return ret;
}
       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       函数CreateRelroFile判断自己是32位还是64位的实现,然后从参数lib32和lib64中选择对应的Chromium动态库进行加载。这个加载过程是通过调用另外一个函数DoCreateRelroFile实现的,如下所示:

void* gReservedAddress = NULL;
size_t gReservedSize = 0;

......

jboolean DoCreateRelroFile(const char* lib, const char* relro) {
  ......

  static const char tmpsuffix[] = ".XXXXXX";
  char relro_tmp[strlen(relro) + sizeof(tmpsuffix)];
  strlcpy(relro_tmp, relro, sizeof(relro_tmp));
  strlcat(relro_tmp, tmpsuffix, sizeof(relro_tmp));
  int tmp_fd = TEMP_FAILURE_RETRY(mkstemp(relro_tmp));
  ......

  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO;
  extinfo.reserved_addr = gReservedAddress;
  extinfo.reserved_size = gReservedSize;
  extinfo.relro_fd = tmp_fd;
  void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
  int close_result = close(tmp_fd);
  ......

  if (close_result != 0 ||
      chmod(relro_tmp, S_IRUSR | S_IRGRP | S_IROTH) != 0 ||
      rename(relro_tmp, relro) != 0) {
    ......
    return JNI_FALSE;
  }

  return JNI_TRUE;
}

       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       参数lib描述的是要加载的Chromium动态库的文件路径,另外一个参数relro描述的是要生成的Chromium GNU_RELRO Section文件路径。

       我们假设要加载的Chromium动态库是32位的。函数DoCreateRelroFile的执行过程如下所示:

       1.  创建一个临时的mium GNU_RELRO Section文件,文件路径为”/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX“,并且会找开这个临时文件,获得一个文件描述符tmp_fd。

       2. 创建一个android_dlextinfo结构体。这个android_dlextinfo结构体会指定一个ANDROID_DLEXT_RESERVED_ADDRESS标志,以及一个Reserved地址gReservedAddress,表示要将Chromium动态库加载在全局变量gReservedAddress描述的地址中。从前面的分析可以知道,Zygote进程为Chromium动态库预留的地址空间的起始地址就保存在全局变量gReservedAddress中。由于当前进程是由Zygote进程fork出来的,因此它同样会获得Zygote进程预留的地址空间。

       3. 前面创建的android_dlextinfo结构体,同时还会指定一个ANDROID_DLEXT_WRITE_RELRO标专,以及一个Relro文件描述符tmp_fd,表示在加载完成Chromium动态库后,将完成重定位操作后的GNU_RELRO Section写入到指定的文件中去,也就是写入到上述的临时文件/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX中去。

       4. 调用Linker导出的函数android_dlopen_ext按照上述两点规则加载Chromium动态库。加载完成后,临时文件/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX将被重新命名为”/data/misc/shared_relro/libwebviewchromium32.relro“。

       这样,我们得就到一个Chromium GNU_RELRO Section文件。这个Chromium GNU_RELRO Section文件在App进程加载Chromium动态库时就会使用到。接下来我们就继续分析App进程加载Chromium动态库的过程。

       当App使用了WebView的时候,系统就为会其加载Chromium动态库。这个加载过程是从创建WebView对象的时候发起的。因此,接下来我们就从WebView类的构造函数开始分析App进程加载Chromium动态库的过程,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
        ......

        ensureProviderCreated();
        mProvider.init(javaScriptInterfaces, privateBrowsing);

        ......
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebView.java中。

       WebView类的构造函数会调用另外一个成员函数ensureProviderCreated确保Chromium动态库已经加载。在Chromium动态库已经加载的情况下,WebView类的成员函数ensureProviderCreated还会创建一个WebView Provider,并且保存保存在成员变量mProvider中。这个WebView Provider才是真正用来实现WebView的功能的。

      有了这个WebView Provider之后,WebView类的构造函数就会调用它的成员函数init启动网页渲染引擎。对于基于Chromium实现的WebView来说,它使用的WebView Provider是一个WebViewChromium对象。当这个WebViewChromium对象的成员函数init被调用的时候,它就会启动Chromium的网页渲染引擎。这个过程我们在接下来的一篇文章再详细分析。

       接下来,我们继续分析WebView类的成员函数ensureProviderCreated的实现,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    private void ensureProviderCreated() {
        checkThread();
        if (mProvider == null) {
            // As this can get called during the base class constructor chain, pass the minimum
            // number of dependencies here; the rest are deferred to init().
            mProvider = getFactory().createWebView(this, new PrivateAccess());
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebView.java中。

       WebView类的成员函数ensureProviderCreated首先调用成员函数checkThread确保它是在WebView的创建线程中调用的,接下来又会判断成员变量mProvider的值是否为null。如果为null,就表示它还没有当前创建的WebView创建过Provider。在这种情况下,它首先会调用成员函数getFactory获得一个WebView Factory。有了这个WebView Factory之后,就可以调用它的成员函数createWebView创建一个WebView Provider。

       WebView类的成员函数getFactory的实现如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    private static synchronized WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebView.java中。

       WebView类的成员函数getFactory返回的WebView Factory是通过调用WebViewFactory类的静态成员函数getProvider获得的,如下所示:

public final class WebViewFactory {
    ......

    private static WebViewFactoryProvider sProviderInstance;
    ......

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;

            ......
            try {
                ......
                loadNativeLibrary();
                ......

                Class<WebViewFactoryProvider> providerClass;
                ......
                try {
                    providerClass = getFactoryClass();
                } catch (ClassNotFoundException e) {
                    ......
                } finally {
                    ......
                }

                ......
                try {
                    sProviderInstance = providerClass.newInstance();
                    ......
                    return sProviderInstance;
                } catch (Exception e) {
                    .......
                } finally {
                    ......
                }
            } finally {
                ......
            }
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数getProvider首先是判断静态成员变量sProviderInstance的值是否等于null。如果等于null,那么就说明当前的App进程还没有加载过Chromium动态库。在这种情况下,就需要加载Chromium动态库,并且创建一个WebView Factory,保存在静态成员变量sProviderInstance。接下来我们就先分析Chromium动态库的加载过程,然后再分析WebView Factory的创建过程。

       加载Chromium动态库是通过调用WebViewFactory类的静态成员函数loadNativeLibrary实现的,如下所示: 

public final class WebViewFactory {
    ......

    private static void loadNativeLibrary() {
        ......

        try {
            String[] args = getWebViewNativeLibraryPaths();
            boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
                                                     args[1] /* path64 */,
                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
            ......
        } catch (PackageManager.NameNotFoundException e) {
            ......
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       WebViewFactory类的静态成员函数loadNativeLibrary首先会调用我们前面分析过的成员函数getWebViewNativeLibraryPaths获得要加载的Chromium动态库的文件路径,然后再调用另外一个静态成员函数nativeLoadWithRelroFile对它进行加载。在加载的时候,会指定一个Chromium GNU_RELRO Section文件。这个Chromium GNU_RELRO Section文件就是前面通过启动一个临时进程生成的。

       WebViewFactory类的静态成员函数nativeLoadWithRelroFile是一个JNI方法,它由C++层的函数LoadWithRelroFile实现,如下所示:

jboolean LoadWithRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64,
                           jstring relro32, jstring relro64) {
#ifdef __LP64__
  jstring lib = lib64;
  jstring relro = relro64;
  (void)lib32; (void)relro32;
#else
  jstring lib = lib32;
  jstring relro = relro32;
  (void)lib64; (void)relro64;
#endif
  jboolean ret = JNI_FALSE;
  const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
  if (lib_utf8 != NULL) {
    const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
    if (relro_utf8 != NULL) {
      ret = DoLoadWithRelroFile(lib_utf8, relro_utf8);
      env->ReleaseStringUTFChars(relro, relro_utf8);
    }
    env->ReleaseStringUTFChars(lib, lib_utf8);
  }
  return ret;
}
       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       函数LoadWithRelroFile判断自己是32位还是64位的实现,然后从参数lib32和lib64中选择对应的Chromium动态库进行加载。这个加载过程是通过调用另外一个函数DoLoadWithRelroFile实现的,如下所示:

jboolean DoLoadWithRelroFile(const char* lib, const char* relro) {
  int relro_fd = TEMP_FAILURE_RETRY(open(relro, O_RDONLY));
  ......

  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_USE_RELRO;
  extinfo.reserved_addr = gReservedAddress;
  extinfo.reserved_size = gReservedSize;
  extinfo.relro_fd = relro_fd;
  void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
  close(relro_fd);
  ......

  return JNI_TRUE;
}
       这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。

       函数DoLoadWithRelroFile的实现与前面分析的函数DoCreateRelroFile类似,都是通过Linker导出的函数android_dlopen_ext在Zyogote进程保留的地址空间中加载Chromium动态库的。注意,App进程是Zygote进程fork出来的,因此它同样会获得Zygote进程预留的地址空间。

       不过,函数DoLoadWithRelroFile会将告诉函数android_dlopen_ext在加载Chromium动态库的时候,将参数relro描述的Chromium GNU_RELRO Section文件内存映射到内存来,并且代替掉已经加载的Chromium动态库的GNU_RELRO Section。这是通过将指定一个ANDROID_DLEXT_USE_RELRO标志实现的。

       之所以可以这样做,是因为参数relro描述的Chromium GNU_RELRO Section文件对应的Chromium动态库的加载地址与当前App进程加载的Chromium动态库的地址一致。只要两个相同的动态库在两个不同的进程中的加载地址一致,它们的链接和重定位信息就是完全一致的,因此就可以通过文件内存映射的方式进行共享。共享之后,就可以达到节省内存的目的了。

        这一步执行完成之后,App进程就加载完成Chromium动态库了。回到前面分析的WebViewFactory类的静态成员函数getProvider,它接下来继续创建一个WebView Factory。这个WebView Factory以后就可以用来创建WebView Provider。

        WebViewFactory类的静态成员函数getProvider首先要确定要创建的WebView Factory的类型。这个类型是通过调用另外一个静态成员函数getFactoryClass获得的,如下所示:

public final class WebViewFactory {

    private static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProvider";

    ......

    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
        Application initialApplication = AppGlobals.getInitialApplication();
        try {
            // First fetch the package info so we can log the webview package version.
            String packageName = getWebViewPackageName();
            sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
            ......

            // Construct a package context to load the Java code into the current app.
            Context webViewContext = initialApplication.createPackageContext(packageName,
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            ......

            ClassLoader clazzLoader = webViewContext.getClassLoader();
            ......
            try {
                return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
                                                                     clazzLoader);
            } finally {
                ......
            }
        } catch (PackageManager.NameNotFoundException e) {
            ......
        }
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

       从这里可以看到,WebViewFactory类的静态成员函数getFactoryClass返回的WebView Factory的类型为com.android.webview.chromium.WebViewChromiumFactoryProvider。这个com.android.webview.chromium.WebViewChromiumFactoryProvider类是由前面提到的WebView Package提供的。

       这意味着WebViewFactory类的静态成员函数getProvider创建的WebView Factory是一个WebViewChromiumFactoryProvider对象。这个WebViewChromiumFactoryProvider的创建过程如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    public WebViewChromiumFactoryProvider() {
        ......

        AwBrowserProcess.loadLibrary();

        .......
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       WebViewChromiumFactoryProvider类的构造函数会调用AwBrowserProcess类的静态成员函数loadLibrary对前面加载的Chromium动态库进行初始化,如下所示:

public abstract class AwBrowserProcess {
    ......

    public static void loadLibrary() {
        ......
        try {
            LibraryLoader.loadNow();
        } catch (ProcessInitException e) {
            throw new RuntimeException("Cannot load WebView", e);
        }
    }

    ......
}
        这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java中。

        AwBrowserProcess类的静态成员函数loadLibrary又调用LibraryLoader类的静态成员函数loadNow对前面加载的Chromium动态库进行初始化,如下所示:

public class LibraryLoader {
    ......

    public static void loadNow() throws ProcessInitException {
        loadNow(null, false);
    }

    ......
}
        这个函数定义在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

        LibraryLoader类的静态成员函数loadNow又调用另外一个重载版本的静态成员函数loadNow对前面加载的Chromium动态库进行初始化,如下所示:

public class LibraryLoader {
    ......

    public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
            throws ProcessInitException {
        synchronized (sLock) {
            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
        }
    }

    ......
}
       这个函数定义在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

       LibraryLoader类重载版本的静态成员函数loadNow又调用另外一个静态成员函数loadAlreadyLocked对前面加载的Chromium动态库进行初始化,如下所示:

public class LibraryLoader {
    ......

    // One-way switch becomes true when the libraries are loaded.
    private static boolean sLoaded = false;
    ......

    private static void loadAlreadyLocked(
            Context context, boolean shouldDeleteOldWorkaroundLibraries)
            throws ProcessInitException {
        try {
            if (!sLoaded) {
                ......

                boolean useChromiumLinker = Linker.isUsed();

                if (useChromiumLinker) Linker.prepareLibraryLoad();

                for (String library : NativeLibraries.LIBRARIES) {
                    Log.i(TAG, "Loading: " + library);
                    if (useChromiumLinker) {
                        Linker.loadLibrary(library);
                    } else {
                        try {
                            System.loadLibrary(library);
                        } catch (UnsatisfiedLinkError e) {
                            ......
                        }
                    }
                }
                if (useChromiumLinker) Linker.finishLibraryLoad();

                ......
                sLoaded = true;
            }
        } catch (UnsatisfiedLinkError e) {
            ......
        }
        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

       由于并不是所有的系统都支持在加载动态库时,以文件内存映射的方式代替它的GNU_RELRO Section,因此Chromium自己提供了一个Linker。通过这个Linker加载动态库时,能够以文件内存映射的方式代替要加载的动态库的GNU_RELRO Section,也就是实现前面提到的函数android_dlopen_ext的功能。     

       在Android 5.0中,由于系统已经提供了函数android_dlopen_ext,因此,Chromium就不会使用自己的Linker加载动态库,而是使用Android系统提供的Linker来加载动态库。通过调用System类的静态成员函数loadLibrary即可以使用系统提供的Linker来加载动态库。

       LibraryLoader类的静态成员函数loadAlreadyLocked要加载的动态库由NativeLibraries类的静态成员变量LIBRARIES指定,如下所示:

public class NativeLibraries {
    ......

    static final String[] LIBRARIES = { "webviewchromium" };
    
    ......
}
      这个静态成员变量定义在文件external/chromium_org/android_webview/java/generated_src/org/chromium/base/library_loader/NativeLibraries.java中。

      从这里可以知道,LibraryLoader类的静态成员函数loadAlreadyLocked要加载的动态库就是Chromium动态库。这个Chromium动态库前面已经加载过了,因此这里通过调用System类的静态成员函数loadLibrary再加载时,仅仅是只会触发它导出的函数JNI_OnLoad被调用,而不会重新被加载。

      Chromium动态库导出的JNI_OnLoad被调用的时候,Chromium动态库就会执行初始化工作,如下所示:

JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  ......

  content::SetContentMainDelegate(new android_webview::AwMainDelegate());
  ......

  return JNI_VERSION_1_4;
}
       这个函数定义在文件external/chromium_org/android_webview/lib/main/webview_entry_point.cc中。

       其中的一个初始化操作是给Chromium的Content层设置一个类型为AwMainDelegate的Main Delegate。这个AwMainDelegate实现在Chromium的android_webview模块中。Android WebView是通过Chromium的android_webview模块加载和渲染网页的。Chromium加载和渲染网页的功能又是实现在Content层的,因此,Chromium的android_webview模块又要通过Content层实现加载和渲染网页功能。这样,Chromium的android_webview模块就可以设置一个Main Delegate给Content层,以便它们可以互相通信。

       给Chromium的Content层设置一个Main Delegate是通过调用函数SetContentMainDelegate实现的,它的实现如下所示:

LazyInstance<scoped_ptr<ContentMainDelegate> > g_content_main_delegate =
    LAZY_INSTANCE_INITIALIZER;

......

void SetContentMainDelegate(ContentMainDelegate* delegate) {
  DCHECK(!g_content_main_delegate.Get().get());
  g_content_main_delegate.Get().reset(delegate);
}
       这个函数定义在文件external/chromium_org/content/app/android/content_main.cc中。

       从前面的分析可以知道,参数delegate指向的是一个AwMainDelegate对象,这个AwMainDelegate对象会被函数SetContentMainDelegate保存在全局变量g_content_main_delegate中。在接下来一篇文章中分析Chromium渲染引擎的启动过程时,我们就会看到这个AwMainDelegate对象的作用。

       这一步执行完成后,Chromium动态库就在App进程中加载完毕,并且也已经完成了初始化工作。与此同时,系统也为App进程创建了一个类型为WebViewChromiumFactoryProvider的WebView Factory。回到前面分析的WebView类的成员函数ensureProviderCreated中,这时候就它会通过调用上述类型为WebViewChromiumFactoryProvider的WebView Factory的成员函数createWebView为当前创建的WebView创建一个WebView Provider,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);

        ......

        return wvc;
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       WebViewChromiumFactoryProvider类的成员函数createWebView创建的是一个类型为WebViewChromium的WebView Provider。这个WebView Provider将会返回给WebView类的成员函数ensureProviderCreated。WebView类的成员函数ensureProviderCreated再将该WebView Provider保存在成员变量mProvider中。

       这样,正在创建的WebView就获得了一个类型为WebViewChromium的WebView Provider。以后通过这个WebView Provider,就可以通过Chromium来加载和渲染网页了。

       至此,我们也分析完成了Android WebView加载Chromium动态库的过程。在接下来的一篇文章中,我们继续分析Android WebView启动Chromium渲染引擎的过程。Chromium渲染引擎启动起来之后,Android WebView就可以通过它来加载和渲染网页了。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/11/28 1:00:19 原文链接
阅读:40020 评论:4 查看评论

Android WebView启动Chromium渲染引擎的过程分析

$
0
0

       Android WebView加载了Chromium动态库之后,就可以启动Chromium渲染引擎了。Chromium渲染引擎由Browser、Render和GPU三端组成。其中,Browser端负责将网页UI合成在屏幕上,Render端负责加载网页的URL和渲染网页的UI,GPU端负责执行Browser端和Render端请求的GPU命令。本文接下来详细分析Chromium渲染引擎三端的启动过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       Android WebView使用了单进程架构的Chromium来加载和渲染网页,因此它的Browser端、Render端和GPU端都不是以进程的形式存在的,而是以线程的形式存在。其中,Browser端实现在App的UI线程中,Render端实现在一个独立的线程中,而GPU端实现在App的Render Thread中。注意,这是针对Android 5.0及以上版本的。Android在4.4版本引入基于Chromium实现的WebView,那时候GPU端与Browser一样,都是实现在App的UI线程中。接下来我们只讨论Android WebView在Android 5.0及以上版本的实现。

       Android WebView启动Chromium渲染引擎三端的过程如图1所示:


图1 Android WebView启动Chromium渲染引擎的过程

       从前面Android WebView加载Chromium动态库的过程分析一文可以知道,当我们在App的UI中嵌入一个WebView时,WebView会在内部创建一个类型为WebViewChromium的Provider。Android WebView就是通过这个Provider来启动和使用Chromium渲染引擎的。

       Chromium里面有一个android_webview模块。这个模块提供了两个类AwBrowserProcess和AwContents,分别用来封装Chromium的Content层提供的两个接口类BrowserStartupController和ContentViewCore,它们分别用来启动Chromium的Browser端和Render端。

       Android WebView启动Chromium的Browser端,实际上就是在App的UI线程创建一个Browser Main Loop。Chromium以后需要请求Browser端执行某一个操作时,就可以向这个Browser Main Loop发送一个Task。这个Task最终会在App进程的UI线程中调度执行。

       Android WebView启动Chromium的Render端,实际上就是在当前的App进程中创建一个线程。以后网页就由这个线程负责加载和渲染。这个线程称为In-Process Renderer Thread。

       由于Chromium的GPU端实现在App的Render Thread中,这个Render Thread是由App负责启动的,因此Chromium无需启动它。不过,Chromium里的android_webview模块会启动一个DeferredGpuCommandService服务。当Chromium的Browser端和Render端需要执行GPU操作时,就会向DeferredGpuCommandService服务发出请求。这时候DeferredGpuCommandService服务又会通过App的UI线程将请求的GPU操作提交给App的Render Thread执行。这一点可以参考前面Android WebView简要介绍和学习计划一文的描述。我们在接下来的一篇文章也会对Chromium的Browser端和Render端执行GPU操作的过程进行详细的分析。

       接下来我们就结合源码,分析Android WebView启动Chromium的Browser端和Render端的过程。对于GPU端,我们仅仅分析与它相关的DeferredGpuCommandService服务的启动过程。在接下来一篇文章分析Android WebView执行GPU命令的过程时,我们再对GPU端进行更详细的分析。

       我们首先分析Android WebView启动Chromium的Browser端的过程。前面提到,WebView会在内部创建一个类型为WebViewChromium的Provider。有了这个Provider之后,WebView就可以调用它的成员函数init启动Chromium的Browser端,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    public void init(final Map<String, Object> javaScriptInterfaces,
            final boolean privateBrowsing) {
        ......

        // We will defer real initialization until we know which thread to do it on, unless:
        // - we are on the main thread already (common case),
        // - the app is targeting >= JB MR2, in which case checkThread enforces that all usage
        //   comes from a single thread. (Note in JB MR2 this exception was in WebView.java).
        if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            mFactory.startYourEngines(false);
            checkThread();
        } else if (!mFactory.hasStarted()) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                mFactory.startYourEngines(true);
            }
        }

        ......

        mRunQueue.addTask(new Runnable() {
                @Override
                public void run() {
                    initForReal();
                    ......
                }
        });
    }

    ......
}
      这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

      WebViewChromium类的成员变量mFactory指向的是一个WebViewChromiumFactoryProvider对象。WebViewChromium类的成员函数init通过调用这个WebViewChromiumFactoryProvider对象的成员函数startYourEngines启动Chromium渲染引擎的Browser端。

       在Android 4.3之前,WebView只能在App的UI线程中创建。相应地,WebView也只能在App的UI线程中启动Chromium渲染引擎的Browser端。这时候WebViewChromium类的成员函数init会传递一个参数true给WebViewChromiumFactoryProvider类的成员函数startYourEngines,表示如果当前线程如果不是UI线程,那么就需要向UI线程发出一个通知,让UI线程执行启动Chromium渲染引擎的Browser端的操作。

       在Android 4.3及以后,WebView也允许在App的非UI线程中创建。这时候WebView允行在App的非UI线程中启动Chromium渲染引擎的Browser端。因此,WebViewChromium类的成员函数init就会传递一个参数false给WebViewChromiumFactoryProvider类的成员函数startYourEngines。

       一般情况下,WebView都是在App的UI线程中创建的。为了简单起见,我们只考虑这种情况。WebViewChromium类的成员函数init调用WebViewChromiumFactoryProvider类的成员函数startYourEngines启动了Chromium渲染引擎的Browser端之后,接下来还会向App的UI线程的消息队列发送一个Runnable。当该Runnable被执行的时候,它就会调用WebViewChromium类的成员函数initForReal创建图1所示的AwContents对象。有了这个AwContents对象之后,后面就可以通过它来加载指定的URL了。

       接下来,我们首先分析WebViewChromiumFactoryProvider类的成员函数startYourEngines启动Chromium渲染引擎的Browser端的过程,然后再分析WebViewChromium类的成员函数initForReal为WebView创建AwContents对象的过程。

       WebViewChromiumFactoryProvider类的成员函数startYourEngines的实现如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    void startYourEngines(boolean onMainThread) {
        synchronized (mLock) {
            ensureChromiumStartedLocked(onMainThread);

        }
    }

    ......
}

       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       WebViewChromiumFactoryProvider类的成员函数startYourEngines调用另外一个成员函数ensureChromiumStartedLocked检查Chromium渲染引擎的Browser端是否已经启动。如果还没有启动,那么就会进行启动,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    private void ensureChromiumStartedLocked(boolean onMainThread) {
        ......

        if (mStarted) {  // Early-out for the common case.
            return;
        }

        Looper looper = !onMainThread ? Looper.myLooper() : Looper.getMainLooper();
        ......
        ThreadUtils.setUiThread(looper);

        if (ThreadUtils.runningOnUiThread()) {
            startChromiumLocked();
            return;
        }

        // We must post to the UI thread to cover the case that the user has invoked Chromium
        // startup by using the (thread-safe) CookieManager rather than creating a WebView.
        ThreadUtils.postOnUiThread(new Runnable() {
            @Override
            public void run() {
                synchronized (mLock) {
                    startChromiumLocked();
                }
            }
        });
        while (!mStarted) {
            try {
                // Important: wait() releases |mLock| the UI thread can take it :-)
                mLock.wait();
            } catch (InterruptedException e) {
                // Keep trying... eventually the UI thread will process the task we sent it.
            }
        }
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       如果Chromium渲染引擎的Browser端已经启动,那么WebViewChromiumFactoryProvider类的成员变量mStarted的值就会等于true。在这种情况下,WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked什么也不用做就可以返回。

       另一方面,如果Chromium渲染引擎的Browser端还没有启动,那么WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked首先会根据参数onMainThread确定Chromium渲染引擎的Browser端要在哪个线程中运行。

       当参数onMainThread的值等于true的时候,就表示Chromium渲染引擎的Browser端要在App的UI线程中运行。这时候如果当前线程不是App的UI线程,那么WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked就会向App的UI线程的消息队列发送一个Runnable。当该Runnable被执行的时候,才会启动Chromium渲染引擎的Browser端。在这种情况下,当前线程也会等待App的UI线程启动完成Chromium渲染引擎的Browser端。

       当参数onMainThread的值等于true的时候,如果当前线程刚好也是App的UI线程,那么WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked就可以马上启动Chromium渲染引擎的Browser端。

       当参数onMainThread的值等于false的时候,不管当前线程是否App的UI线程,都表示Chromium渲染引擎的Browser端要在它里面运行。因此,这时候WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked都会马上启动Chromium渲染引擎的Browser端。

       无论是上述的哪一种情况,用来运行Chromium渲染引擎的Browser端的线程都会通过调用ThreadUtils类的静态成员函数setUiThread记录起来。以后WebView都需要在该线程中访问Chromium渲染引擎。

       WebViewChromiumFactoryProvider类的成员函数ensureChromiumStartedLocked是通过调用另外一个成员函数startChromiumLocked启动Chromium渲染引擎的Browser端的,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    private void startChromiumLocked() {
        ......

        AwBrowserProcess.start(ActivityThread.currentApplication());

        ......
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       WebViewChromiumFactoryProvider类的成员函数startChromiumLocked通过调用AwBrowserProcess类的静态成员函数start启动Chromium渲染引擎的Browser端的,如下所示:

public abstract class AwBrowserProcess {
    ......

    public static void start(final Context context) {
        // We must post to the UI thread to cover the case that the user
        // has invoked Chromium startup by using the (thread-safe)
        // CookieManager rather than creating a WebView.
        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
            @Override
            public void run() {
                try {
                    BrowserStartupController.get(context).startBrowserProcessesSync(
                                BrowserStartupController.MAX_RENDERERS_SINGLE_PROCESS);
                    ......
                } catch (ProcessInitException e) {
                    ......
                }
            }
        });
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java中。

       前面提到,用来运行Chromium渲染引擎的Browser端的线程会通过ThreadUtils类的静态成员函数setUiThread记录起来。AwBrowserProcess类的静态成员函数start为了确保Chromium渲染引擎的Browser端在该线程中启动,会通过调用ThreadUtils类的静态成员函数runOnUiThreadBlocking检查当前线程是否就是该线程。如果是的话,那么就会直接启动。否则的话,会向该线程的消息队列发送一个Runnable。当该Runnable被执行的时候,再启动Chromium渲染引擎的Browser端。

       AwBrowserProcess类的静态成员函数start是通过调用当前App进程中的一个BrowserStartupController单例对象的成员函数startBrowserProcessesSync来启动Chromium渲染引擎的Browser端的。这个BrowserStartupController单例对象可以通过调用BrowserStartupController类的静态成员函数get获得。

       AwBrowserProcess类的静态成员函数start在启动Chromium渲染引擎的Browser端的时候,会指定一个BrowserStartupController.MAX_RENDERERS_SINGLE_PROCESS参数。这个参数的值等于0,表示要启动一个单进程架构的Chromium渲染引擎。

       接下来,我们就继续分析Chromium渲染引擎的Browser端的启动过程,也就是BrowserStartupController类的成员函数startBrowserProcessesSync的实现,如下所示:

public class BrowserStartupController {
    ......

    public void startBrowserProcessesSync(int maxRenderers) throws ProcessInitException {
        // If already started skip to checking the result
        if (!mStartupDone) {
            if (!mHasStartedInitializingBrowserProcess) {
                prepareToStartBrowserProcess(maxRenderers);
            }

            ......
            if (contentStart() > 0) {
                // Failed. The callbacks may not have run, so run them.
                enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
            }
        }

        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java中。

       当BrowserStartupController类的成员变量mStartupDone的值等于true的时候,就表示Chromium渲染引擎的Browser端已经启动了。这时候BrowserStartupController类的成员函数startBrowserProcessesSync就什么也不做就直接返回。

       另一方面,如果Chromium渲染引擎的Browser端还没有启动。这时候BrowserStartupController类的成员函数startBrowserProcessesSync就会调用另外一个成员函数contentStart进行启动。

       在启动Chromium渲染引擎的Browser端之前,BrowserStartupController类的成员函数startBrowserProcessesSync也会检查成员变量mHasStartedInitializingBrowserProcess的值。当这个值等于false的时候,就会先调用成员函数prepareToStartBrowserProcess设置Chromium渲染引擎的启动参数。其中,最重要的就是将Chromium渲染引擎设置为单进程架构。

       接下来,我们先分析将Chromium渲染引擎设置为单进程架构的过程,也就是BrowserStartupController类的成员函数prepareToStartBrowserProcess的实现,然后再分析启动Chromium渲染引擎的Browser端的过程,也就是BrowserStartupController类的成员函数contentStart的实现。

       BrowserStartupController类的成员函数prepareToStartBrowserProcess的实现如下所示:

public class BrowserStartupController {
    ......

    void prepareToStartBrowserProcess(int maxRendererProcesses) throws ProcessInitException {
        ......

        nativeSetCommandLineFlags(maxRendererProcesses,
                nativeIsPluginEnabled() ? getPlugins() : null);
        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java中。

       BrowserStartupController类的成员函数prepareToStartBrowserProcess调用另外一个成员函数nativeSetCommandLineFlags将Chromium渲染引擎设置为单进程架构。

       BrowserStartupController类的成员函数nativeSetCommandLineFlags是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_BrowserStartupController_nativeSetCommandLineFlags实现,如下所示:

__attribute__((visibility("default")))
void
    Java_com_android_org_chromium_content_browser_BrowserStartupController_nativeSetCommandLineFlags(JNIEnv*
    env, jclass jcaller,
    jint maxRenderProcesses,
    jstring pluginDescriptor) {
  return SetCommandLineFlags(env, jcaller, maxRenderProcesses,
      pluginDescriptor);
}
       这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/BrowserStartupController_jni.h中。

       函数Java_com_android_org_chromium_content_browser_BrowserStartupController_nativeSetCommandLineFlags调用另外一个函数SetCommandLineFlags将Chromium渲染引擎设置为单进程架构,如下所示:

static void SetCommandLineFlags(JNIEnv* env,
                                jclass clazz,
                                jint max_render_process_count,
                                jstring plugin_descriptor) {
  std::string plugin_str =
      (plugin_descriptor == NULL
           ? std::string()
           : base::android::ConvertJavaStringToUTF8(env, plugin_descriptor));
  SetContentCommandLineFlags(max_render_process_count, plugin_str);
}
       这个函数定义在文件external/chromium_org/content/browser/android/browser_startup_controller.cc中。

       函数SetCommandLineFlags又会调用另外一个函数SetContentCommandLineFlags将Chromium渲染引擎设置为单进程架构,如下所示:

void SetContentCommandLineFlags(int max_render_process_count,
                                const std::string& plugin_descriptor) {
  ......

  CommandLine* parsed_command_line = CommandLine::ForCurrentProcess();
  
  int command_line_renderer_limit = -1;
  if (parsed_command_line->HasSwitch(switches::kRendererProcessLimit)) {
    std::string limit = parsed_command_line->GetSwitchValueASCII(
        switches::kRendererProcessLimit);
    int value;
    if (base::StringToInt(limit, &value)) {
      command_line_renderer_limit = value;
      if (value <= 0)
        max_render_process_count = 0;
    }
  }

  if (command_line_renderer_limit > 0) {
    int limit = std::min(command_line_renderer_limit,
                         static_cast<int>(kMaxRendererProcessCount));
    RenderProcessHost::SetMaxRendererProcessCount(limit);
  } else if (max_render_process_count <= 0) {
    // Need to ensure the command line flag is consistent as a lot of chrome
    // internal code checks this directly, but it wouldn't normally get set when
    // we are implementing an embedded WebView.
    parsed_command_line->AppendSwitch(switches::kSingleProcess);
  } else {
    int default_maximum = RenderProcessHost::GetMaxRendererProcessCount();
    DCHECK(default_maximum <= static_cast<int>(kMaxRendererProcessCount));
    if (max_render_process_count < default_maximum)
      RenderProcessHost::SetMaxRendererProcessCount(max_render_process_count);
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/android/content_startup_flags.cc中。

       函数SetContentCommandLineFlags首先检查Android WebView是否设置了switches::kRendererProcessLimit命令行参数。如果设置了,那么这个参数的值就会被解析出来,保存在本地变量command_line_renderer_limit中,用来限定Chromium渲染引擎最多可创建的Render进程的个数的。

       Chromium渲染引擎最多可创建的Render进程的个数还受到参数max_render_process_count的限制:

       1. 当本地变量command_line_renderer_limit的值大于0的时候,那么取max_render_process_count和command_line_renderer_limit之间的较小者作为最多可创建的Render进程的个数。

       2. 当本地变量command_line_renderer_limit的值小于等于0,并且参数max_render_process_count的值也小于等于0的时候,那么Chromium渲染引擎不允许创建Render进程,也就是它使用的是单进程架构。

       3. 当本地变量command_line_renderer_limit的值小于等于0,并且参数max_render_process_count的值大于0的时候,会调用RenderProcessHost类的静态成员函数GetMaxRendererProcessCount根据设备内存的大小计算出可以创建的Render进程的最大数default_maximum。如果参数max_render_process_count的值小于这个最大值,那么就将它设置为可以创建的Render进程的个数。

       在我们这个情景中,Android WebView没有设置switches::kRendererProcessLimit命令行参数,并且参数max_render_process_count的值等于0,因此函数SetContentCommandLineFlags会将Chromium渲染引擎设置为单进程架构。这是通过在Android WebView的命令行参数中设置一个switches::kSingleProcess选项实现的。

       这一步执行完成后,回到前面分析的BrowserStartupController类的成员函数startBrowserProcessesSync中,接下来它会调用另外一个成员函数contentStart启动Chromium渲染引擎的Browser端,如下所示:

public class BrowserStartupController {
    ......

    int contentStart() {
        return ContentMain.start();
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java中。

       BrowserStartupController类的成员函数contentStart调用ContentMain类的静态成员函数Start启动Chromium渲染引擎的Browser端,如下所示:

public class ContentMain {
    ......

    public static int start() {
        return nativeStart();
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ContentMain.java中。

       ContentMain类的静态成员函数Start调用另外一个静态成员函数nativeStart启动Chromium渲染引擎的Browser端。

       ContentMain类的静态成员函数nativeStart是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart实现,如下所示:

__attribute__((visibility("default")))
jint Java_com_android_org_chromium_content_app_ContentMain_nativeStart(JNIEnv*
    env, jclass jcaller) {
  return Start(env, jcaller);
}
        这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentMain_jni.h中。

        函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart调用另外一个函数Start启动Chromium渲染引擎的Browser端,如下所示:

LazyInstance<scoped_ptr<ContentMainRunner> > g_content_runner =
    LAZY_INSTANCE_INITIALIZER;

LazyInstance<scoped_ptr<ContentMainDelegate> > g_content_main_delegate =
    LAZY_INSTANCE_INITIALIZER;

......

static jint Start(JNIEnv* env, jclass clazz) {
  ......

  if (!g_content_runner.Get().get()) {
    ContentMainParams params(g_content_main_delegate.Get().get());
    g_content_runner.Get().reset(ContentMainRunner::Create());
    g_content_runner.Get()->Initialize(params);
  }
  return g_content_runner.Get()->Run();
}
       这个函数定义在文件external/chromium_org/content/app/android/content_main.cc中。

       函数Start判断全局变量g_content_runner是否已经指向了一个ContentMainRunner对象。如果还没有指向,那么就说明Chromium渲染引擎的Browser端还没有启动。在这种情况下,函数Start就会调用ContentMainRunner类的静态成员函数Create创建一个ContentMainRunner对象,并且保存在全局变量g_content_runner中。

       ContentMainRunner类的静态成员函数Create的实现如下所示:

ContentMainRunner* ContentMainRunner::Create() {
  return new ContentMainRunnerImpl();
}
       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       从这里可以看到,ContentMainRunner类的静态成员函数Create实际创建的是一个ContentMainRunnerImpl对象。这个ContentMainRunnerImpl返回给函数Start之后,它的成员函数Initialize就会被调用,用来对它进行初始化。

       从前面Android WebView加载Chromium动态库的过程分析一文可以知道,全局变量g_content_main_delegate指向的是一个AwMainDelegate对象。这个AwMainDelegate对象将会封装在一个ContentMainParams对象中,并且传递给前面创建的ContentMainRunnerImpl对象的成员函数Initialize,以便后者用来执行初始化工作。

        ContentMainRunnerImpl类的成员函数Initialize的实现如下所示:

class ContentMainRunnerImpl : public ContentMainRunner {
 public:
  ......

  virtual int Initialize(const ContentMainParams& params) OVERRIDE {
    ......

    delegate_ = params.delegate;
    ......

    int exit_code;
    if (delegate_ && delegate_->BasicStartupComplete(&exit_code))
      return exit_code;
    ......
    
    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
    std::string process_type =
        command_line.GetSwitchValueASCII(switches::kProcessType);

    ......

    ContentClientInitializer::Set(process_type, delegate_);

    ......
}
       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       参数params描述的ContentMainParams对象的成员变量delegate指向的是就是前面描述的全局变量g_content_main_delegate指向的AwMainDelegate对象。ContentMainRunnerImpl类的成员函数Initialize会将这个AwMainDelegate对象保存在成员变量delegate_中,并且会调用这个AwMainDelegate对象的成员函数BasicStartupComplete执行一些基本的初始化工作,如下所示:

bool AwMainDelegate::BasicStartupComplete(int* exit_code) {
  content::SetContentClient(&content_client_);
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/lib/main/aw_main_delegate.cc中。

       AwMainDelegate类的成员变量content_client_描述的是一个AwContentClient对象。这个AwContentClient对象将会设置给Chromium的Content层。这个通过调用函数SetContentClient实现的,如下所示:

static ContentClient* g_client;

......

void SetContentClient(ContentClient* client) {
  g_client = client;
  ......
}

ContentClient* GetContentClient() {
  return g_client;
}
       这个函数定义在文件external/chromium_org/content/public/common/content_client.cc中。

       函数SetContentClient将参数client指向的一个AwContentClient对象保存在全局变量g_client中。这个AwContentClient对象以后可以通过调用函数GetContentClient获得。

       这一步执行完成后,回到前面分析的ContentMainRunnerImpl类的成员函数Initialize的中,它接下来检查Android WebView是否设置了switches::kProcessType命令行参数。如果设置了,那么该参数值process_type描述的就是当前启动的进程的类型(Browser端、Render端或者GPU端)。如果没有设置,那么参数值process_type就会等于一个空字符串,表示当前要启动的是一个Browser端。

       在我们这个情景中,Android WebView没有设置switches::kProcessType,因此得到的参数值process_type就等于一个空字符串。这个空字符串,连同ContentMainRunnerImpl类的成员变量delegate_指向的AwMainDelegate对象,会进一步传递给ContentClientInitializer类的静态成员函数Set执行初始化操作,如下所示:

class ContentClientInitializer {
 public:
  static void Set(const std::string& process_type,
                  ContentMainDelegate* delegate) {
    ContentClient* content_client = GetContentClient();
    if (process_type.empty()) {
      if (delegate)
        content_client->browser_ = delegate->CreateContentBrowserClient();
      ......
    }

    ......
  }
 
  ......
};

      这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

      ContentClientInitializer类的静态成员函数Set首先是调用前面提到的函数GetContentClient获得一个AwContentClient对象,接下来判断参数process_type的值是否等于一个空字符串。如果等于的话,那么就会调用参数delegate指向的一个AwMainDelegate对象的成员函数CreateContentBrowserClient创建一个ContentBrowserClient对象,并且保存在前面获得的AwContentClient对象的成员变量browser_中。

      从前面的分析可以知道,参数process_type的值等于一个空字符串,因此接下来ContentClientInitializer类的静态成员函数Set就会调用参数delegate指向的AwMainDelegate对象的成员函数CreateContentBrowserClient创建一个ContentBrowserClient对象,如下所示:

content::ContentBrowserClient*
    AwMainDelegate::CreateContentBrowserClient() {
  content_browser_client_.reset(new AwContentBrowserClient(this));
  return content_browser_client_.get();
}
       这个函数定义在文件external/chromium_org/android_webview/lib/main/aw_main_delegate.cc中。

       AwMainDelegate类的成员函数CreateContentBrowserClient实际创建的是一个AwContentBrowserClient对象。这个AwContentBrowserClient对象是从ContentBrowserClient类继承下来的。

       这意味着前面设置到Conent层的一个AwContentClient对象的成员变量browser_指向的是一个AwContentBrowserClient对象。这个AwContentBrowserClient对象在接下来启动Chromium渲染引擎的Browser端过程中会使用到。

       这一步执行完成后,回到前面分析的函数Start中。这时候它就创建了一个ContentMainRunner对象,并且对这个ContentMainRunner对象进行初始化。接下来,函数Start继续调用这个ContentMainRunner对象的成员函数Run,以便启动Chromium渲染引擎的Browser端,如下所示:

class ContentMainRunnerImpl : public ContentMainRunner {
 public:
  ......

  virtual int Run() OVERRIDE {
    ......
    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
    std::string process_type =
          command_line.GetSwitchValueASCII(switches::kProcessType);

    MainFunctionParams main_params(command_line);
    ......

#if !defined(OS_IOS)
    return RunNamedProcessTypeMain(process_type, main_params, delegate_);
#else
    return 1;
#endif
  }

  ......
};
       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       ContentMainRunner类的成员函数Run首先获得Android WebView设置的命令行参数switches::kProcessType的值。前面提到,Android WebView没有设置命令行参数switches::kProcessType,因此这里获得的值为一个空字符串,也就是本地变量process_type的值等于一个空字符串。

       接下来,ContentMainRunner类的成员函数Run还会将Android WebView设置的命令行参数封装在一个MainFunctionParams对象中。这个MainFunctionParams对象,连同前面设置的本地变量process_type,以及ContentMainRunner类的成员变量delegate_指向的一个AwMainDelegate对象,会传递给另外一个函数RunNamedProcessTypeMain。这个函数将会负责启动Chromium渲染引擎的Browser端,如下所示:

   const MainFunctionParams& main_function_params,
    ContentMainDelegate* delegate) {
  static const MainFunction kMainFunctions[] = {
#if !defined(CHROME_MULTIPLE_DLL_CHILD)
    { "",                            BrowserMain },
#endif
    ......
    { switches::kRendererProcess,    RendererMain },
    { switches::kGpuProcess,         GpuMain }, 
    ......
  };

  RegisterMainThreadFactories();

  for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {
    if (process_type == kMainFunctions[i].name) {
      if (delegate) {
        int exit_code = delegate->RunProcess(process_type,
            main_function_params);
#if defined(OS_ANDROID)
        // In Android's browser process, the negative exit code doesn't mean the
        // default behavior should be used as the UI message loop is managed by
        // the Java and the browser process's default behavior is always
        // overridden.
        if (process_type.empty())
          return exit_code;
#endif
        if (exit_code >= 0)
          return exit_code;
      }
      return kMainFunctions[i].function(main_function_params);
    }
  }

  ......
}

       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       函数RunNamedProcessTypeMain定义了一个MainFunction数组。这个MainFunction数组用来指定不同类型的进程的入口函数。其中,Browser进程、Render进程和GPU进程对应的入口函数分别为BrowserMain、RendererMain和GpuMain。当然,只有在参数delegate的值等于NULL的情况下,这个MainFunction数组才会生效。否则的话,所有进程的入口函数都为该参数指向的ContentMainDelegate对象的成员函数RunProcess。对于非Browser进程,如果参数delegate指向的ContentMainDelegate对象的成员函数RunProcess的返回值小于0,那么上述MainFunction数组也会同样生效。

       从前面的调用过程可以知道,参数process_type的值是一个空字符串,表示函数RunNamedProcessTypeMain需要启动的是一个Chromium渲染引擎的Browser进程(端)。这时候由于另外一个参数delegate指向了一个AwMainDelegate对象,因此,函数RunNamedProcessTypeMain将调用这个AwMainDelegate对象的成员函数RunProcess启动Chromium渲染引擎的Browser端。

       函数RunNamedProcessTypeMain在调用参数delegate指向的AwMainDelegate对象的成员函数RunProcess启动Chromium渲染引擎的Browser端之前,还会调用函数RegisterMainThreadFactories注册一些线程创建工厂函数,如下所示:

static void RegisterMainThreadFactories() {
#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
  ......
  RenderProcessHostImpl::RegisterRendererMainThreadFactory(
      CreateInProcessRendererThread);
  ......
#else
  ......
#endif
}
      这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

      其中的一个线程创建工厂函数是Render线程创建工厂函数,它被指定为函数CreateInProcessRendererThread,并且会通过调用RenderProcessHostImpl类的静态成员函数RegisterRendererMainThreadFactory记录起来,如下所示:

RendererMainThreadFactoryFunction g_renderer_main_thread_factory = NULL;

......

void RenderProcessHostImpl::RegisterRendererMainThreadFactory(
    RendererMainThreadFactoryFunction create) {
  g_renderer_main_thread_factory = create;
}
       这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

       参数create描述的函数CreateInProcessRendererThread将会保存在全局变量g_renderer_main_thread_factory中。以后Chromium渲染引擎的Browser端将会通过这个函数创建In-Process Renderer Thread,以便用来加载和渲染指定的URL。

       这一步执行完成后,回到前面分析的函数RunNamedProcessTypeMain,接下来它就会调用参数delegate指向的AwMainDelegate对象的成员函数RunProcess启动Chromium渲染引擎的Browser端,如下所示:

int AwMainDelegate::RunProcess(
    const std::string& process_type,
    const content::MainFunctionParams& main_function_params) {
  if (process_type.empty()) {
    ......

    browser_runner_.reset(content::BrowserMainRunner::Create());
    int exit_code = browser_runner_->Initialize(main_function_params);
    ......

    return 0;
  }

  return -1;
}
      这个函数定义在文件external/chromium_org/android_webview/lib/main/aw_main_delegate.cc中。

      从前面的调用过程可以知道,参数process_type的值等于一个空字符串。在这种情况下,AwMainDelegate类的成员函数RunProcess会调用BrowserMainRunner类的静态成员函数Create创建一个BrowserMainRunner对象,并且会保存在成员变量browser_runner_中,如下所示:

BrowserMainRunner* BrowserMainRunner::Create() {
  return new BrowserMainRunnerImpl();
}
      这个函数定义在文件external/chromium_org/content/browser/browser_main_runner.cc中。

      从这里可以看到,BrowserMainRunner类的静态成员函数Create创建的实际上是一个BrowserMainRunnerImpl对象。这意味着AwMainDelegate类的成员变量browser_runner_指向的是一个BrowserMainRunnerImpl对象。这个BrowserMainRunnerImpl对象的成员函数Initialize接下来会被调用。在调用的过程中,就会将Chromium渲染引擎的Browser端启动起来,如下所示:

class BrowserMainRunnerImpl : public BrowserMainRunner {
 public:
  ......

  virtual int Initialize(const MainFunctionParams& parameters) OVERRIDE {
    ......

    if (!initialization_started_) {
      initialization_started_ = true;
      ......

      main_loop_.reset(new BrowserMainLoop(parameters));

      main_loop_->Init();

      main_loop_->EarlyInitialization();

      ......

      main_loop_->MainMessageLoopStart();

      ......
    }

    main_loop_->CreateStartupTasks();
    int result_code = main_loop_->GetResultCode();
    if (result_code > 0)
      return result_code;

    // Return -1 to indicate no early termination.
    return -1;
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_runner.cc中。

       BrowserMainRunnerImpl类的成员函数Initialize首先检查成员变量initialization_started_的值是否等于true。如果等于true,那么就说明Chromium渲染引擎的Browser端已经启动过。在这种情况下,BrowserMainRunnerImpl类的成员函数Initialize只会创建一些Startup Task。

       如果Chromium渲染引擎的Browser端还没有启动过,那么BrowserMainRunnerImpl类的成员函数Initialize首先就会创建一个BrowserMainLoop对象,并且保存在成员变量main_loop_中。接下来,BrowserMainRunnerImpl类的成员函数Initialize会调用上述BrowserMainLoop对象的成员函数Init和EarlyInitialization对其进行初始化。初始化完成后,它的成员函数MainMessageLoopStart又会被调用。调用完成后,Chromium渲染引擎的Browser端就启动完成了。启动完成后,上述BrowserMainLoop对象的成员函数CreateStartupTasks也会被调用,用来创建一些Startup Task。

       接下来,我们就分别分析BrowserMainLoop类的成员函数Init、EarlyInitialization、MainMessageLoopStart和CreateStartupTasks的实现,以便了解Chromium渲染引擎的Browser端的启动过程。

       BrowserMainLoop类的成员函数Init用来创建一个BrowserMainParts对象,它的实现如下所示:

void BrowserMainLoop::Init() {
  ......
  parts_.reset(
      GetContentClient()->browser()->CreateBrowserMainParts(parameters_));
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       BrowserMainLoop类的成员函数Init首先调用前面提到的函数GetContentClient获得一个AwContentClient对象,接下来又会调用这个AwContentClient对象的成员函数browser获得它的成员变量browser_指向的一个AwContentBrowserClient对象。获得了这个AwContentBrowserClient对象之后,就可以调用它的成员函数CreateBrowserMainParts创建一个BrowserMainParts对象,并且保存在BrowserMainLoop类的成员变量parts_中,如下所示:

content::BrowserMainParts* AwContentBrowserClient::CreateBrowserMainParts(
    const content::MainFunctionParams& parameters) {
  return new AwBrowserMainParts(browser_context_.get());
}
       这个函数定义在文件external/chromium_org/android_webview/browser/aw_content_browser_client.cc中。

       从这里可以看出,AwContentBrowserClient类的成员函数CreateBrowserMainParts创建的实际上是一个AwBrowserMainParts对象。这个AwBrowserMainParts对象接下来会用来创建一个Native层的UI Message Loop。这个UI Message Loop接下来又会用来创建一个Browser Thread,用来表示Chromium渲染引擎的Browser端。

       这一步执行完成后,回到前面分析的BrowserMainRunnerImpl类的成员函数Initialize中,接下来BrowserMainLoop类的成员函数EarlyInitialization会被调用,用来创建一个Native层的UI Message Loop,如下所示:

void BrowserMainLoop::EarlyInitialization() {
  ......

  if (parts_)
    parts_->PreEarlyInitialization();
  
  ......
}
      这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

      从前面的分析可以知道,BrowserMainLoop类的成员变量parts_指向的是一个AwBrowserMainParts对象。BrowserMainLoop类的成员函数EarlyInitialization会调用这个AwBrowserMainParts对象的成员函数PreEarlyInitialization创建一个UI Message Loop,如下所示:

void AwBrowserMainParts::PreEarlyInitialization() {
  ......
  main_message_loop_.reset(new base::MessageLoopForUI);
  base::MessageLoopForUI::current()->Start();
}
       这个函数定义在文件external/chromium_org/android_webview/browser/aw_browser_main_parts.cc中。

       AwBrowserMainParts类的成员函数PreEarlyInitialization创建了一个MessageLoopForUI对象。这个MessageLoopForUI对象描述的就是一个Native层的UI Message Loop。从前面Chromium多线程模型设计和实现分析一文可以知道,Native层的UI Message Loop并没有自己的线程,而是寄生在App的UI线程中运行(当前线程就是App的UI线程)。App的UI线程在Java层也有一个Message Loop,并且是由这个Java层的Message Loop驱动运行的。

       当我们往Native层的UI Message Loop发送一个消息的时候,Native层的UI Message Loop会向App的UI线程在Java层的Message Loop发送一个消息。当该消息被Java层的Message Loop调度执行的时候,之前发送在Native层的UI Message Loop中的消息就会得到执行。Chromium渲染引擎的Browser端,就是以这种方式运行在App的UI线程中的。

       AwBrowserMainParts类的成员函数PreEarlyInitialization在当前线程中创建了一个MessageLoopForUI对象之后,以后在当前线程中调用MessageLoopForUI类的静态成员函数current时,就会获得该MessageLoopForUI对象。有了这个MessageLoopForUI对象之后,AwBrowserMainParts类的成员函数PreEarlyInitialization就会调用它的成员函数Start,用来启动它描述的Native UI Message Loop。

       这一步执行完成后,回到前面分析的BrowserMainRunnerImpl类的成员函数Initialize中,接下来BrowserMainLoop类的成员函数MainMessageLoopStart会被调用,用来创建一个Browser Thread,如下所示:

void BrowserMainLoop::MainMessageLoopStart() {
  ......

  InitializeMainThread();

  .....
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       BrowserMainLoop类的成员函数MainMessageLoopStart调用另外一个成员函数InitializeMainThread创建一个Browser Thread,如下所示:

void BrowserMainLoop::InitializeMainThread() {
  ......
  main_thread_.reset(
      new BrowserThreadImpl(BrowserThread::UI, base::MessageLoop::current()));
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       BrowserMainLoop类的成员函数InitializeMainThread使用前面创建的Native UI Message Loop创建了一个Browser Thread。这个Browser Thread描述的就是Chromium渲染引擎的Browser端。由于这个Browser Thread是使用前面创建的Native UI Message Loop创建的,因此,它实际上描述的是App的UI线程。以后Chromium请求这个Browser Thread执行操作时,这个操作就会在App的UI线程中执行。

       这一步执行完成之后,Chromium渲染引擎的Browser端就启动完成了。再回到前面分析的BrowserMainRunnerImpl类的成员函数Initialize中,接下来BrowserMainLoop类的成员函数CreateStartupTasks会被调用,用来在Chromium渲染引擎的Browser端执行一些Startup Task,如下所示:

void BrowserMainLoop::CreateStartupTasks() {
  ......

  if (!startup_task_runner_.get()) {
    ......

    StartupTask pre_create_threads =
        base::Bind(&BrowserMainLoop::PreCreateThreads, base::Unretained(this));
    startup_task_runner_->AddTask(pre_create_threads);

    ......
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       其中的一个Startup Task,是在Chromium渲染引擎的Browser端创建其它线程(IO线程、数据库线程、文件线程等)之前执行的,对应的函数为BrowserMainLoop类的成员函数PreCreateThreads。

       BrowserMainLoop类的成员函数PreCreateThread会检查Android WebView的命令行参数是否设置了一个switches::kSingleProcess选项。如果设置了,那么就会将Chromium渲染引擎设置为单进程架构,如下所示:

int BrowserMainLoop::PreCreateThreads() {
  ......

#if !defined(OS_IOS) && (!defined(GOOGLE_CHROME_BUILD) || defined(OS_ANDROID))
  // Single-process is an unsupported and not fully tested mode, so
  // don't enable it for official Chrome builds (except on Android).
  if (parsed_command_line_.HasSwitch(switches::kSingleProcess))
    RenderProcessHost::SetRunRendererInProcess(true);
#endif
  return result_code_;
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       从前面的分析可以知道,函数SetContentCommandLineFlags会给Android WebView的命令行参数设置一个switches::kSingleProcess选项。在这种情况下,BrowserMainLoop类的成员函数PreCreateThread会调用RenderProcessHost类的静态成员函数SetRunRendererInProcess将Chromium渲染引擎设置为单进程架构,如下所示:

bool g_run_renderer_in_process_ = false;

......

void RenderProcessHost::SetRunRendererInProcess(bool value) {
  g_run_renderer_in_process_ = value;

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

       从前面的调用过程可以知道,参数value的值等于true。这时候RenderProcessHost类的静态成员函数SetRunRendererInProcess就会将全局变量g_run_renderer_in_process_的值设置为true,表示Chromium渲染引擎使用单进程加载,也就是在需要创建Render进程来加载和渲染网页时,通过一个In-Process Renderer Thread模拟。

       这一步执行完成后,Chromium渲染引擎的Browser端就启动完毕。回到前面分析的WebViewChromium类的成员函数init中,接下来它会继续调用另外一个成员函数initForReal为WebView创建一个AwContents对象。这个AwContents对象以后可以用来加载指定的URL。

       接下来,我们就继续分析WebViewChromium类的成员函数initForReal创建AwContents对象的过程,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    private void initForReal() {
        ......
        mAwContents = new AwContents(mFactory.getBrowserContext(), mWebView, ctx,
                new InternalAccessAdapter(), new WebViewNativeGLDelegate(),
                mContentsClientAdapter, mWebSettings.getAwSettings());

        ......
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewChromium类的成员函数initForReal主要是创建了一个AwContents对象,并且保存在成员变量mAwContents中。这个AwContents对象的创建过程,也就是AwContents类的构造函数的实现,如下所示:

public class AwContents {
    ......

    public AwContents(AwBrowserContext browserContext, ViewGroup containerView, Context context,
            InternalAccessDelegate internalAccessAdapter, NativeGLDelegate nativeGLDelegate,
            AwContentsClient contentsClient, AwSettings awSettings) {
        this(browserContext, containerView, context, internalAccessAdapter, nativeGLDelegate,
                contentsClient, awSettings, new DependencyFactory());
    }

    ......
}
      这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

      AwContents类的构造函数调用另外一个重载版本的构造函数创建一个AwContents对象,如下所示:

public class AwContents {
    ......

    public AwContents(AwBrowserContext browserContext, ViewGroup containerView, Context context,
            InternalAccessDelegate internalAccessAdapter, NativeGLDelegate nativeGLDelegate,
            AwContentsClient contentsClient, AwSettings settings,
            DependencyFactory dependencyFactory) {
        ......
        mNativeGLDelegate = nativeGLDelegate;
        ......

        setNewAwContents(nativeInit(mBrowserContext));

        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       参数nativeGLDelegate指向的是一个WebViewNativeGLDelegate对象。这个WebViewNativeGLDelegate对象会被保存在AwContents类的成员变量mNativeGLDelegate中。

       AwContents类的构造函数会调用另外一个成员函数nativeInit在Native层创建一个WebContents对象。WebContents类是Chromium的Content层向外提供的一个类,通过它可以描述一个网页。

       在Native层创建了一个WebContents对象之后,AwContents类的构造函数会将该WebContents对象传递给另外一个成员函数setNewAwContents,用来在Native层创建一个ContentViewCore对象。ContentViewCore类同样是Chromium的Content层向外提供的一个类,通过它可以加载一个指定的URL,也就是通过可以启动Chromium渲染引擎的Render端。

       接下来,我们就继续分析AwContents成员函数nativeInit和setNewAwContents的实现,以便了解Android WebView在Native层ContentViewCore对象的过程,为接下来分析Chromium渲染引擎的Render端的启动过程做准备。

       AwContents成员函数nativeInit是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_android_1webview_AwContents_nativeInit实现,如下所示:

__attribute__((visibility("default")))
jlong
    Java_com_android_org_chromium_android_1webview_AwContents_nativeInit(JNIEnv*
    env, jclass jcaller,
    jobject browserContext) {
  return Init(env, jcaller, browserContext);
}
       这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/android_webview/jni/AwContents_jni.h中。

       函数Java_com_android_org_chromium_android_1webview_AwContents_nativeInit调用另外一个函数Init创建一个WebContents对象,并且使用这个WebContents对象创建一个Native层的AwContents对象,如下所示:

static jlong Init(JNIEnv* env, jclass, jobject browser_context) {
  ......
  scoped_ptr<WebContents> web_contents(content::WebContents::Create(
      content::WebContents::CreateParams(AwBrowserContext::GetDefault())));
  ......
  return reinterpret_cast<intptr_t>(new AwContents(web_contents.Pass()));
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       函数Init是通过调用WebContents类的静态成员函数Create创建一个WebContents对象的。WebContents类的静态成员函数Create的实现,可以参考前面Chromium网页Frame Tree创建过程分析一文。

       创建了一个WebContents对象之后,函数Init就使用它来创建一个Native层的AwContents对象,如下所示:

AwContents::AwContents(scoped_ptr<WebContents> web_contents)
    : web_contents_(web_contents.Pass()),
      shared_renderer_state_(
          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI),
          this),
      browser_view_renderer_(
          this,
          &shared_renderer_state_,
          web_contents_.get(),
          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI)),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       AwContents类的构造函数首先将参数web_contents指向的WebContents对象保存在成员变量web_contents_中,接下来又会分别构造一个SharedRendererState对象和一个BrowserViewRenderer对象,并且保存在成员变量shared_renderer_state_和browser_view_renderer_中。

       通过AwContents类的成员变量shared_renderer_state_描述的SharedRendererState对象,Chromium渲染引擎的Browser端和Render端可以请求App的Render Thread执行GPU命令。同时,这个SharedRendererState对象也会用来保存Chromium渲染引擎的Render端渲染的每一帧数据。这些帧数据将会交给Chromium渲染引擎的Browser端合成显示在屏幕上。

       通过AwContents类的成员变量browser_view_renderer_描述的BrowserViewRenderer对象,则可以为Chromium渲染引擎的Render端创建一个Synchronous Compositor。这个Synchronous Compositor可以用来将网页的CC Layer Tree渲染在一个Synchronous Compositor Output Surface上。

       接下来我们就继续分析上述SharedRendererState对象和BrowserViewRenderer对象的构造过程。

       SharedRendererState对象的构造过程,也就是SharedRendererState类的构造函数的实现,如下所示:

SharedRendererState::SharedRendererState(
    scoped_refptr<base::MessageLoopProxy> ui_loop,
    BrowserViewRendererClient* client)
    : ui_loop_(ui_loop),
      client_on_ui_(client),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/android_webview$ vi browser/shared_renderer_state.cc中。

       参数ui_loop描述的是一个Native UI Message Loop。这个Native UI Message Loop是通过前面调用BrowserThread类的静态成员函数GetMessageLoopProxyForThread获得的。这个Native UI Message Loop会保存在SharedRendererState类的成员变量ui_loop_。以后通过这个成员变量,就可以向App的Render Thread请求执行GPU操作了。

       另外一个参数client指向的就是前面创建的AwContents对象。这个AwContents对象会保存在SharedRendererState类的成员变量client_on_ui_中。

       BrowserViewRenderer对象的构造过程,也就是BrowserViewRenderer类的构造函数的实现,如下所示:

BrowserViewRenderer::BrowserViewRenderer(
    BrowserViewRendererClient* client,
    SharedRendererState* shared_renderer_state,
    content::WebContents* web_contents,
    const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner)
    : client_(client),
      ...... {
  ......
  content::SynchronousCompositor::SetClientForWebContents(web_contents_, this);
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/browser/browser_view_renderer.cc中。

       从前面的调用过程可以知道,参数client指向的是前面创建的AwContents对象。这个AwContents对象会保存在BrowserViewRenderer类的成员变量client_中。

       BrowserViewRenderer类的构造函数接下来会调用SynchronousCompositor类的静态成员函数SetClientForWebContents创建一个Synchronous Compositor,如下所示:

void SynchronousCompositor::SetClientForWebContents(
    WebContents* contents,
    SynchronousCompositorClient* client) {
  ......
  if (client) {
    ......
    SynchronousCompositorImpl::CreateForWebContents(contents);
  }
  if (SynchronousCompositorImpl* instance =
      SynchronousCompositorImpl::FromWebContents(contents)) {
    instance->SetClient(client);
  }
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       从前面的调用过程可以知道,参数client的值不等于NULL,它指向的是一个BrowserViewRenderer对象。在这种情况下,SynchronousCompositor类的静态成员函数SetClientForWebContents会调用SynchronousCompositorImpl类的静态成员函数CreateForWebContents为前面创建的WebContents对象创建一个Synchronous Compositor。

       SynchronousCompositorImpl类的静态成员函数CreateForWebContents是从父类WebContentsUserData<SynchronousCompositorImpl>继承下来的,它的实现如下所示:

template <typename T>
class WebContentsUserData : public base::SupportsUserData::Data {
 public:
  // Creates an object of type T, and attaches it to the specified WebContents.
  // If an instance is already attached, does nothing.
  static void CreateForWebContents(WebContents* contents) {
    ......
    if (!FromWebContents(contents))
      contents->SetUserData(UserDataKey(), new T(contents));
  }

  ......
};
       这个函数定义在文件external/chromium_org/content/public/browser/web_contents_user_data.h中。

       WebContentsUserData<SynchronousCompositorImpl>类的静态成员函数CreateForWebContents首先调用另外一个FromWebContents检查之前是否已经为参数contents描述的WebContents对象创建过一个SynchronousCompositorImpl对象。如果没有创建过,那么就会创建一个,并且保存在该WebContents对象的内部,这是通过调用它的成员函数SetUserData实现的。这里创建出来的SynchronousCompositorImpl对象描述的就是一个ynchronous Compositor。

       这一步执行完成后,回到前面分析的SynchronousCompositor类的静态成员函数SetClientForWebContents中,接下来它又会通过SynchronousCompositorImpl类的静态成员函数FromWebContents获得前面创建的SynchronousCompositorImpl对象,并且调用它的成员函数SetClient,用来将参数client指向的BrowserViewRenderer对象保存在内部,如下所示:

void SynchronousCompositorImpl::SetClient(
    SynchronousCompositorClient* compositor_client) {
  DCHECK(CalledOnValidThread());
  compositor_client_ = compositor_client;
}
      这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

      SynchronousCompositorImpl类的成员函数SetClient将参数compositor_client指向的BrowserViewRenderer对象保存在成员变量compositor_client_中。

      这一步执行完成后,回到前面分析的Java层的AwContents类的构造函数中,这时候就在Native层创建一个WebContents对象、一个AwContents对象、一个SharedRendererState对象、一个BrowserViewRenderer对象,以及一个SynchronousCompositorImpl对象。这些对象后面在启动Chromium渲染引擎的Render端,以及Chromium渲染引擎的Render端渲染网页UI时,都会使用到。

       Java层的AwContents类的构造函数接下来会调用另外一个成员函数setNewAwContents在Native层创建一个ContentViewCore对象,如下所示:

public class AwContents {
    ......

    private void setNewAwContents(long newAwContentsPtr) {
        ......

        mNativeAwContents = newAwContentsPtr;
        ......

        long nativeWebContents = nativeGetWebContents(mNativeAwContents);
        mContentViewCore = createAndInitializeContentViewCore(
                mContainerView, mContext, mInternalAccessAdapter, nativeWebContents,
                new AwGestureStateListener(), mContentViewClient, mZoomControls);
        .......
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       从前面的调用过程可以知道,参数newAwContentsPtr描述的是前面在Native层创建的AwContents对象。这个AwContents对象将会保存在AwContents类的成员变量mNativeAwContents中。

        AwContents类的成员函数setNewAwContents接下来又会调用另外一个成员函数nativeGetWebContents获得用来创建上述Native层AwContents对象所使用的一个WebContents对象。有了这个WebContents对象之后,就使用它来在Native层创建一个ContentViewCore对象。这是通过调用AwContents类的成员函数createAndInitializeContentViewCore实现的,如下所示:

public class AwContents {
    ......

    private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView,
            Context context, InternalAccessDelegate internalDispatcher, long nativeWebContents,
            GestureStateListener gestureStateListener,
            ContentViewClient contentViewClient,
            ContentViewCore.ZoomControlsDelegate zoomControlsDelegate) {
        ContentViewCore contentViewCore = new ContentViewCore(context);
        contentViewCore.initialize(containerView, internalDispatcher, nativeWebContents,
                context instanceof Activity ?
                        new ActivityWindowAndroid((Activity) context) :
                        new WindowAndroid(context.getApplicationContext()));
        ......
        return contentViewCore;
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       AwContents类的成员函数createAndInitializeContentViewCore首先会创建一个Java层的ContentViewCore对象,然后再调用这个Java层的ContentViewCore对象的成员函数initialize对它进行初始化。在初始化的过程中,就会在Native层创建一个对应的ContentViewCore对象,如下所示:

public class ContentViewCore
        implements NavigationClient, AccessibilityStateChangeListener, ScreenOrientationObserver {
    ......

    public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
            long nativeWebContents, WindowAndroid windowAndroid) {
        ......

        mNativeContentViewCore = nativeInit(
                nativeWebContents, viewAndroidNativePointer, windowNativePointer,
                mRetainedJavaScriptObjects);
      
        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java中。

       ContentViewCore类的成员函数initialize会调用另外一个成员函数nativeInit在Native层创建一个ContentViewCore对象,并且保存在成员变量mNativeContentViewCore中。在创建这个Native层的ContentViewCore对象的时候,需要使用到参数nativeWebContents描述的一个Native层的WebContents对象。

       ContentViewCore类的成员函数nativeInit是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentViewCore_nativeInit实现,如下所示:

__attribute__((visibility("default")))
jlong
    Java_com_android_org_chromium_content_browser_ContentViewCore_nativeInit(JNIEnv*
    env, jobject jcaller,
    jlong webContentsPtr,
    jlong viewAndroidPtr,
    jlong windowAndroidPtr,
    jobject retainedObjectSet) {
  return Init(env, jcaller, webContentsPtr, viewAndroidPtr, windowAndroidPtr,
      retainedObjectSet);
}
      这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentViewCore_jni.h中。

      函数Java_com_android_org_chromium_content_browser_ContentViewCore_nativeInit调用另外一个函数Init在Native层创建一个ContentViewCore对象,如下所示:

jlong Init(JNIEnv* env,
           jobject obj,
           jlong native_web_contents,
           jlong view_android,
           jlong window_android,
           jobject retained_objects_set) {
  ContentViewCoreImpl* view = new ContentViewCoreImpl(
      env, obj,
      reinterpret_cast<WebContents*>(native_web_contents),
      reinterpret_cast<ui::ViewAndroid*>(view_android),
      reinterpret_cast<ui::WindowAndroid*>(window_android),
      retained_objects_set);
  return reinterpret_cast<intptr_t>(view);
}
       这个函数定义在文件external/chromium_org/content/browser/android/content_view_core_impl.cc中。

       从这里可以看到,函数Init会使用参数native_web_contents描述的一个WebContents对象以及其它参数在Native层创建一个ContentViewCoreImpl对象。 ContentViewCoreImpl类是从ContentViewCore继承下来的。  

       ContentViewCoreImpl对象的创建过程,也就是ContentViewCoreImpl类的构造函数的实现,如下所示:

ContentViewCoreImpl::ContentViewCoreImpl(
    JNIEnv* env,
    jobject obj,
    WebContents* web_contents,
    ui::ViewAndroid* view_android,
    ui::WindowAndroid* window_android,
    jobject java_bridge_retained_object_set)
    : ......,
      web_contents_(static_cast<WebContentsImpl*>(web_contents)),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/content/browser/android/content_view_core_impl.cc中。

       ContentViewCoreImpl类的构造函数会将参数web_contents指向的一个WebContents对象保存在成员变量web_contents_中。以后通过ContentViewCoreImpl类的成员函数LoadUrl加载指定的URL时,就需要使用到这个WebContents对象。

       在Java层创建了一个AwContents对象和在Native层创建了一个WebContents对象和一个ContentViewCore对象之后,接下来我们就可以在Android WebView中加载指定的URL了。Android WebView又会请求Chromium渲染引擎启动一个Render端,并且在这个Render端中加载指定的URL。接下来,我们就从Android WebView中加载URL开始,分析Chromium渲染引擎启动Render端的过程。

       Android WebView提供了一个成员函数loadUrl,用来加载指定的URL,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    public void loadUrl(String url) {
        checkThread();
        if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl=" + url);
        mProvider.loadUrl(url);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebView.java中。

       WebView类的成员函数loadUrl首先调用成员函数checkThread检查当前线程是否是创建WebView的线程。如果不是,那么就会抛出一个异常出来。

       从前面Android WebView加载Chromium动态库的过程分析一文可以知道,WebView类的成员变量mProvider指向的是一个WebViewChromium对象。如果通过前面检查,那么接下来这个WebViewChromium对象的成员函数loadUrl会被调用,用来加载参数url指定的URL,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    @Override
    public void loadUrl(String url) {
        // Early out to match old WebView implementation
        if (url == null) {
            return;
        }
        loadUrl(url, null);
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewChromium类的成员函数loadUrl调用另外一个重载版本的成员函数loadUrl加载参数url指定的URL,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    @Override
    public void loadUrl(final String url, Map<String, String> additionalHttpHeaders) {
        ......

        LoadUrlParams params = new LoadUrlParams(url);
        if (additionalHttpHeaders != null) params.setExtraHeaders(additionalHttpHeaders);
        loadUrlOnUiThread(params);
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewChromium类重载版本的成员函数loadUrl将参数url和additinalHttpHeaders封装一个LoadUrlParams对象中,然后再传这个LoadUrlParams对象传递给另外一个成员函数loadUrlonUiThread,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    private void loadUrlOnUiThread(final LoadUrlParams loadUrlParams) {
        ......
        if (checkNeedsPost()) {
            // Disallowed in WebView API for apps targetting a new SDK
            assert mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2;
            mRunQueue.addTask(new Runnable() {
                @Override
                public void run() {
                    mAwContents.loadUrl(loadUrlParams);
                }
            });
            return;
        }
        mAwContents.loadUrl(loadUrlParams);
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewChromium类的成员函数LoadUrlParams会调用成员函数checkNeedsPost检查当前线程是否就是WebView的创建线程。如果不是,并且当前的Android版本小于4.3,那么就会向WebView的创建线程的消息队列发送一个Runnable。当该Runnable被执行的时候,才会调用WebViewChromium类的成员变量mAwContents指向的一个AwContents对象的成员函数loadUrl加载参数loadUrlParam描述的URL。

       注意,如果当前线程不是WebView的创建线程,并且当前的Android版本大于等于4.3,那么WebViewChromium类的成员函数LoadUrlParams是不允许调用的。在我们这个情景中,前面已经保证了当前线程就是WebView的创建线程。在这种情况下,WebViewChromium类的成员函数LoadUrlParams就会直接调用成员变量mAwContents指向的一个AwContents对象的成员函数loadUrl加载参数loadUrlParam描述的URL,如下所示:

public class AwContents {
    ......

    public void loadUrl(LoadUrlParams params) {
        ......

        mContentViewCore.loadUrl(params);

        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       从前面的分析可以知道,AwContents类的成员变量mContentViewCore指向的是一个ContentViewCore对象。AwContents类的成员函数loadUrl调用这个ContentViewCore对象的成员函数loadUrl加载参数params描述的URL。

       ContentViewCore类的成员函数loadUrl加载指定URL的过程可以参考前面Chromium网页Frame Tree创建过程分析一文。在加载的过程中,会创建一个RenderViewHostImpl对象。从前面Chromium的Render进程启动过程分析一文又可以知道,在创建这个RenderViewHostImpl对象的过程中,又会创建一个RenderProcessHostImpl对象描述一个Render端。接下来这个RenderProcessHostImpl对象的成员函数Init又会被调用。在调用的过程中,它就会判断是要创建一个Render进程还是一个Render线程描述一个Render端,如下所示:

bool RenderProcessHostImpl::Init() {    
  ......  
  
  if (run_renderer_in_process()) {  
    ......  
    in_process_renderer_.reset(g_renderer_main_thread_factory(channel_id));  
  
    base::Thread::Options options;  
    ......  
    options.message_loop_type = base::MessageLoop::TYPE_DEFAULT;  
      
    in_process_renderer_->StartWithOptions(options);  
    ......  
  } else {  
    ......  
  
    child_process_launcher_.reset(new ChildProcessLauncher(  
        new RendererSandboxedProcessLauncherDelegate(channel_.get()),  
        cmd_line,  
        GetID(),  
        this));  
  
    ......  
  }  
  
  return true;  
}  
       这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

       RenderProcessHostImpl类的成员函数Init调用另外一个成员函数run_renderer_in_process判断要创建一个Render进程还是一个Render线程描述一个Render端。

       RenderProcessHostImpl类的成员函数run_renderer_in_process是从父类RenderProcessHost继承下来的,它的实现如下所示:

bool g_run_renderer_in_process_ = false;

......

bool RenderProcessHost::run_renderer_in_process() {
  return g_run_renderer_in_process_;
}
      这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

      RenderProcessHostImpl类的成员函数run_renderer_in_process返回的是全局变量g_run_renderer_in_process_的值。从前面的分析可以知道,这个全局变量g_run_renderer_in_process_已经被Android WebView设置为true。因此,RenderProcessHostImpl类的成员函数Init会创建一个Render线程来描述一个Render端。

      这个Render线程是通过调用另外一个全局变量g_renderer_main_thread_factory描述的一个线程创建工厂函数创建的。从前面的分析可以知道,这个全局变量g_renderer_main_thread_factory描述的线程创建工厂函数为CreateInProcessRendererThread,它的实现如下所示:

base::Thread* CreateInProcessRendererThread(const std::string& channel_id) {
  return new InProcessRendererThread(channel_id);
}
       这个函数定义在文件external/chromium_org/content/renderer/in_process_renderer_thread.cc中。

       从这里可以看到,函数为CreateInProcessRendererThread创建的是一个InProcessRendererThread对象。这个InProcessRendererThread对象描述的是一个类型为In-Process的Render线程。这个Render线程在RenderProcessHostImpl类的成员函数Init中将会被启动起来。这时候Android WebView就将Chromium渲染引擎的Render端启动起来了。

       最后,我们分析Chromium渲染引擎的GPU端。由于Android WebView要求Chromium渲染引擎使用App的Render Thread来执行GPU命令,因此Chromium渲染引擎的GPU端是通过App的Render Thread描述的,它的启动过程可以参考前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章。

       为了让Chromium渲染引擎可以使用App的Render Thread执行GPU命令,Chromium的android_webview模块会创建一个DeferredGpuCommandService服务。这个DeferredGpuCommandService服务将会负责请求App的Render Thread执行Chromium渲染引擎的Render端和Browser端发出来的GPU命令。接下来我们就分析这个DeferredGpuCommandService服务的创建过程。

       DeferredGpuCommandService服务是在Android WebView的成员函数onDraw被App的UI线程调用时创建的,因此接下来我们就从Android WebView的成员函数onDraw开始分析DeferredGpuCommandService服务的创建过程,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    @Override
    protected void onDraw(Canvas canvas) {
        mProvider.getViewDelegate().onDraw(canvas);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/webkit/WebView.java中。

       前面提到,WebView类的成员变量mProvider指向的是一个WebViewChromium对象。WebView的成员函数onDraw调用这个WebViewChromium对象的成员函数getViewDelegate获得一个View Delegate,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    @Override
    // This needs to be kept thread safe!
    public WebViewProvider.ViewDelegate getViewDelegate() {
        return this;
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       从这里可以看到,WebViewChromium类的成员函数getViewDelegate返回的View Delegate就是当前正在处理的WebViewChromium对象。这个WebViewChromium对象返回给WebView类的成员函数onDraw之后,它的成员函数onDraw就会被调用,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    @Override
    public void onDraw(final Canvas canvas) {
        ......
        if (checkNeedsPost()) {
            runVoidTaskOnUiThreadBlocking(new Runnable() {
                @Override
                public void run() {
                    onDraw(canvas);
                }
            });
            return;
        }
        mAwContents.onDraw(canvas);
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewChromium类的成员函数onDraw首先会调用成员函数checkNeedsPost检查当前线程是否就是WebView的创建线程。如果不是,那么就会向WebView的创建线程的消息队列发送一个Runnable。当该Runnable被执行的时候,才会重新进入WebViewChromium类的成员函数onDraw中,并且调用它的成员变量mAwContents指向的一个AwContents对象的成员函数onDraw,用来绘制网页的UI。

       如果当前线程就是WebView的创建线程,那么WebViewChromium类的成员函数onDraw就会直接调用成员变量mAwContents指向的一个AwContents对象的成员函数onDraw绘制网页的UI。

       AwContents类的成员函数onDraw的实现如下所示:

public class AwContents {
    ......

    public void onDraw(Canvas canvas) {
        mAwViewMethods.onDraw(canvas);
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       AwContents类的成员变量mAwViewMethods指向的是一个AwViewMethodsImpl对象。AwContents类的成员函数onDraw调用这个AwViewMethodsImpl对象的成员函数onDraw绘制网页的UI,如下所示:

public class AwContents {
    ......

    private long mNativeAwContents;
    ......
 
    private class AwViewMethodsImpl implements AwViewMethods {
        ......

        @Override
        public void onDraw(Canvas canvas) {
            ......

            if (!nativeOnDraw(mNativeAwContents, canvas, canvas.isHardwareAccelerated(),
                    mContainerView.getScrollX(), mContainerView.getScrollY(),
                    globalVisibleRect.left, globalVisibleRect.top,
                    globalVisibleRect.right, globalVisibleRect.bottom)) {
                ......
            }

            ......
        }

        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       AwViewMethodsImpl类的成员函数onDraw会调用外部类AwContents的成员函数nativeOnDraw绘制网页的UI,并且会将外部类的成员变量mNativeAwContents描述的一个Native层的AwContents对象传递给它。

       AwContents类的成员函数nativeOnDraw是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_android_1webview_AwContents_nativeOnDraw实现,如下所示:

__attribute__((visibility("default")))
jboolean
    Java_com_android_org_chromium_android_1webview_AwContents_nativeOnDraw(JNIEnv*
    env,
    jobject jcaller,
    jlong nativeAwContents,
    jobject canvas,
    jboolean isHardwareAccelerated,
    jint scrollX,
    jint scrollY,
    jint visibleLeft,
    jint visibleTop,
    jint visibleRight,
    jint visibleBottom) {
  AwContents* native = reinterpret_cast<AwContents*>(nativeAwContents);
  CHECK_NATIVE_PTR(env, jcaller, native, "OnDraw", false);
  return native->OnDraw(env, jcaller, canvas, isHardwareAccelerated, scrollX,
      scrollY, visibleLeft, visibleTop, visibleRight, visibleBottom);
}
       这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/android_webview/jni/AwContents_jni.h中。

       函数Java_com_android_org_chromium_android_1webview_AwContents_nativeOnDraw调用参数nativeAwContents描述的一个Native层AwContents对象的成员函数OnDraw绘制网页的UI,如下所示:

bool AwContents::OnDraw(JNIEnv* env,
                        jobject obj,
                        jobject canvas,
                        jboolean is_hardware_accelerated,
                        jint scroll_x,
                        jint scroll_y,
                        jint visible_left,
                        jint visible_top,
                        jint visible_right,
                        jint visible_bottom) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (is_hardware_accelerated)
    InitializeHardwareDrawIfNeeded();
  return browser_view_renderer_.OnDraw(
      canvas,
      is_hardware_accelerated,
      gfx::Vector2d(scroll_x, scroll_y),
      gfx::Rect(visible_left,
                visible_top,
                visible_right - visible_left,
                visible_bottom - visible_top));
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       参数is_hardware_accelerated表示App的UI是否采用硬件加速方式绘制。如果是的话,那么Chromium也会使用硬件加速方式绘制网页的UI。在这种情况下,AwContents类的成员函数OnDraw首先会调用另外一个成员函数InitializeHardwareDrawIfNeeded检查是否需要为Chromium初始化一个硬件加速渲染环境。这个硬件加速渲染环境初始化完成后,AwContents类的成员函数OnDraw才会调用成员变量browser_view_renderer_描述的一个BrowserViewRenderer对象的成员函数OnDraw绘制网页的UI。

       AwContents类的成员函数InitializeHardwareDrawIfNeeded在为Chromium初始化硬件加速渲染环境的过程中,就会创建一个DeferredGpuCommandService服务,如下所示:

void AwContents::InitializeHardwareDrawIfNeeded() {
  GLViewRendererManager* manager = GLViewRendererManager::GetInstance();

  base::AutoLock lock(render_thread_lock_);
  if (renderer_manager_key_ == manager->NullKey()) {
    renderer_manager_key_ = manager->PushBack(&shared_renderer_state_);
    DeferredGpuCommandService::SetInstance();
  }
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       在Chromium渲染引擎中,存在一个GLViewRendererManager单例对象。这个GLViewRendererManager单例对象可以通过调用GLViewRendererManager类的静态成员函数GetInstance获得,它用来记录当前有哪些WebView是采用硬件加速方式绘制的。

       AwContents类的成员函数InitializeHardwareDrawIfNeeded会检查成员变量renderer_manager_key_的值是否等于一个Null Key。如果等于的话,那么就说明当前正在绘制的WebView还没有初始化过硬件加速渲染环境。这时候AwContents类的成员函数InitializeHardwareDrawIfNeeded就会将成员变量shared_renderer_state_描述的一个SharedRendererState对象添加到上述GLViewRendererManager单例对象中。添加之后,会获得一个Key。这个Key就保存在AwContents类的成员变量renderer_manager_key_。同时,DeferredGpuCommandService类的静态成员函数SetInstance会被调用,用来创建一个DeferredGpuCommandService服务。这时候就表示当前正在绘制的WebView的硬件加速渲染环境初始化好了。

        接下来我们继续分析DeferredGpuCommandService类的静态成员函数SetInstance会创建ferredGpuCommandService服务的过程,如下所示:

base::LazyInstance<scoped_refptr<DeferredGpuCommandService> >
    g_service = LAZY_INSTANCE_INITIALIZER;

......

void DeferredGpuCommandService::SetInstance() {
  if (!g_service.Get()) {
    g_service.Get() = new DeferredGpuCommandService;
    content::SynchronousCompositor::SetGpuService(g_service.Get());
  }
}

......

DeferredGpuCommandService* DeferredGpuCommandService::GetInstance() {
  DCHECK(g_service.Get().get());
  return g_service.Get().get();
}
       这个函数定义在文件external/chromium_org/android_webview/browser/deferred_gpu_command_service.cc中。

       DeferredGpuCommandService类的静态成员函数SetInstance检查全局变量g_service是否已经指向了一个DeferredGpuCommandService对象。如果还没有指向,那么就会创建一个DeferredGpuCommandService对象让它指向。创建出来的DeferredGpuCommandService对象描述的就是一个DeferredGpuCommandService服务。这个DeferredGpuCommandService服务可以通过调用DeferredGpuCommandService类的静态成员函数GetInstance获得。

       DeferredGpuCommandService类的静态成员函数SetInstance创建出来的DeferredGpuCommandService服务会被设置为Android WebView绘制网页所使用的Synchronous Compositor的GPU服务,这是通过调用SynchronousCompositor类的静态成员函数SetGpuService实现的,如下所示:

base::LazyInstance<synchronouscompositorfactoryimpl>::Leaky g_factory =
    LAZY_INSTANCE_INITIALIZER;

......

void SynchronousCompositor::SetGpuService(
    scoped_refptr<gpu::InProcessCommandBuffer::Service> service) {
  g_factory.Get().SetDeferredGpuService(service);
}

       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       全局变量g_factory描述的是一个SynchronousCompositorFactoryImpl对象,SynchronousCompositor类的静态成员函数SetGpuService会将参数service描述的一个DeferredGpuCommandService服务保存在它内部。这个通过调用它的成员函数SetDeferredGpuService实现的,如下所示

void SynchronousCompositorFactoryImpl::SetDeferredGpuService(
    scoped_refptr<gpu::InProcessCommandBuffer::Service> service) {
  DCHECK(!service_);
  service_ = service;
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。

       SynchronousCompositorFactoryImpl类的成员函数SetDeferredGpuService将参数service描述的一个DeferredGpuCommandService服务保存在成员变量service_。

       这一步执行完成后,Chromium渲染引擎的Render端以后就会通过保存在SynchronousCompositorFactoryImpl类的成员变量service_中的DeferredGpuCommandService服务来执行GPU命令。这一点我们在接下来一篇文章中再详细分析。

       至此,我们就分析完成了Android WebView启动Chromium渲染引擎的过程,主要就是启动的Chromium渲染引擎的Browser端和Render端。其中,Browser端对应的就是App的UI线程,而Render端对应的是一个类型为In-Process的Render线程。

       由于Android WebView要求Chromium渲染引擎使用App的Render Thread执行GPU命令,因此Chromium渲染引擎就不会创建自己的GPU端。不过,它会创建一个DeferredGpuCommandService服务,用来将Chromium渲染引擎发出的GPU命令交给App的Render Thread执行。在接下来一篇文章中,我们就详细分析Chromium渲染引擎执行GPU命令的过程。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/12/5 1:02:47 原文链接
阅读:40067 评论:6 查看评论

Android WebView执行GPU命令的过程分析

$
0
0

       Android WebView使用的Chromium引擎,虽然没有自己的GPU进程或者线程,但是却可以执行GPU命令。原来,Android WebView会给它提供一个In-Process Command Buffer GL接口。通过这个接口,Chromium引擎就可以将GPU命令提交给App的Render Thread执行。本文接下来就详细分析Android WebView执行GPU命令的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       从前面Chromium硬件加速渲染的OpenGL命令执行过程分析这篇文章可以知道,Chromium渲染引擎在有自己的GPU进程或者线程的情况下,是通过一个Command Buffer GL接口执行GPU命令的。这个Command Buffer GL接口通过一个GLES2Implementation类描述。Android WebView给Chromium引擎提供的In-Process Command Buffer GL接口,同样是通过GLES2Implementation类描述的。这样,Chromium渲染引擎就不用关心它发出的GPU命令是如何执行的。

       在Chromium渲染引擎中,需要执行GPU命令的是Render端和Browser端。Render端执行GPU命令是为渲染网页的UI,而Browser端执行GPU命令是为了将Render端渲染的网页UI合成显示在屏幕上。对Android WebView来说,它的Render端会将网页抽象成一个CC Layer Tree,然后使用一个Synchronous Compositor将它渲染在一个Synchronous Compositor Output Surface上,如图1所示:


图1 Android WebView的Render端渲染网页UI的示意图

       Android WebView的Browser端同样会将自己要合成的UI抽象为一个CC Layer Tree,然后使用一个Hardware Renderer将它渲染在一个Parent Output Surface上,如图2所示:


图2 Android WebView的Browser端合成网页UI的示意图

       Browser端的CC Layer Tree比较特别,它只有两个节点。一个是根节点,另一个是根节点的子节点,称为Delegated Render Layer,它要渲染的内容来自于Render端的渲染输出。从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,Render端的渲染输出是一系列的Render Pass。每一个Render Pass都包含了若干个纹理。这些纹理是在Render端光栅化网页时产生的。Browser端的Hardware Renderer所要做的事情就是将这些纹理渲染在屏幕上。这个过程也就是Browser端合成网页UI的过程。

       不管是Render端,还是Browser端,当它们执行GPU命令的时候,都是通过GLES2Implementation类描述的一个In-Process Command Buffer GL接口写入到一个Command Buffer中去的。只不过在Android WebView的情况下,这些GPU命令会被一个DeferredGpuCommandService服务提交给App的Render Thread执行,如图3所示:


图3 Android WebView执行GPU命令的过程

       Render端和Browser端将要执行的GPU命令写入到Command Buffer之后,就会请求DeferredGpuCommandService服务在App的Render Thread中调度执行一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread。InProcessCommandBuffer类的成员函数FlushOnGpuThread又是通过一个Gpu Scheduler按照上下文来执行请求的GPU命令,并且这些GPU命令在执行之前,先要经过一个GLES2 Decoder进行解码。这个过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       对于Browser端来说,它本来就是在App的Render Thread中请求执行GPU命令的。这时候DeferredGpuCommandService服务会直接在当前线程中调用InProcessCommandBuffer类的成员函数FlushOnGpuThread,以便执行请求的GPU命令。

       对于Render端来说,它在两种情景下需要请求执行GPU命令。第一种情景是光栅化网页的UI,第二种情景是绘制网页的UI。这两种情景都是发生在Render端的Compositor线程中。关于Render端的Compositor线程,以及网页UI的光栅化和绘制操作,可以参考前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章。

       上述两种情景都会将要执行的GPU操作抽象为一个DrawGLFunctor对象。对于第一个情景,DrawGLFunctor对象会通过App的UI线程直接提交给App的Render Thread。App的Render Thread再通知该DrawGLFunctor对象执行GPU操作。

       对于第二个情景,DrawGLFunctor对象会被App的UI线程封装为一个DrawFunctorOp操作,并且写入到App UI的Display List中。 接下来App的UI线程会将Display List同步给App的Render Thread。随后这个Display List就会被App的Render Thread通过一个OpenGL Renderer进行Replay,这时候Display List中包含的DrawFunctorOp操作就会被执行。在执行的过程中,与它关联的DrawGLFunctor对象获得通知。DrawGLFunctor对象获得通知以后就会执行之前Render端请求的GPU操作了。

       DrawGLFunctor对象在执行GPU操作的时候,会调用到一个DrawGL函数。这个DrawGL函数是Android WebView在启动Chromium渲染引擎时注册的。它在执行的过程中,就会通过Browser端的Hardware Renderer通知DeferredGpuCommandService服务执行此前请求调度的Task。这时候InProcessCommandBuffer类的成员函数FlushOnGpuThread就会被调用,这时候Render端之前请求的GPU命令就会被执行。

       接下来,我们就结合源码,分析Chromium渲染引擎的Render端和Browser端创建In-Process Command Buffer GL接口的过程,以及以Render端使用GPU光栅化网页的过程为例,分析Chromium渲染引擎通过In-Process Command Buffer GL接口执行GPU命令的过程。不过,在分析这些过程之前,我们首先分析Android WebView向Chromium渲染引擎注册DrawGL函数的过程。这个过程发生在Android WebView启动Chromium渲染引擎的Browser端的过程中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,Android WebView在启动Chromium渲染引擎的Browser端的过程中,会调用到WebViewChromiumFactoryProvider类的成员函数startChromiumLocked,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    private void startChromiumLocked() {
        ......

        initPlatSupportLibrary();
        AwBrowserProcess.start(ActivityThread.currentApplication());
    
        ......
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       在调用AwBrowserProcess类的静态成员函数start动Chromium渲染引擎的Browser端之前,WebViewChromiumFactoryProvider类的成员函数startChromiumLocked先会调用成员函数initPlatSupportLibrary向Chromium渲染引擎注册一个DrawGL函数,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    private void initPlatSupportLibrary() {
        DrawGLFunctor.setChromiumAwDrawGLFunction(AwContents.getAwDrawGLFunction());
        ......
    }

    ......
}

       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

       WebViewChromiumFactoryProvider类的成员函数startChromiumLocked首先会调用AwContents类的静态成员函数getAwDrawGLFunction获得要注册的DrawGL函数,然后再调用DrawGLFunctor类的静态成员函数setChromiumAwDrawGLFunction将它注册到Chromium渲染引擎中。

       接下来,我们首先分析AwContents类的静态成员函数getAwDrawGLFunction获取要注册的DrawGL函数的过程,然后再分析DrawGLFunctor类的静态成员函数setChromiumAwDrawGLFunction注册DrawGL函数到Chromium渲染引擎的过程。

       AwContents类的静态成员函数getAwDrawGLFunction的实现如下所示:

public class AwContents {
    ......

    public static long getAwDrawGLFunction() {
        return nativeGetAwDrawGLFunction();
    }

    ......
}
       这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

       AwContents类的静态成员函数getAwDrawGLFunction调用另外一个静态成员函数AwContents类的静态成员函数获取要注册的的DrawGL函数的过程。

       AwContents类的静态成员函数AwContents类的静态成员函数是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_android_1webview_AwContents_nativeGetAwDrawGLFunction实现,如下所示:

__attribute__((visibility("default")))
jlong
    Java_com_android_org_chromium_android_1webview_AwContents_nativeGetAwDrawGLFunction(JNIEnv*
    env, jclass jcaller) {
  return GetAwDrawGLFunction(env, jcaller);
}
       这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/android_webview/jni/AwContents_jni.h中。

       函数Java_com_android_org_chromium_android_1webview_AwContents_nativeGetAwDrawGLFunction又通过调用另外一个函数GetAwDrawGLFunction获取要注册的DrawGL函数,如下所示:

static jlong GetAwDrawGLFunction(JNIEnv* env, jclass) {
  return reinterpret_cast<intptr_t>(&DrawGLFunction);
}
      这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

      函数GetAwDrawGLFunction返回的是定义在同文件中的函数DrawGLFunction。这个函数的地址将会返回到前面分析的WebViewChromiumFactoryProvider类的成员函数initPlatSupportLibrary向Chromium,后者再调用DrawGLFunctor类的静态成员函数setChromiumAwDrawGLFunction将它注册到Chromium渲染引擎中,如下所示:

class DrawGLFunctor {
    ......

    public static void setChromiumAwDrawGLFunction(long functionPointer) {
        nativeSetChromiumAwDrawGLFunction(functionPointer);
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/DrawGLFunctor.java中。

       DrawGLFunctor类的静态成员函数setChromiumAwDrawGLFunction调用另外一个静态成员函数nativeSetChromiumAwDrawGLFunction将前面获得的函数DrawGLFunction注册在Chromium渲染引擎中。

       DrawGLFunctor类的静态成员函数nativeSetChromiumAwDrawGLFunction是一个JNI方法,它由C++层的函数SetChromiumAwDrawGLFunction实现,如下所示:

AwDrawGLFunction* g_aw_drawgl_function = NULL;

......

void SetChromiumAwDrawGLFunction(JNIEnv*, jclass, jlong draw_function) {
  g_aw_drawgl_function = reinterpret_cast<AwDrawGLFunction*>(draw_function);
}
      这个函数定义在文件frameworks/webview/chromium/plat_support/draw_gl_functor.cpp中。

      函数SetChromiumAwDrawGLFunction将参数draw_function描述的函数DrawGLFunction的地址保存在全局变量g_aw_drawgl_function中。这样,Android WebView就在启动Chromium渲染引擎的Browser端的过程中,向Chromium渲染引擎注册了一个DrawGL函数。

      接下来,我们分析Chromium渲染引擎为Render端创建In-Process Command Buffer GL接口的过程。Chromium渲染引擎为Render端创建的In-Process Command Buffer GL接口是封装在绘图表面(Output Surface)里面的。在Chromium中,每一个网页都关联一个Output Surface。在分析Render端的In-Process Command Buffer GL接口的创建之前,我们首先分析网页的Output Surface的创建过程。

      从前面Chromium网页绘图表面(Output Surface)创建过程分析Chromium的GPU进程启动过程分析这两篇文章可以知道,Render端在加载了网页之后,会为网页创建一个绘图表面,即一个Output Surface,最终是通过调用RenderWidget类的成员函数CreateOutputSurface进行创建的,如下所示:

scoped_ptr<cc::OutputSurface> RenderWidget::CreateOutputSurface(bool fallback) {
  ......

#if defined(OS_ANDROID)
  if (SynchronousCompositorFactory* factory =
      SynchronousCompositorFactory::GetInstance()) {
    return factory->CreateOutputSurface(routing_id());
  }
#endif

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       在Android平台上,RenderWidget类的成员函数CreateOutputSurface首先会调用SynchronousCompositorFactory类的静态成员函数GetInstance检查当前进程是否存在一个用来创建Output Surface的工厂对象。如果存在,那么就会调用它的成员函数CreateOutputSurface为当前加载的网页创建一个Output Surface。

       SynchronousCompositorFactory类的静态成员函数GetInstance的实现如下所示:

SynchronousCompositorFactory* g_instance = NULL;

......

void SynchronousCompositorFactory::SetInstance(
    SynchronousCompositorFactory* instance) {
  ......

  g_instance = instance;
}

SynchronousCompositorFactory* SynchronousCompositorFactory::GetInstance() {
  return g_instance;
}

       这两个函数定义在文件external/chromium_org/content/renderer/android/synchronous_compositor_factory.cc。

       SynchronousCompositorFactory类的静态成员函数GetInstance返回的是全局变量g_instance指向的一个SynchronousCompositorFactoryImpl对象。这个SynchronousCompositorFactoryImpl对象是通过SynchronousCompositorFactory类的静态成员数SetInstance设置给全局变量g_instance的。

       这个SynchronousCompositorFactoryImpl对象是什么时候设置给全局变量g_instance的呢?回忆前面Android WebView启动Chromium渲染引擎的过程分析一文,Android WebView在启动Chromium渲染引擎的过程,会创建一个DeferredGpuCommandService服务,并且将这个DeferredGpuCommandService服务保存在一个SynchronousCompositorFactoryImpl对象的成员变量service_中。这个SynchronousCompositorFactoryImpl对象在创建的过程中,就会通过调用SynchronousCompositorFactory类的静态成员数SetInstance将自己保存在上述全局变量g_instance中,如下所示:

SynchronousCompositorFactoryImpl::SynchronousCompositorFactoryImpl()
    : record_full_layer_(true),
      num_hardware_compositors_(0) {
  SynchronousCompositorFactory::SetInstance(this);
}
      这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。

      回到前面分析的RenderWidget类的成员函数CreateOutputSurface中,这时候它调用SynchronousCompositorFactory类的静态成员函数GetInstance就会获得一个SynchronousCompositorFactoryImpl对象。有了这个SynchronousCompositorFactoryImpl对象之后,RenderWidget类的成员函数CreateOutputSurface就会调用它的成员函数CreateOutputSurface为当前正在加载的网页创建一个Output Surface,如下所示:

scoped_ptr<cc::OutputSurface>
SynchronousCompositorFactoryImpl::CreateOutputSurface(int routing_id) {
  scoped_ptr<SynchronousCompositorOutputSurface> output_surface(
      new SynchronousCompositorOutputSurface(routing_id));
  return output_surface.PassAs<cc::OutputSurface>();
}
      这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。

      SynchronousCompositorFactoryImpl类的成员函数CreateOutputSurface创建一个类型为Synchronous Compositor的Output Surface返回给调用者。从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,这个Synchronous Compositor Output Surface将会返回给ThreadProxy类的成员函数CreateAndInitializeOutputSurface,如下所示:

void ThreadProxy::CreateAndInitializeOutputSurface() {  
  ......    
  
  scoped_ptr<OutputSurface> output_surface =  
      layer_tree_host()->CreateOutputSurface();  
  
  if (output_surface) {  
    Proxy::ImplThreadTaskRunner()->PostTask(  
        FROM_HERE,  
        base::Bind(&ThreadProxy::InitializeOutputSurfaceOnImplThread,  
                   impl_thread_weak_ptr_,  
                   base::Passed(&output_surface)));  
    return;  
  }  
  
  ......  
}  
       这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。

       ThreadProxy类的成员函数CreateAndInitializeOutputSurface又会将这个Synchronous Compositor Output Surface传递给Render端的Compositor线程,让后者对它进行初始化。这个初始化操作是通过在Render端的Compositor线程中调用ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread实现的。

       从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread在初始化Synchronous Compositor Output Surface的过程中,会调用它的成员函数BindToClient,表示它已经被设置给一个网页使用。

       SynchronousCompositorOutputSurface类的成员函数BindToClient的实现如下所示:

bool SynchronousCompositorOutputSurface::BindToClient(
    cc::OutputSurfaceClient* surface_client) {
  ......

  SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate();
  if (delegate)
    delegate->DidBindOutputSurface(this);

  return true;
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       SynchronousCompositorOutputSurface类的成员函数BindToClient会调用另外一个成员函数GetDelegate获得一个Delegate对象,如下所示:

SynchronousCompositorOutputSurfaceDelegate*
SynchronousCompositorOutputSurface::GetDelegate() {
  return SynchronousCompositorImpl::FromRoutingID(routing_id_);
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       SynchronousCompositorOutputSurface类的成员函数GetDelegate通过调用SynchronousCompositorImpl类的静态成员函数FromRoutingID获得一个Delegate对象,如下所示:

SynchronousCompositorImpl* SynchronousCompositorImpl::FromRoutingID(
    int routing_id) {
  return FromID(GetInProcessRendererId(), routing_id);
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       SynchronousCompositorImpl类的静态成员函数FromRoutingID首先会调用静态成员函数GetInProcessRendererId获得当前正在处理的Render端的ID。有了这个ID之后,连同参数routing_id描述的网页ID,传递给SynchronousCompositorImpl类的另外一个静态成员函数FromID,如下所示:

SynchronousCompositorImpl* SynchronousCompositorImpl::FromID(int process_id,
                                                             int routing_id) {
  ......
  RenderViewHost* rvh = RenderViewHost::FromID(process_id, routing_id);
  ......
  WebContents* contents = WebContents::FromRenderViewHost(rvh);
  ......
  return FromWebContents(contents);
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       SynchronousCompositorImpl类的静态成员函数FromID首先通过调用RenderViewHost类的静态成员函数FromID获得与参数process_id和routing_id对应的一个RenderViewHost对象。这个RenderViewHost对象用来在Browser端描述的一个网页。这个网页就是当前正在Android WebView中加载的网页。

       获得了与当前正在加载的网页对应的RenderViewHost对象之后,就可以调用WebContents类的静态成员函数FromRenderViewHost获得一个WebContents对象。在Chromium中,每一个网页在Content层又都是通过一个WebContents对象描述的。这个WebContents对象的创建过程可以参考前面Android WebView启动Chromium渲染引擎的过程分析一文。

       获得了用来描述当前正在加载的网页的WebContents对象之后,SynchronousCompositorImpl类的静态成员函数FromID就可以调用另外一个静态成员函数FromWebContents获得一个SynchronousCompositorImpl对象。这个SynchronousCompositorImpl对象的创建过程可以参考前面Android WebView启动Chromium渲染引擎的过程分析一文,它是负责用来渲染在Android WebView中加载的网页的UI的。

       SynchronousCompositorImpl类的静态成员函数FromID最后会将获得的SynchronousCompositorImpl对象返回给调用者,也就是前面分析的SynchronousCompositorOutputSurface类的成员函数BindToClient。SynchronousCompositorOutputSurface类的成员函数BindToClient接下来会调用这个SynchronousCompositorImpl对象的成员函数DidBindOutputSurface,表示它现在已经与一个Synchronous Compositor Output Surface建立了绑定关系,这样以后它就可以将网页的UI渲染在这个Synchronous Compositor Output Surface之上。

       SynchronousCompositorImpl类的成员函数DidBindOutputSurface的实现如下所示:

void SynchronousCompositorImpl::DidBindOutputSurface(
      SynchronousCompositorOutputSurface* output_surface) {
  ......
  output_surface_ = output_surface;
  if (compositor_client_)
    compositor_client_->DidInitializeCompositor(this);
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       SynchronousCompositorImpl类的成员函数DidBindOutputSurface首先将参数output_surface描述的Synchronous Compositor Output Surface保存在成员变量output_surface_中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,SynchronousCompositorImpl类的成员变量compositor_client_指向的是一个BrowserViewRenderer对象。SynchronousCompositorImpl类的成员函数DidBindOutputSurface最后会调用这个BrowserViewRenderer对象的成员函数DidInitializeCompositor,表示Chromium渲染引擎已经为它创建了一个用来渲染网页UI的Synchronous Compositor,如下所示:

void BrowserViewRenderer::DidInitializeCompositor(
    content::SynchronousCompositor* compositor) {
  ......

  compositor_ = compositor;
}
       这个函数定义在文件external/chromium_org/android_webview/browser/browser_view_renderer.cc中。

       BrowserViewRenderer类的成员函数DidInitializeCompositor会将参数compositor指向的一个SynchronousCompositorImpl对象保存在成员变量compositor_中。

       这一步执行完成之后,Chromium渲染引擎就为在Render端加载的网页创建了一个Synchronous Compositor Output Surface,并且会将这个Synchronous Compositor Output Surface设置给一个Synchronous Compositor。这个Synchronous Compositor又会设置给一个BrowserViewRenderer对象。这个BrowserViewRenderer对象负责绘制Android WebView的UI。

       接下来我们就开始分析Chromium渲染引擎为Render端创建In-Process Command Buffer GL接口的过程。这个In-Process Command Buffer GL接口是在Android WebView第一次执行硬件加速渲染之前创建的。从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,Android WebView每次被绘制时,它在Chromium的android_webview模块中对应的AwContents对象的成员函数OnDraw都会被调用,如下所示:

bool AwContents::OnDraw(JNIEnv* env,
                        jobject obj,
                        jobject canvas,
                        jboolean is_hardware_accelerated,
                        jint scroll_x,
                        jint scroll_y,
                        jint visible_left,
                        jint visible_top,
                        jint visible_right,
                        jint visible_bottom) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (is_hardware_accelerated)
    InitializeHardwareDrawIfNeeded();
  return browser_view_renderer_.OnDraw(
      canvas,
      is_hardware_accelerated,
      gfx::Vector2d(scroll_x, scroll_y),
      gfx::Rect(visible_left,
                visible_top,
                visible_right - visible_left,
                visible_bottom - visible_top));
}
      这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc。

      如果执行的是硬件加速渲染,那么AwContents类的成员函数OnDraw首先会调用另外一个成员函数InitializeHardwareDrawIfNeeded检查当前是否已经创建了一个DeferredGpuCommandService服务。如果还没有创建,那么就会进行创建。这个DeferredGpuCommandService服务的创建过程可以参考前面Android WebView启动Chromium渲染引擎的过程分析一文。

      AwContents类的成员函数OnDraw接下来会调用成员变量browser_view_renderer_描述的一个BrowserViewRenderer对象的成员函数OnDraw执行硬件加速渲染,如下所示:

bool BrowserViewRenderer::OnDraw(jobject java_canvas,
                                 bool is_hardware_canvas,
                                 const gfx::Vector2d& scroll,
                                 const gfx::Rect& global_visible_rect) {
  ......

  if (is_hardware_canvas && attached_to_window_)
    return OnDrawHardware(java_canvas);
  // Perform a software draw
  return OnDrawSoftware(java_canvas);
}
       这个函数定义在文件external/chromium_org/android_webview/browser/browser_view_renderer.cc中。

       在执行硬件加速渲染的情况下,BrowserViewRenderer对象的成员函数OnDraw会调用成员函数OnDrawHardware对Android WebView进行绘制,如下所示:

bool BrowserViewRenderer::OnDrawHardware(jobject java_canvas) {
  ......

  if (!hardware_enabled_) {
    hardware_enabled_ = compositor_->InitializeHwDraw();
    ......
  }

  ......

  scoped_ptr<DrawGLInput> draw_gl_input(new DrawGLInput);
  ......

  scoped_ptr<cc::CompositorFrame> frame =
      compositor_->DemandDrawHw(surface_size,
                                gfx::Transform(),
                                viewport,
                                clip,
                                viewport_rect_for_tile_priority,
                                transform_for_tile_priority);
  ......

  shared_renderer_state_->SetDrawGLInput(draw_gl_input.Pass());
  ......
  return client_->RequestDrawGL(java_canvas, false);
}
       这个函数定义在文件external/chromium_org/android_webview/browser/browser_view_renderer.cc中。

       当BrowserViewRenderer类的成员变量hardware_enabled_的值等于false时,表示Android WebView是第一次启用硬件加速渲染。在这种情况下,BrowserViewRenderer类的函数OnDraw首先会初始化一个硬件加速渲染环境,然后再对Android WebView进行绘制。

       从前面的分析可以知道,BrowserViewRenderer类的成员变量compositor_指向的是一个SynchronousCompositorImpl对象。BrowserViewRenderer类的函数OnDraw就是通过调用这个SynchronousCompositorImpl对象的成员函数InitializeHwDraw初始化一个硬件加速渲染环境的。在初始化这个硬件加速渲染环境的过程中,就会创建一个In-Process Command Buffer GL接口。

       接下来,我们就继续分析SynchronousCompositorImpl类的成员函数InitializeHwDraw初始化创建In-Process Command Buffer GL接口的过程。在接下来一篇文章中,我们再分析BrowserViewRenderer类的函数OnDraw绘制Android WebView的过程。

       SynchronousCompositorImpl类的成员函数InitializeHwDraw的实现如下所示:

base::LazyInstance<SynchronousCompositorFactoryImpl>::Leaky g_factory =
    LAZY_INSTANCE_INITIALIZER;

......

bool SynchronousCompositorImpl::InitializeHwDraw() {
  ......

  scoped_refptr<cc::ContextProvider> onscreen_context =
      g_factory.Get().CreateOnscreenContextProviderForCompositorThread();

  bool success = output_surface_->InitializeHwDraw(onscreen_context);

  ......
  return success;
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

       全局变量指向的就是前面提到的用来为网页创建Output Surface的SynchronousCompositorFactoryImpl对象。SynchronousCompositorImpl类的成员函数InitializeHwDraw调用这个SynchronousCompositorFactoryImpl对象的成员函数CreateOnscreenContextProviderForCompositorThread创建一个硬件加速渲染环境。

       创建出来的硬件加速渲染环境是通过一个ContextProviderInProcess对象描述的。这个ContextProviderInProcess对象又会设置给SynchronousCompositorImpl类的成员变量output_surface_描述的一个Synchronous Compositor Output Surface。Synchronous Compositor Output Surface有了硬件加速渲染环境之后,就可以执行GPU命令了。

       接下来,我们首先分析SynchronousCompositorFactoryImpl类的成员函数CreateOnscreenContextProviderForCompositorThread创建硬件加速渲染环境的过程,如下所示:

scoped_refptr<cc::ContextProvider> SynchronousCompositorFactoryImpl::
    CreateOnscreenContextProviderForCompositorThread() {
  ......
  return webkit::gpu::ContextProviderInProcess::Create(
      WrapContext(CreateContext(service_, share_context_.get())),
      "Child-Compositor");
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。

       SynchronousCompositorFactoryImpl类的成员函数CreateOnscreenContextProviderForCompositorThread首先调有函数CreateContext创建一个In-Process Command Buffer GL接口,然后再调用另外一个函数WrapContext将这个In-Process Command Buffer GL接口封装在一个WebGraphicsContext3DInProcessCommandBufferImpl对象,最后调用ContextProviderInProcess类的静态成员函数Create将该WebGraphicsContext3DInProcessCommandBufferImpl对象封装在一个ContextProviderInProcess对象中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,SynchronousCompositorFactoryImpl类的成员变量service_指向的就是一个DeferredGpuCommandService服务。函数CreateContext在创建In-Process Command Buffer GL接口的时候,会使用到这个DeferredGpuCommandService服务,如下所示:

scoped_ptr<gpu::GLInProcessContext> CreateContext(
    scoped_refptr<gpu::InProcessCommandBuffer::Service> service,
    gpu::GLInProcessContext* share_context) {
  ......

  scoped_ptr<gpu::GLInProcessContext> context(
      gpu::GLInProcessContext::Create(service,
                                      NULL /* surface */,
                                      false /* is_offscreen */,
                                      gfx::kNullAcceleratedWidget,
                                      gfx::Size(1, 1),
                                      share_context,
                                      false /* share_resources */,
                                      in_process_attribs,
                                      gpu_preference));
  return context.Pass();
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。 

       函数调用GLInProcessContext类的静态成员函数Create创建了一个In-Process Command Buffer GL接口,如下所示:

GLInProcessContext* GLInProcessContext::Create(
    scoped_refptr<gpu::InProcessCommandBuffer::Service> service,
    scoped_refptr<gfx::GLSurface> surface,
    bool is_offscreen,
    gfx::AcceleratedWidget window,
    const gfx::Size& size,
    GLInProcessContext* share_context,
    bool use_global_share_group,
    const GLInProcessContextAttribs& attribs,
    gfx::GpuPreference gpu_preference) {
  ......

  scoped_ptr<GLInProcessContextImpl> context(new GLInProcessContextImpl());
  if (!context->Initialize(surface,
                           is_offscreen,
                           use_global_share_group,
                           share_context,
                           window,
                           size,
                           attribs,
                           gpu_preference,
                           service))
    return NULL;

  return context.release();
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gl_in_process_context.cc中。

       GLInProcessContext类的静态成员函数Create首先创建了一个GLInProcessContextImpl对象,然后调用这个GLInProcessContextImpl对象的成员函数Initialize对其进行初始化。在初始化的过程中,就会创建一个In-Process Command Buffer GL接口,如下所示:

bool GLInProcessContextImpl::Initialize(
    scoped_refptr<gfx::GLSurface> surface,
    bool is_offscreen,
    bool use_global_share_group,
    GLInProcessContext* share_context,
    gfx::AcceleratedWidget window,
    const gfx::Size& size,
    const GLInProcessContextAttribs& attribs,
    gfx::GpuPreference gpu_preference,
    const scoped_refptr<InProcessCommandBuffer::Service>& service) {
  ......

  command_buffer_.reset(new InProcessCommandBuffer(service));
  ......

  if (!command_buffer_->Initialize(surface,
                                   is_offscreen,
                                   window,
                                   size,
                                   attrib_vector,
                                   gpu_preference,
                                   wrapped_callback,
                                   share_command_buffer)) {
    ......
    return false;
  }

  // Create the GLES2 helper, which writes the command buffer protocol.
  gles2_helper_.reset(new gles2::GLES2CmdHelper(command_buffer_.get()));
  ......

  // Create the object exposing the OpenGL API.
  gles2_implementation_.reset(new gles2::GLES2Implementation(
      gles2_helper_.get(),
      share_group,
      transfer_buffer_.get(),
      bind_generates_resources,
      attribs.lose_context_when_out_of_memory > 0,
      command_buffer_.get()));
  ......

  return true;
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gl_in_process_context.cc中。

       GLInProcessContextImpl类的成员函数Initialize创建In-Process Command Buffer GL接口的过程与前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文提到的Command Buffer GL接口的创建过程是类似的。首先是创建一个Command Buffer,然后再将该Command Buffer封装在一个GLES2CmdHelper对象中。以后通过个GLES2CmdHelper对象就可以将要执行的GPU命令写入到它封装的Command Buffer中去。最后又会使用前面封装得到的GLES2CmdHelper对象创建一个GLES2Implementation对象。这个GLES2Implementation对象就用来描述的一个In-Process Command Buffer GL或者Command Buffer GL接口。

       In-Process Command Buffer GL接口与Command Buffer GL接口的最大区别就在于它们使用了不同的Command Buffer。In-Process Command Buffer GL使用的Command Buffer是一个In-Process Command Buffer,而Command Buffer GL接口使用的Command Buffer是一个Command Buffer Proxy。In-Process Command Buffer会将要执行的GPU命令发送给App的Render Thread处理,而Command Buffer Proxy会将要执行的GPU命令发送给Chromium的GPU进程/线程处理。

       接下来,我们就重点分析In-Process Command Buffer的创建过程,以便后面可以更好地理解它是怎么将要执行的GPU命令发送给App的Render Thread处理的。

       从前面的调用过程可以知道,参数service描述的是一个DeferredGpuCommandService服务。这个DeferredGpuCommandService服务将会用来创建In-Process Command Buffer,如下所示:

InProcessCommandBuffer::InProcessCommandBuffer(
    const scoped_refptr<Service>& service)
    : ......,
      service_(service.get() ? service : GetDefaultService()),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       在创建In-Process Command Buffer的过程中,如果指定了一个Service,那么以后就会通过这个Service执行GPU命令。在我们这个情景中,指定的Service即为一个DeferredGpuCommandService服务。因此,这时候InProcessCommandBuffer类的成员变量service_指向的是一个DeferredGpuCommandService服务。

       如果在创建In-Process Command Buffer的过程中,没有指定一个Service,那么InProcessCommandBuffer的构造函数就会通过调用另外一个成员函数GetDefaultService获得一个默认的Service,用来执行GPU命令。这个默认的Service实际上就是一个自行创建的GPU线程。

       回到GLInProcessContextImpl类的成员函数Initialize中,它创建了一个In-Process Command Buffer之后,接下来还会调用这个InProcessCommandBuffer类的成员函数Initialize对它进行初始化,如下所示:

bool InProcessCommandBuffer::Initialize(
    scoped_refptr<gfx::GLSurface> surface,
    bool is_offscreen,
    gfx::AcceleratedWidget window,
    const gfx::Size& size,
    const std::vector<int32>& attribs,
    gfx::GpuPreference gpu_preference,
    const base::Closure& context_lost_callback,
    InProcessCommandBuffer* share_group) {
  ......

  gpu::Capabilities capabilities;
  InitializeOnGpuThreadParams params(is_offscreen,
                                     window,
                                     size,
                                     attribs,
                                     gpu_preference,
                                     &capabilities,
                                     share_group);

  base::Callback<bool(void)> init_task =
      base::Bind(&InProcessCommandBuffer::InitializeOnGpuThread,
                 base::Unretained(this),
                 params);

  base::WaitableEvent completion(true, false);
  bool result = false;
  QueueTask(
      base::Bind(&RunTaskWithResult<bool>, init_task, &result, &completion));
  completion.Wait();

  ......
  return result;
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       InProcessCommandBuffer类的成员函数Initialize所做的事情就是通过成员变量service_指向的DeferredGpuCommandService服务请求在App的Render Thread中调用InProcessCommandBuffer类的成员函数InitializeOnGpuThread,以便对当前正在创建的In-Process Command Buffer进行初始化。

       后面分析Render端执行的GPU命令的过程时,我们就会清楚地看到DeferredGpuCommandService服务是如何请求在App的Render Thread执行一个操作的。现在我们主要关注In-Process Command Buffer的初始化过程,也就是InProcessCommandBuffer类的成员函数InitializeOnGpuThread的实现,如下所示:

bool InProcessCommandBuffer::InitializeOnGpuThread(
    const InitializeOnGpuThreadParams& params) {
  ......

  scoped_ptr<CommandBufferService> command_buffer(
      new CommandBufferService(transfer_buffer_manager_.get()));
  command_buffer->SetPutOffsetChangeCallback(base::Bind(
      &InProcessCommandBuffer::PumpCommands, gpu_thread_weak_ptr_));
  ......

  decoder_.reset(gles2::GLES2Decoder::Create(
      params.context_group
          ? params.context_group->decoder_->GetContextGroup()
          : new gles2::ContextGroup(NULL,
                                    NULL,
                                    NULL,
                                    service_->shader_translator_cache(),
                                    NULL,
                                    bind_generates_resource)));

  gpu_scheduler_.reset(
      new GpuScheduler(command_buffer.get(), decoder_.get(), decoder_.get()));
  ......
  command_buffer_ = command_buffer.Pass();

  decoder_->set_engine(gpu_scheduler_.get());

  ......
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       InProcessCommandBuffer类的成员函数InitializeOnGpuThread首先是创建了一个Command Buffer Service,并且保存在成员变量command_buffer_中。这个Command Buffer Service负责管理Command Buffer的状态,例如第一个等待执行的GPU命令的位置,以及最新写入的GPU命令的位置,等等。

       InProcessCommandBuffer类的成员函数InitializeOnGpuThread创建了一个Command Buffer Service,会调用它的成员函数SetPutOffsetChangeCallback,用来设置一个Put Offset Change Callback,如下所示:

void CommandBufferService::SetPutOffsetChangeCallback(
    const base::Closure& callback) {
  put_offset_change_callback_ = callback;
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/command_buffer_service.cc中。

       这个Callback指定为当前正在创建的In-Process Command Buffer的成员函数PumpCommands。当我们往Command Buffer写入了新的GPU命令时,这个Callback就会被执行,也就是InProcessCommandBuffer类的成员函数PumpCommands会被调用。

       InProcessCommandBuffer类的成员函数PumpCommands在调用的过程中,就会通过一个Gpu Scheduler和一个GLES2 Decoder执行新写入到Command Buffer中的GPU命令。Gpu Scheduler在执行一个GPU命令之前,会将当前的OpenGL上下文切换至该GPU命令所属的OpenGL上下文,这样它就可以同时支持多个OpenGL上下文。GLES2 Decoder负责从Command Buffer中解析出每一个待执行的GPU命令及其携带的参数,这样就可以通过调用对应的OpenGL函数执行它们。关于Gpu Scheduler和一个GLES2 Decoder执行GPU命令的过程,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       回到前面分析的InProcessCommandBuffer类的成员函数InitializeOnGpuThread中,上述Gpu Scheduler和GLES2 Decoder也是在InProcessCommandBuffer类的成员函数InitializeOnGpuThread中创建的。创建出来之后,就分别保存在InProcessCommandBuffer类的成员变量gpu_scheduler_和decoder_中。

       这一步执行完成后,回到前面分析的SynchronousCompositorFactoryImpl类的成员函数CreateOnscreenContextProviderForCompositorThread中,这时候它就获得了一个In-Process Command Buffer GL接口。这个In-Process Command Buffer GL接口是封装在一个GLInProcessContextImpl对象中的。这个GLInProcessContextImpl对象接下来又会通过函数WrapContext封装在一个WebGraphicsContext3DInProcessCommandBufferImpl对象中,如下所示:

scoped_ptr<WebGraphicsContext3DInProcessCommandBufferImpl> WrapContext(
    scoped_ptr<gpu::GLInProcessContext> context) {
  ......

  return scoped_ptr<WebGraphicsContext3DInProcessCommandBufferImpl>(
      WebGraphicsContext3DInProcessCommandBufferImpl::WrapContext(
          context.Pass(), GetDefaultAttribs()));
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_factory_impl.cc中。

       函数WrapContext通过调用WebGraphicsContext3DInProcessCommandBufferImpl类的静态成员函数WrapContext将一个GLInProcessContextImpl对象封装在一个WebGraphicsContext3DInProcessCommandBufferImpl对象中,如下所示:

scoped_ptr<WebGraphicsContext3DInProcessCommandBufferImpl>
WebGraphicsContext3DInProcessCommandBufferImpl::WrapContext(
    scoped_ptr< ::gpu::GLInProcessContext> context,
    const blink::WebGraphicsContext3D::Attributes& attributes) {
  bool lose_context_when_out_of_memory = false;  // Not used.
  bool is_offscreen = true;                      // Not used.
  return make_scoped_ptr(new WebGraphicsContext3DInProcessCommandBufferImpl(
      context.Pass(),
      attributes,
      lose_context_when_out_of_memory,
      is_offscreen,
      gfx::kNullAcceleratedWidget /* window. Not used. */));
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc中。

       WebGraphicsContext3DInProcessCommandBufferImpl类的构造函数会将要封装的GLInProcessContextImpl对象保存在成员变量context_中,如下所示:

WebGraphicsContext3DInProcessCommandBufferImpl::
    WebGraphicsContext3DInProcessCommandBufferImpl(
        scoped_ptr< ::gpu::GLInProcessContext> context,
        const blink::WebGraphicsContext3D::Attributes& attributes,
        bool lose_context_when_out_of_memory,
        bool is_offscreen,
        gfx::AcceleratedWidget window)
    : ......,
      context_(context.Pass()) {
  ......
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc中。

       这一步执行完成后,继续回到前面分析的SynchronousCompositorFactoryImpl类的成员函数CreateOnscreenContextProviderForCompositorThread中,这时候它就获得了一个WebGraphicsContext3DInProcessCommandBufferImpl对象。这个WebGraphicsContext3DInProcessCommandBufferImpl对象通过一个GLInProcessContextImpl对象间接地保存了前面创建的In-Process Command Buffer GL接口

       SynchronousCompositorFactoryImpl类的成员函数CreateOnscreenContextProviderForCompositorThread最后又会调用ContextProviderInProcess类的静态成员函数Create将上述WebGraphicsContext3DInProcessCommandBufferImpl对象封装在一个ContextProviderInProcess对象中,如下所示:

scoped_refptr<ContextProviderInProcess> ContextProviderInProcess::Create(
    scoped_ptr<WebGraphicsContext3DInProcessCommandBufferImpl> context3d,
    const std::string& debug_name) {
  ......
  return new ContextProviderInProcess(context3d.Pass(), debug_name);
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/context_provider_in_process.cc中。

       ContextProviderInProcess对象会将要封装的WebGraphicsContext3DInProcessCommandBufferImpl对象保存在成员变量context_中,如下所示:

ContextProviderInProcess::ContextProviderInProcess(
    scoped_ptr<WebGraphicsContext3DInProcessCommandBufferImpl> context3d,
    const std::string& debug_name)
    : context3d_(context3d.Pass()),
      ...... {
  ......
}
      这个函数定义在文件external/chromium_org/webkit/common/gpu/context_provider_in_process.cc中。

      这一步执行完成后,SynchronousCompositorImpl类的成员函数InitializeHwDraw,这时候它就是初始化了一个硬件加速渲染环境。这个硬件加速渲染环境就是通过前面创建的ContextProviderInProcess对象描述。这个ContextProviderInProcess对象接来会设置给SynchronousCompositorImpl类的成员变量output_surface_描述的一个Synchronous Compositor Output Surface。这是通过调用SynchronousCompositorOutputSurface类的成员函数InitializeHwDraw实现的,如下所示:

bool SynchronousCompositorOutputSurface::InitializeHwDraw(
    scoped_refptr<cc::ContextProvider> onscreen_context_provider) {
  ......

  return InitializeAndSetContext3d(onscreen_context_provider);
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       SynchronousCompositorOutputSurface类的成员函数InitializeHwDraw会调用另外一个成员函数InitializeAndSetContext3d将参数onscreen_context_provider指向的ContextProviderInProcess对象保存在内部。以后通过这个ContextProviderInProcess对象,就可以获得前面创建的In-Process Command Buffer GL接口了。

       SynchronousCompositorOutputSurface类的成员函数InitializeAndSetContext3d是从父类OutputSurface继承下来的,它的实现如下所示:

bool OutputSurface::InitializeAndSetContext3d(
    scoped_refptr<ContextProvider> context_provider) {
  ......

  bool success = false;
  if (context_provider->BindToCurrentThread()) {
    context_provider_ = context_provider;
    ......
    success = true;
  }

  ......

  return success;
}
       这个函数定义在文件external/chromium_org/cc/output/output_surface.cc中。

       OutputSurface类的成员函数InitializeAndSetContext3d首先会调用参数context_provider指向的ContextProviderInProcess对象的成员函数BindToCurrentThread将其引用的In-Process Command Buffer GL接口设置为当前线程所使用的GL接口。当前线程即为Render端的Compositor线程。有了这个GL接口之后,Render端的Compositor线程就可以执行GPU操作了。

       成功将参数context_provider指向的ContextProviderInProcess对象引用的In-Process Command Buffer GL接口设置为当前线程所使用的GL接口之后,该ContextProviderInProcess对象就会保存在OutputSurface类的成员变量context_provider_中。

       接下来我们继续分析ContextProviderInProcess类的成员函数BindToCurrentThread为当前线程设置Process Command Buffer GL接口的过程,如下所示:

bool ContextProviderInProcess::BindToCurrentThread() {
  ......

  if (!context3d_->makeContextCurrent())
    return false;

  ......
  return true;
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/context_provider_in_process.cc中。

       从前面的分析可以知道,ContextProviderInProcess类的成员变量context3d_指向的是一个WebGraphicsContext3DInProcessCommandBufferImpl对象。ContextProviderInProcess类的成员函数BindToCurrentThread会调用这个WebGraphicsContext3DInProcessCommandBufferImpl对象的成员函数makeContextCurrent将前面创建的In-Process Command Buffer GL接口设置为当前线程所使用的GL接口,如下所示:

bool WebGraphicsContext3DInProcessCommandBufferImpl::makeContextCurrent() {
  if (!MaybeInitializeGL())
    return false;
  ::gles2::SetGLContext(GetGLInterface());
  return context_ && !isContextLost();
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc中。

       WebGraphicsContext3DInProcessCommandBufferImpl类的成员函数makeContextCurrent首先调用成员函数MaybeInitializeGL检查是否已经为当前线程初始化过GL接口了。如果还没有初始化,那么就会进行初始化,如下所示:

bool WebGraphicsContext3DInProcessCommandBufferImpl::MaybeInitializeGL() {
  if (initialized_)
    return true;
  ......

  real_gl_ = context_->GetImplementation();
  setGLInterface(real_gl_);

  ......

  initialized_ = true;
  return true;
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc中。

       当WebGraphicsContext3DInProcessCommandBufferImpl类的成员变量initialized_的值等于true的时候,就表示当前线程初始化过GL接口了。另一方面,如果当前线程还没有初始化过GL接口,那么WebGraphicsContext3DInProcessCommandBufferImpl类的成员函数MaybeInitializeGL就会进行初始化。

       从前面的分析可以知道,WebGraphicsContext3DInProcessCommandBufferImpl类的成员变量context_指向的是一个GLInProcessContextImpl对象。调用这个GLInProcessContextImpl对象的成员函数可以获得它内部封装一个GLES2Implementation对象。这个GLES2Implementation对象描述的就是一个In-Process Command Buffer GL接口。

       WebGraphicsContext3DInProcessCommandBufferImpl类的成员函数MaybeInitializeGL获得了上述In-Process Command Buffer GL接口之后,会调用另外一个成员函数setGLInterface将其保存起来。

       WebGraphicsContext3DInProcessCommandBufferImpl类的成员函数setGLInterface是从父类WebGraphicsContext3DImpl继承下来的,它的实现如下所示:

class WEBKIT_GPU_EXPORT WebGraphicsContext3DImpl  
    : public NON_EXPORTED_BASE(blink::WebGraphicsContext3D) {  
 public:  
  ......  
  
  ::gpu::gles2::GLES2Interface* GetGLInterface() {  
    return gl_;  
  }  
  
 protected:  
  ......  
  
  void setGLInterface(::gpu::gles2::GLES2Interface* gl) {  
    gl_ = gl;  
  }  
  
  ......  
  
  ::gpu::gles2::GLES2Interface* gl_;  
  ......  
};  
       这个函数定义在文件external/chromium_org/webkit/common/gpu/webgraphicscontext3d_impl.h中。

       WebGraphicsContext3DImpl类的成员函数setGLInterface将参数描述的In-Process Command Buffer GL接口保存在成员变量gl_中。这个In-Process Command Buffer GL接口可以通过调用WebGraphicsContext3DImpl类的另外一个成员函数GetGLInterface获得。

       这一步执行完成后,回到前面分析的WebGraphicsContext3DInProcessCommandBufferImpl类的成员函数makeContextCurrent,它接下来又会调用从父类WebGraphicsContext3DImpl继承下来的成员函数GetGLInterface获得前面所保存的In-Process Command Buffer GL接口,并且将该In-Process Command Buffer GL接口设置为当前线程的GL接口,也就是OpenGL调用接口。这是通过调用函数gles2::SetGLContext实现的,如下所示:

static gpu::ThreadLocalKey g_gl_context_key;   

......
 
gpu::gles2::GLES2Interface* GetGLContext() {  
  return static_cast<gpu::gles2::GLES2Interface*>(  
    gpu::ThreadLocalGetValue(g_gl_context_key));  
}
 
void SetGLContext(gpu::gles2::GLES2Interface* context) {  
  gpu::ThreadLocalSetValue(g_gl_context_key, context);  
}  
       这两个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_lib.cc中。

       参数context描述的In-Process Command Buffer GL接口将被保存在全局变量g_gl_context_key所描述的一个线程局部储存中,作为当前线程所使用的OpenGL接口。以后通过调用另外一个函数gles2::GetGLContext就可以获得保存在这个线程局部储存中的In-Process Command Buffer GL接口。

       这一步执行完成后,以后Render端的Compositor线程直接调用OpenGL函数glXXX时,就会通过In-Process Command Buffer GL接口执行指定的GPU的操作。从OpenGL函数glXXX调用到In-Process Command Buffer GL接口的原理可以参考前面Chromium网页GPU光栅化原理分析一文。

       Render端的Compositor线程除了可以通过OpenGL函数glXXX使用In-Process Command Buffer GL接口,还可以通过它为网页创建的Synchronous Compositor Output Surface使用In-Process Command Buffer GL接口。接下来,我们就以Render端的Compositor线程执行GPU光栅化操作为例,说明它执行GPU命令的过程。

       从前面Chromium网页GPU光栅化原理分析一文可以知道,当Render端的Compositor线程是通过一个Skia Canvas对网页UI进行GPU光栅化操作的。这个Skia Canvas又是通过一个类型为SkSurface_Gpu的Skia Surface获得的。这个类型为SkSurface_Gpu的Skia Surface是通过调用DirectRasterBuffer类的成员函数CreateSurface创建的,如下所示:

skia::RefPtr<SkSurface> ResourceProvider::DirectRasterBuffer::CreateSurface() {  
  skia::RefPtr<SkSurface> surface;  
  switch (resource()->type) {  
    case GLTexture: {  
      ......  
      class GrContext* gr_context = resource_provider()->GrContext();  
      if (gr_context) {  
        GrBackendTextureDesc desc;  
        ......
 
        skia::RefPtr<GrTexture> gr_texture =  
            skia::AdoptRef(gr_context->wrapBackendTexture(desc));  
        ......
  
        surface = skia::AdoptRef(SkSurface::NewRenderTargetDirect(  
            gr_texture->asRenderTarget(), text_render_mode));  
      }  
      break;  
    }  
    ......
  }  
  return surface;  
}
       这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。

       DirectRasterBuffer类的成员函数CreateSurface的详细实现可以参考前面Chromium网页GPU光栅化原理分析一文。这里我们所关注的重点是它通过调用ResourceProvider类的成员函数GrContext获得一个In-Process Command Buffer GL接口的过程。这个In-Process Command Buffer GL接口会传递给这里所创建的类型为SkSurface_Gpu的Skia Surface。有了In-Process Command Buffer GL接口之后,类型为SkSurface_Gpu的Skia Surface就可以通过GPU对网页的UI进行光栅化了。

       DirectRasterBuffer类的成员函数CreateSurface首先是调用成员函数resource_provider获得一个ResourceProvider对象。这个ResourceProvider对象负责管理网页在渲染过程中所要使用到的资源。有这个ResourceProvider对象之后,就可以调用它的成员函数GrContext获得一个In-Process Command Buffer GL接口,如下所示:

class GrContext* ResourceProvider::GrContext() const {
  ContextProvider* context_provider = output_surface_->context_provider();
  return context_provider ? context_provider->GrContext() : NULL;
}
      这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。

      ResourceProvider类的成员变量output_surface_指向的就是一个SynchronousCompositorOutputSurface对象。ResourceProvider类的成员函数GrContext会调用这个SynchronousCompositorOutputSurface对象的成员函数context_provider获得一个Context Provider。

      SynchronousCompositorOutputSurface对象的成员函数context_provider是从父类OutputSurface继承下来的,它的实现如下所示:

class CC_EXPORT OutputSurface {
  ......

  scoped_refptr<ContextProvider> context_provider() const {
    return context_provider_.get();
  }

  ......
};
       这个函数定义在文件external/chromium_org/cc/output/output_surface.h中。

       从前面的分析可以知道,此时OutputSurface类的成员变量context_provider_指向的是一个ContextProviderInProcess对象。OutputSurface类的成员函数context_provider将这个ContextProviderInProcess对象返回给调用者。

       回到前面分析的ResourceProvider类的成员函数GrContext中,这时候它就获得了一个ContextProviderInProcess对象。接下来它继续调用这个ContextProviderInProcess对象的成员函数GrContext获得一个GrContextForWebGraphicsContext3D对象,如下所示:

class GrContext* ContextProviderInProcess::GrContext() {
  ......

  if (gr_context_)
    return gr_context_->get();

  gr_context_.reset(
      new webkit::gpu::GrContextForWebGraphicsContext3D(context3d_.get()));
  return gr_context_->get();
}
       这个函数定义在文件external/chromium_org/webkit/common/gpu/context_provider_in_process.cc中。

       ContextProviderInProcess类的成员函数GrContext首先判断成员变量gr_context_是否指向了一个GrContextForWebGraphicsContext3D对象。如果已经指向,那么就会将该GrContextForWebGraphicsContext3D对象返回给调用者。

       另一方面,如果ContextProviderInProcesGrContextForWebGraphicsContext3Ds类的成员变量gr_context_还没有指向一个GrContextForWebGraphicsContext3D对象,那么ContextProviderInProcess类的成员函数GrContext就会先创建该GrContextForWebGraphicsContext3D对象,然后再将它返回给调用者。

       在创建GrContextForWebGraphicsContext3D对象的时候,会使用到ContextProviderInProcess类的成员变量context3d_。从前面的分析可以知道,ContextProviderInProcess类的成员变量context3d_指向的是一个WebGraphicsContext3DInProcessCommandBufferImpl对象。这个WebGraphicsContext3DInProcessCommandBufferImpl对象内部封装了一个In-Process Command Buffer GL接口。

       因此,ContextProviderInProcess类的成员函数GrContext创建出来的GrContextForWebGraphicsContext3D对象也会间接地引用了一个In-Process Command Buffer GL接口。前面创建的类型为SkSurface_Gpu的Skia Surface在光栅化网页UI的时候,就会使用到这个In-Process Command Buffer GL接口,也就是会将要执行的GPU命令写入到一个In-Process Command Buffer中去。

       写入到In-Process Command Buffer中的命令会被周期性地提交给DeferredGpuCommandService服务处理。这是通过调用InProcessCommandBuffer类的成员函数Flush实现的。或者我们也可以主动地调用In-Process Command Buffer GL接口提供的成员函数Flush将写入在In-Process Command Buffer中的命令会被周期性地提交给DeferredGpuCommandService服务。这个主动提交的操作最终也是通过调用InProcessCommandBuffer类的成员函数Flush实现的。这一点可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       接下来,我们就从InProcessCommandBuffer类的成员函数Flush开始,分析Render端在使用GPU光栅化网页UI的过程中,是如何执行GPU命令的,如下所示:

void InProcessCommandBuffer::Flush(int32 put_offset) {
  ......

  last_put_offset_ = put_offset;
  base::Closure task = base::Bind(&InProcessCommandBuffer::FlushOnGpuThread,
                                  gpu_thread_weak_ptr_,
                                  put_offset);
  QueueTask(task);
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       参数put_offset表示最新写入的GPU命令在In-Process Command Buffer中的位置。InProcessCommandBuffer类的成员函数Flush首先将这个位置记录在成员变量last_put_offset_中。

       InProcessCommandBuffer类的成员函数Flush接下来创建了一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread。接下来这个Task会被提交给DeferredGpuCommandService服务处理。这是通过调用InProcessCommandBuffer类的成员函数QueueTask实现的,如下所示:

class GPU_EXPORT InProcessCommandBuffer : public CommandBuffer,
                                          public GpuControl {
 ......

 private:
  ......

  void QueueTask(const base::Closure& task) { service_->ScheduleTask(task); }

  ......
}
      这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.h中。

      从前面的分析可以知道,InProcessCommandBuffer类的成员变量service_描述的就是一个DeferredGpuCommandService服务。InProcessCommandBuffer类的成员函数QueueTask通过调用这个DeferredGpuCommandService服务的成员函数ScheduleTask调度执行参数task描述的Task。

      DeferredGpuCommandService类的成员函数ScheduleTask的实现如下所示:

void DeferredGpuCommandService::ScheduleTask(const base::Closure& task) {
  {
    base::AutoLock lock(tasks_lock_);
    tasks_.push(task);
  }
  if (ScopedAllowGL::IsAllowed()) {
    RunTasks();
  } else {
    RequestProcessGL();
  }
}
       这个函数定义在文件external/chromium_org/android_webview/browser/deferred_gpu_command_service.cc中。

       DeferredGpuCommandService类的成员函数ScheduleTask首先将参数task描述的Task保存在成员变量tasks_描述的一个std::queue中。

       DeferredGpuCommandService类的成员函数ScheduleTask接下来通过调用ScopedAllowGL类的静态成员函数IsAllowed判断当前线程是否允许直接执行GPU命令,也就是当前线程是否是一个GPU线程。如果是的话,那么就会调用成员函数RunTasks执行参数task描述的Task,如下所示:

void DeferredGpuCommandService::RunTasks() {
  bool has_more_tasks;
  {
    base::AutoLock lock(tasks_lock_);
    has_more_tasks = tasks_.size() > 0;
  }

  while (has_more_tasks) {
    base::Closure task;
    {
      base::AutoLock lock(tasks_lock_);
      task = tasks_.front();
      tasks_.pop();
    }
    task.Run();
    {
      base::AutoLock lock(tasks_lock_);
      has_more_tasks = tasks_.size() > 0;
    }
  }
}
       这个函数定义在文件external/chromium_org/android_webview/browser/deferred_gpu_command_service.cc中。

       DeferredGpuCommandService类的成员函数RunTasks会依次执行保存在成员变量tasks_描述的std::queue中的每一个Task。从前面的分析可以知道,这个std::queue保存了一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread。因此,当该Task被执行的时候,InProcessCommandBuffer类的成员函数FlushOnGpuThread就会被调用。在调用的过程中,就会执行那些新写入到In-Process Command Buffer的GPU命令。

       DeferredGpuCommandService类的成员函数ScheduleTask可以直接调用成员函数RunTasks执行GPU命令的情况发生在Browser端合成网页UI的过程中。这时候Browser端运行在App的Render Thread中,并且它会通过ScopedAllowGL类标记App的Render Thread可以执行GPU命令。这样DeferredGpuCommandService类的成员函数ScheduleTask就可以知道它可以直接执行GPU命令了。

       在我们这个情景中,正在执行的是网页UI的光栅化操作。这个操作是发生在Render端的Compositor线程中的。Render端的Compositor线程不是一个GPU线程,因此这时候DeferredGpuCommandService类的成员函数ScheduleTask就不能直接调用成员函数RunTasks执行GPU命令,而是要通过调用另外一个成员函数RequestProcessGL请求App的Render Thread执行。

       另外一个情景,也就是Render端绘制网页UI的情景,也是发生在Render端的Compositor线程。这时候DeferredGpuCommandService类的成员函数ScheduleTask也需要调用成员函数RequestProcessGL请求App的Render Thread执行绘制网页UI所需要执行的GPU命令。

       接下来,我们就继续分析DeferredGpuCommandService类的成员函数RequestProcessGL,以便了解它请求App的Render Thread执行GPU命令的过程,如下所示:

void DeferredGpuCommandService::RequestProcessGL() {
  SharedRendererState* renderer_state =
      GLViewRendererManager::GetInstance()->GetMostRecentlyDrawn();
  ......
  renderer_state->ClientRequestDrawGL();
}
       这个函数定义在文件external/chromium_org/android_webview/browser/deferred_gpu_command_service.cc中。

       我们在前面Android WebView启动Chromium渲染引擎的过程分析一文中提到,在Chromium渲染引擎中,存在一个GLViewRendererManager单例对象。这个GLViewRendererManager单例对象记录了当前有哪些WebView是采用硬件加速方式绘制的。通过调用这个GLViewRendererManager对象的成员函数GetMostRecentlyDrawn可以获得一个SharedRendererState对象。这个SharedRendererState对象的创建过程可以参考前面Android WebView启动Chromium渲染引擎的过程分析一文,它记录了当前正在绘制的Android WebView的状态。

       获得了与当前正在绘制的Android WebView关联的一个SharedRendererState对象之后,DeferredGpuCommandService类的成员函数RequestProcessGL就可以调用它的成员函数ClientRequestDrawGL请求App的Render Thread执行GPU命令,如下所示:

void SharedRendererState::ClientRequestDrawGL() {
  if (ui_loop_->BelongsToCurrentThread()) {
    ......
    ClientRequestDrawGLOnUIThread();
  } else {
    ......
    base::Closure callback;
    {
      base::AutoLock lock(lock_);
      callback = request_draw_gl_closure_;
    }
    ui_loop_->PostTask(FROM_HERE, callback);
  }
}
       这个函数定义在文件external/chromium_org/android_webview/browser/shared_renderer_state.cc中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,SharedRendererState类的成员变量ui_loop_描述的就是Chromium的Browser端的Native UI Message Loop。通过这个Native UI Message Loop可以判断当前线程是否就是App的UI线程。如果是的话,那么SharedRendererState类的成员函数ClientRequestDrawGL就会直接调用另外一个成员函数ClientRequestDrawGLOnUIThread请求App的Render Thread执行GPU命令。

       如果当前线程不是App的UI线程,那么SharedRendererState类的成员函数ClientRequestDrawGL就会向Chromium的Browser端的Native UI Message Loop发送一个Task。这个Task由SharedRendererState类的成员变量request_draw_gl_closure_描述,它绑定的函数为SharedRendererState类的成员函数ClientRequestDrawGLOnUIThread。这样做的目的是让SharedRendererState类的成员函数ClientRequestDrawGLOnUIThread运行在App的UI线程中,以便可以通过App的UI线程来请求App的Render Thread执行GPU命令。

       SharedRendererState类的成员函数ClientRequestDrawGLOnUIThread的实现如下所示:

void SharedRendererState::ClientRequestDrawGLOnUIThread() {
  ......

  if (!client_on_ui_->RequestDrawGL(NULL, false)) {
    ......
  }
}
       这个函数定义在文件external/chromium_org/android_webview/browser/shared_renderer_state.cc中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,SharedRendererState类的成员变量client_on_ui_指向的是一个Native层的AwContents对象。SharedRendererState类的成员函数ClientRequestDrawGLOnUIThread通过调用这个AwContents对象的成员函数RequestDrawGL请求App的Render Thread执行GPU命令,如下所示:

bool AwContents::RequestDrawGL(jobject canvas, bool wait_for_completion) {
  ......
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  .......
  return Java_AwContents_requestDrawGL(
      env, obj.obj(), canvas, wait_for_completion);
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,每一个Native层的AwContents对象在Java层都有一个对应的AwContents对象。这个Java层的AwContents对象就保存在Native层的AwContents对象的成员变量java_ref_中。AwContents类的成员函数RequestDrawGL通过JNI方法Java_AwContents_requestDrawGL调用上述的Java层AwContents对象的成员函数requestDrawGL,用来请求App的Render Thread执行GPU命令。

       Java层的AwContents类的成员函数requestDrawGL的实现如下所示:

public class AwContents {
    ......

    @CalledByNative
    private boolean requestDrawGL(Canvas canvas, boolean waitForCompletion) {
        return mNativeGLDelegate.requestDrawGL(canvas, waitForCompletion, mContainerView);
    }

    ......
}
      这个函数定义在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwContents.java中。

      从前面Android WebView启动Chromium渲染引擎的过程分析一文可以知道,AwContents类的成员变量mNativeGLDelegate指向的是一个WebViewNativeGLDelegate对象。AwContents类的成员函数requestDrawGL调用这个WebViewNativeGLDelegate对象的成员函数requestDrawGL请求App的Render Thread执行GPU命令,如下所示:

class WebViewChromium implements WebViewProvider,
          WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
    ......

    private DrawGLFunctor mGLfunctor;
    ......

    private class WebViewNativeGLDelegate implements AwContents.NativeGLDelegate {
        @Override
        public boolean requestDrawGL(Canvas canvas, boolean waitForCompletion,
                View containerView) {
            if (mGLfunctor == null) {
                mGLfunctor = new DrawGLFunctor(mAwContents.getAwDrawGLViewContext());
            }
            return mGLfunctor.requestDrawGL(
                    (HardwareCanvas) canvas, containerView.getViewRootImpl(), waitForCompletion);
        }

        ......
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java中。

       WebViewNativeGLDelegate类的成员函数requestDrawGL首先判断外部类WebViewChromium的成员变量mGLFunctor是否指向了一个DrawGLFunctor对象。如果指向了,那么就会调用它的成员函数requestDrawGL请求App的Render Thread执行GPU命令。

       另一方面,如果WebViewChromium类的成员变量mGLFunctor还没有指向一个DrawGLFunctor对象,那么WebViewNativeGLDelegate类的成员函数requestDrawGL就会创建这个DrawGLFunctor对象。创建过程如下所示:

class DrawGLFunctor {
    ......

    public DrawGLFunctor(long viewContext) {
        mDestroyRunnable = new DestroyRunnable(nativeCreateGLFunctor(viewContext));
        ......
    }

    ......
}
      这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/DrawGLFunctor.java中。

      DrawGLFunctor类的构造函数首先会调用成员函数nativeCreateGLFunctor获得一个Native层的DrawGLFunctor对象,然后再使用这个Native层的DrawGLFunctor对象创建一个DestroyRunnable对象,如下所示:

class DrawGLFunctor {
    ......

    private static final class DestroyRunnable implements Runnable {
        ......
        long mNativeDrawGLFunctor;
        DestroyRunnable(long nativeDrawGLFunctor) {
            mNativeDrawGLFunctor = nativeDrawGLFunctor;
        }

        ......
    }

    ......
}
      这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/DrawGLFunctor.java中。

      DestroyRunnable类的构造函数主要就是将参数nativeDrawGLFunctor描述的Native层DrawGLFunctor对象保存在成员变量mNativeDrawGLFunctor中。

      接下来我们继续分析Native层的DrawGLFunctor对象的创建过程,也就是DrawGLFunctor类的成员函数nativeCreateGLFunctor的实现。wGLFunctor类的成员函数nativeCreateGLFunctor是一个JNI方法,它由C++层的函数CreateGLFunctor实现,如下所示:

jlong CreateGLFunctor(JNIEnv*, jclass, jlong view_context) {
  RaiseFileNumberLimit();
  return reinterpret_cast<jlong>(new DrawGLFunctor(view_context));
}
      这个函数定义在文件frameworks/webview/chromium/plat_support/draw_gl_functor.cpp中。

      从这里可以看到,函数CreateGLFunctor创建的是一个Native层的DrawGLFunctor对象。这个DrawGLFunctor对象是用来描述Chromium渲染引擎请求App的Render Thread执行的GPU操作集合。

      这一步执行完成后,回到前面分析的WebViewNativeGLDelegate类的成员函数requestDrawGL中,接下来我们继续分析它调用外部类WebViewChromium类的成员变量mGLFunctor指向一个Java层的DrawGLFunctor对象的成员函数requestDrawGL请求App的Render Thread执行GPU命令,如下所示:

class DrawGLFunctor {
    ......

    public boolean requestDrawGL(HardwareCanvas canvas, ViewRootImpl viewRootImpl,
            boolean waitForCompletion) {
        ......

        if (canvas == null) {
            viewRootImpl.invokeFunctor(mDestroyRunnable.mNativeDrawGLFunctor, waitForCompletion);
            return true;
        }

        canvas.callDrawGLFunction(mDestroyRunnable.mNativeDrawGLFunctor);
        ......

        return true;
    }

    ......
}

       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/DrawGLFunctor.java中。

       从前面的调用过程可以知道,参数canvas的值为null,另外一个参数waitForCompletion的值等于false。

       当参数canvas的值为null的时候,表示Chromium渲染引擎直接请求App的Render Thread执行GPU命令。这种情况一般就是发生在Render端光栅化网页UI的过程中。

       当参数canvas的值不等于null时,它指向一个Hardware Canvas,表示Chromium渲染引擎请求App的UI线程先将要执行的GPU命令记录在App UI的Display List中。等到该Display List同步给App的Render Thread,并且被App的Render Thread重放的时候,再执行请求的GPU命令。这种情况发生在Render端绘制网页UI的过程中。

       另外一个参数waitForCompletion表示App的UI线程是否需要同步等待App的Render Thread执行完成请求的GPU命令。

       在我们这个情景中,DrawGLFunctor类的成员函数requestDrawGL将会直接请求App的Render Thread执行GPU命令。这是通过调用参数viewRootImpl指向的一个ViewRootImpl对象的成员函数invokeFunctor实现的,如下所示:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    ......

    public void invokeFunctor(long functor, boolean waitForCompletion) {
        ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。

       ViewRootImpl类的成员函数invokeFunctor会调用ThreadedRenderer类的静态成员函数invokeFunctor请求App的Render Thread执行GPU命令,如下所示:

public class ThreadedRenderer extends HardwareRenderer {
    ......

    static void invokeFunctor(long functor, boolean waitForCompletion) {
        nInvokeFunctor(functor, waitForCompletion);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

       ThreadedRenderer类的静态成员函数invokeFunctor调用另外一个静态成员函数nInvokeFunctor请求App的Render Thread执行GPU命令。

       ThreadedRenderer类的静态成员函数nInvokeFunctor是一个JNI方法,它由C++层的函数android_view_ThreadedRenderer_invokeFunctor实现,如下所示:

static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
        jlong functorPtr, jboolean waitForCompletion) {
    Functor* functor = reinterpret_cast<Functor*>(functorPtr);
    RenderProxy::invokeFunctor(functor, waitForCompletion);
}
       这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

       从前面的分析可以知道,参数functorPtr描述的是一个Native层的DrawGLFunctor对象。这个DrawGLFunctor对象是从Functor类继承下来的,因此函数android_view_ThreadedRenderer_invokeFunctor可以将它转换为一个Functor对象。这个Functor对象会通过RenderProxy类的静态成员函数invokeFunctor提交给App的Render Thread处理,如下所示:

CREATE_BRIDGE2(invokeFunctor, RenderThread* thread, Functor* functor) {
    CanvasContext::invokeFunctor(*args->thread, args->functor);
    return NULL;
}

void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
    ATRACE_CALL();
    RenderThread& thread = RenderThread::getInstance();
    SETUP_TASK(invokeFunctor);
    args->thread = &thread;
    args->functor = functor;
    if (waitForCompletion) {
        // waitForCompletion = true is expected to be fairly rare and only
        // happen in destruction. Thus it should be fine to temporarily
        // create a Mutex
        Mutex mutex;
        Condition condition;
        SignalingRenderTask syncTask(task, &mutex, &condition);
        AutoMutex _lock(mutex);
        thread.queue(&syncTask);
        condition.wait(mutex);
    } else {
        thread.queue(task);
    }
}
       这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。

       App的Render Thread可以通过调用RenderThread类的静态成员函数getInstance获得。获得了App的Render Thread之后,RenderProxy类的静态成员函数invokeFunctor就可以往它的消息队列发送一个Task。这个Task封装参数funtor描述的一个Native层的DrawGLFunctor对象,并且它绑定了由宏CREATE_BRIDGE2定义的函数invokeFunctor。

       这意味着当上述Task被App的Render Thread调度执行的时候,函数invokeFunctor就会在App的Render Thread中执行,它主要就是通过调用CanvasContext类的静态成员函数invokeFunctor通知参数参数funtor描述的Native层DrawGLFunctor对象,现在可以执行GPU命令了。

       从前面的调用过程可以知道,参数waitForCompletion的值等于false,表示当前线程(也就是App的UI线程)不用等待App的Render Thread执行完成请求的GPU命令,于是它就可以马上返回。

       接下来我们就继续分析CanvasContext类的静态成员函数invokeFunctor的实现,如下所示:

void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
    ATRACE_CALL();
    DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
    if (thread.eglManager().hasEglContext()) {
        thread.eglManager().requireGlContext();
        mode = DrawGlInfo::kModeProcess;
    }

    thread.renderState().invokeFunctor(functor, mode, NULL);
}
       这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

       CanvasContext类的静态成员函数invokeFunctor主要就是通过调用一个RenderState对象的成员函数invokeFunctor通知参数funtor描述的一个Native层DrawGLFunctor对象执行GPU命令。这个RenderState对象记录了App的Render Thread的当前渲染状态,它可以通过调用参数thread描述的一个RenderThread对象的成员函数renderState获得。

       如果此时App的Render Thread已经初始化好了OpenGL环境,那么RenderState类的成员函数invokeFunctor将会通知参数funtor描述的Native层DrawGLFunctor对象以DrawGlInfo::kModeProcess的模式执行GPU命令。否则的话,就以DrawGlInfo::kModeProcessNoContext的模式行。由于App的Render Thread一开始就会初始化OpenGL环境,因此我们将认为RenderState类的成员函数invokeFunctor将会通知参数funtor描述的Native层DrawGLFunctor对象以DrawGlInfo::kModeProcess的模式执行GPU命令。

       RenderState类的成员函数invokeFunctor的实现如下所示:

void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
    interruptForFunctorInvoke();
    (*functor)(mode, info);
    resumeFromFunctorInvoke();
}
       这个函数定义在文件frameworks/base/libs/hwui/RenderState.cpp中。

       RenderState类的成员函数invokeFunctor主要就是调用了参数functor描述的一个Native层DrawGLFunctor对象的重载操作符函数(),用来通知它执行GPU命令,如下所示:

AwDrawGLFunction* g_aw_drawgl_function = NULL;

class DrawGLFunctor : public Functor {
 public:
  ......

  // Functor
  virtual status_t operator ()(int what, void* data) {
    ......

    AwDrawGLInfo aw_info;
    aw_info.version = kAwDrawGLInfoVersion;
    switch (what) {
      case DrawGlInfo::kModeDraw: {
        aw_info.mode = AwDrawGLInfo::kModeDraw;
        ......
        break;
      }
      case DrawGlInfo::kModeProcess:
        aw_info.mode = AwDrawGLInfo::kModeProcess;
        break;
      case DrawGlInfo::kModeProcessNoContext:
        aw_info.mode = AwDrawGLInfo::kModeProcessNoContext;
        break;
      case DrawGlInfo::kModeSync:
        aw_info.mode = AwDrawGLInfo::kModeSync;
        break;
      default:
        ALOGE("Unexpected DrawGLInfo type %d", what);
        return DrawGlInfo::kStatusDone;
    }

    // Invoke the DrawGL method.
    g_aw_drawgl_function(view_context_, &aw_info, NULL);

    return DrawGlInfo::kStatusDone;
  }

  ......
};
       这个函数定义在文件frameworks/webview/chromium/plat_support/draw_gl_functor.cpp中。

       DrawGLFunctor类的重载操作符函数()主要就是调用全局变量g_aw_drawgl_function描述的一个DrawGL函数执行GPU命令,并且告知该DrawGL函数,App的Render Thread当前处于什么状态。这个状态由参数what描述的GPU执行模式决定。

       App的Render Thread有四种状态:

       1. AwDrawGLInfo::kModeDraw:表示App的Render Thread正在重放App UI的Display List。

       2. DrawGlInfo::kModeProcess:表示App的Render Thread正在执行外界请求的GPU命令,并且App的Render Thread当前具有OpenGL上下文。

       3. AwDrawGLInfo::kModeProcessNoContext:表示App的Render Thread正在执行外界请求的GPU命令,但是App的Render Thread当前没有OpenGL上下文。

       4, AwDrawGLInfo::kModeSync:表示App的Render Thread正在同步的App的UI线程的Display List。

       在我们这个情景中,App的Render Thread正处于第2种状态。第3种状态几乎不会出现,我们不予考虑。第1种和第4种状态我们在接下来一篇文章分析Android WebView渲染网页UI的过程中再详细分析。

       从前面的分析可以知道,全局变量g_aw_drawgl_function描述的DrawGL函数是由Android WebView在启动Chromium渲染引擎时注册的,它指向的函数为DrawGLFunction,它的实现如下所示:

static void DrawGLFunction(long view_context,
                           AwDrawGLInfo* draw_info,
                           void* spare) {
  // |view_context| is the value that was returned from the java
  // AwContents.onPrepareDrawGL; this cast must match the code there.
  reinterpret_cast<android_webview::AwContents*>(view_context)
      ->DrawGL(draw_info);
}
      这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

      参数view_context描述的是一个Native层的AwContents对象。函数DrawGLFunction主要就是调用这个AwContents对象的成员函数DrawGL执行GPU命令,如下所示:

void AwContents::DrawGL(AwDrawGLInfo* draw_info) {
  if (draw_info->mode == AwDrawGLInfo::kModeSync) {
    if (hardware_renderer_)
      hardware_renderer_->CommitFrame();
    return;
  }

  ......

  ScopedAllowGL allow_gl;
  ......

  if (draw_info->mode != AwDrawGLInfo::kModeDraw) {
    ......
    return;
  }

  if (!hardware_renderer_) {
    hardware_renderer_.reset(new HardwareRenderer(&shared_renderer_state_));
    hardware_renderer_->CommitFrame();
  }

  hardware_renderer_->DrawGL(state_restore.stencil_enabled(),
                             state_restore.framebuffer_binding_ext(),
                             draw_info);
  ......
}

       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       AwContents类的成员函数DrawGL根据App的Render Thread的当前不同的状态执行不同的操作。在分析这些操作之前,我们首先介绍AwContents类的成员变量hardware_renderer_。如果它的值不等于NULL,那么它就是指向一个HardwareRenderer对象。这个HardwareRenderer对象是Browser端用来合成网页UI的。

       如果App的Render Thread的当前状态是AwDrawGLInfo::kModeSync,并且当前AwContents类的成员变量hardware_renderer_指向了一个HardwareRenderer对象,那么AwContents类的成员函数DrawGL就会调用这个HardwareRenderer对象的成员函数CommitFrame将Render端上一次绘制出来的网页UI同步到Browser端。

       如果App的Render Thread的当前状态是AwDrawGLInfo::kModeProcess,那么AwContents类的成员函数DrawGL就会构造一个ScopedAllowGL对象。这个ScopedAllowGL对象在构造的过程中,就会通知DeferredGpuCommandService服务执行Render端之前请求执行的GPU命令。

       如果App的Render Thread的当前状态是AwDrawGLInfo::kModeDraw,那么AwContents类的成员函数DrawGL同样先通过上述构造的ScopedAllowGL对象通知DeferredGpuCommandService服务执行Render端之前请求执行的GPU命令。此外,AwContents类的成员函数DrawGL还会调用成员变量hardware_renderer_指向了一个HardwareRenderer对象的成员函数DrawGL将从Render端同步过来的网页UI合成显示在屏幕中。如果这个HardwareRenderer对象还没有创建,那么就会进行创建,并且会在创建后将Render端上一次绘制出来的网页UI同步到Browser端来,以便接下来可以将它合成显示在屏幕中。

       从前面的分析可以知道,App的Render Thread的当前状态是AwDrawGLInfo::kModeProcess,因此接下来AwContents类的成员函数DrawGL就会通过构造一个ScopedAllowGL对象来通知DeferredGpuCommandService服务执行Render端之前请求执行的GPU命令。这些GPU命令是用来光栅化网页的UI的。

       ScopedAllowGL对象的构造过程如下所示:

base::LazyInstance<scoped_refptr<DeferredGpuCommandService> >
    g_service = LAZY_INSTANCE_INITIALIZER;
......

base::LazyInstance<base::ThreadLocalBoolean> ScopedAllowGL::allow_gl;
......

bool ScopedAllowGL::IsAllowed() {
  return allow_gl.Get().Get();
}

ScopedAllowGL::ScopedAllowGL() {
  DCHECK(!allow_gl.Get().Get());
  allow_gl.Get().Set(true);

  if (g_service.Get())
    g_service.Get()->RunTasks();
}
       这个函数定义在文件external/chromium_org/android_webview/browser/deferred_gpu_command_service.cc中。

       ScopedAllowGL类的构造函数首先是通过ScopedAllowGL类的静态成员变量allow_gl描述的一个线程局存储将当前线程标记为一个GPU线程,这样当前线程就可以直接执行GPU命令了。

       全局变量g_service描述的就是一个DeferredGpuCommandService服务。ScopedAllowGL类的构造函数接下来就会调用这个DeferredGpuCommandService服务的成员函数RunTasks调度执行保存在它内部的一个Task队列中的Task了。DeferredGpuCommandService类的成员函数RunTasks我们在前面已经分析过了,这里不再复述。

       从前面的分析可以知道,DeferredGpuCommandService服务内部的Task队列保存了一个Task。这个Task绑定了InProcessCommandBuffer类的成员函数FlushOnGpuThread。这意味着接下来InProcessCommandBuffer类的成员函数FlushOnGpuThread会在App的Render Thread中调用,它的实现如下所示:

void InProcessCommandBuffer::FlushOnGpuThread(int32 put_offset) {
  ......

  command_buffer_->Flush(put_offset);

  ......
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       从前面的分析可以知道,InProcessCommandBuffer类的成员变量command_buffer_指向的是一个CommandBufferService对象。InProcessCommandBuffer类的成员函数FlushOnGpuThread调用这个CommandBufferService对象的成员函数Flush执行GPU命令,如下所示:

void CommandBufferService::Flush(int32 put_offset) {
  ......

  put_offset_ = put_offset;

  if (!put_offset_change_callback_.is_null())
    put_offset_change_callback_.Run();
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/command_buffer_service.cc中。

       参数表示put_offset表示最新写入的GPU命令在Command Buffer中的位置。CommandBufferService类的成员函数Flush首先将这个位置记录在成员变量put_offset_中。

       CommandBufferService类的成员函数Flush接下来又会检查成员变量put_offset_change_callback_是否指向了一个Callback。如果指向了一个Callback,那么就会调用它所绑定的函数来执行GPU命令。

       从前面的分析可以知道,CommandBufferService类的成员变量put_offset_change_callback_指向了一个Callback。这个Callback绑定的函数为InProcessCommandBuffer类的成员函数PumpCommands。因此,接下来InProcessCommandBuffer类的成员函数PumpCommands会被调用。在调用期间,就会执行前面写入在Command Buffer中的GPU命令,如下所示:

void InProcessCommandBuffer::PumpCommands() {
  ......

  gpu_scheduler_->PutChanged();
}
       这个函数定义在文件external/chromium_org/gpu/command_buffer/service/in_process_command_buffer.cc中。

       从前面的分析可以知道,InProcessCommandBuffer类的成员变量gpu_scheduler_描述的是一个Gpu Scheduler。InProcessCommandBuffer类的成员函数PumpCommands调用这个Gpu Scheduler的成员函数PutChanged,用来通知它Command Buffer有新的GPU命令需要处理。这个Gpu Scheduler获得这个通知后,就会从Command Buffer中读出新写入的GPU命令,并且调用相应的OpenGL函数进行处理。这个处理过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

       这样,我们就以Render端使用GPU光栅化网页UI的情景为例,分析了Android WebView的Render端执行GPU命令的过程。其它的情景,Render端也是通过In-Process Command Buffer GL接口请求App的Render Thread来执行GPU命令。

       最后,我们分析Android WebView为Browser端创建In-Process Command Buffer GL接口的过程。有了In-Process Command Buffer GL接口之后,Browser端就可以像Render端一样,在App的Render Thread中执行GPU命令了。

       Android WebView的Browser端的主要任务是将网页的UI合成在屏幕中。为了完成这个任务,Browser端会创建一个Hardware Renderer。Hardware Renderer又会为Browser端创建一个CC Layer Tree,目的是为了可以使用Chromium的CC模块来完成合成网页UI的任务。

      Hardware Renderer会将它为Browser端创建的CC Layer Tree绘制在一个Parent Output Surface上。这个Parent Output Surface内部封装了一个In-Process Command Buffer GL接口。以后Chromium的CC模块就会通过这个In-Process Command Buffer GL接口合成网页的UI。

      接下来,我们就从Browser端创建Hardware Renderer的过程开始,分析Browser端用来合成网页UI的In-Process Command Buffer GL接口的创建过程。

      前面分析,App的Render Thread在AwContents类的成员函数DrawGL时提到,当App的Render Thread的处于AwDrawGLInfo::kModeDraw状态时,会检查Android WebView的Browser端是否已经创建了一个Hardware Renderer。如果还没有创建,那么就会进行创建。创建过程如下所示:

HardwareRenderer::HardwareRenderer(SharedRendererState* state)
    : ......,
      root_layer_(cc::Layer::Create()),
      ...... {
  ......

  layer_tree_host_ =
      cc::LayerTreeHost::CreateSingleThreaded(this, this, NULL, settings);
  layer_tree_host_->SetRootLayer(root_layer_);
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc中。

       HardwareRenderer类的构造函数主要就是为Browser端创建一个LayerTreeHost对象,并且保存成员变量layer_tree_host_中。这个LayerTreeHost对象描述的就是一个CC Layer Tree。这个CC Layer Tree是通过调用LayerTreeHost类的静态成员函数CreateSingleThreaded创建的,并且会将这个CC Layer Tree的Client指定为当前正在创建的Hardware Renderer,如下所示:

scoped_ptr<LayerTreeHost> LayerTreeHost::CreateSingleThreaded(
    LayerTreeHostClient* client,
    LayerTreeHostSingleThreadClient* single_thread_client,
    SharedBitmapManager* manager,
    const LayerTreeSettings& settings) {
  scoped_ptr<LayerTreeHost> layer_tree_host(
      new LayerTreeHost(client, manager, settings));
  layer_tree_host->InitializeSingleThreaded(single_thread_client);
  return layer_tree_host.Pass();
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       LayerTreeHost类的静态成员函数CreateSingleThreaded首先是创建了一个LayerTreeHost对象。这个LayerTreeHost对象描述的就是一个CC Layer Tree,它的创建过程如下所示:

LayerTreeHost::LayerTreeHost(LayerTreeHostClient* client,
                             SharedBitmapManager* manager,
                             const LayerTreeSettings& settings)
    : ......,
      client_(client),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       LayerTreeHost类的构造函数主要是将参数client描述的一个HardwareRenderer对象保存成员变量client_中,作为当前正在创建的CC Layer Tree的Client。以后当前正在创建的CC Layer Tree将会通过这个Client为自己创建一个Output Surface。

       回到前面分析的LayerTreeHost类的静态成员函数CreateSingleThreaded中,它接下来又会调用前面创建的LayerTreeHost对象的成员函数InitializeSingleThreaded,用来执行初始化工作,如下所示:

void LayerTreeHost::InitializeSingleThreaded(
    LayerTreeHostSingleThreadClient* single_thread_client) {
  InitializeProxy(SingleThreadProxy::Create(this, single_thread_client));
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       LayerTreeHost类的成员函数InitializeSingleThreaded首先是调用SingleThreadProxy类的静态成员函数Create创建了一个SingleThreadProxy对象,如下所示:

scoped_ptr<Proxy> SingleThreadProxy::Create(
    LayerTreeHost* layer_tree_host,
    LayerTreeHostSingleThreadClient* client) {
  return make_scoped_ptr(
      new SingleThreadProxy(layer_tree_host, client)).PassAs<Proxy>();
}
       这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       从这里可以看到,SingleThreadProxy类的静态成员函数Create创建的是一个SingleThreadProxy对象。这个SingleThreadProxy对象在创建的过程中,会将参数layer_tree_host指向的一个LayerTreeHost对象保存在自己的成员变量layer_tree_host_中,如下所示:

SingleThreadProxy::SingleThreadProxy(LayerTreeHost* layer_tree_host,
                                     LayerTreeHostSingleThreadClient* client)
    : Proxy(NULL),
      layer_tree_host_(layer_tree_host),
      ...... {
  ......
}
       这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       回到前面分析的LayerTreeHost类的成员函数InitializeSingleThreaded中,它获得了一个SingleThreadProxy对象之后,就会调用另外一个成员函数InitializeProxy对该SingleThreadProxy进行初始化,如下所示:

void LayerTreeHost::InitializeProxy(scoped_ptr<Proxy> proxy) {
  ......

  proxy_ = proxy.Pass();
  proxy_->Start();
}
      这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

      LayerTreeHost类的成员函数InitializeProxy首先将参数proxy指向的一个SingleThreadProxy对象保存在成员变量proxy_中,然后再调用这个SingleThreadProxy对象的成员函数
Start创建一个LayerTreeHostImpl对象,如下所示:

void SingleThreadProxy::Start() {
  ......
  layer_tree_host_impl_ = layer_tree_host_->CreateLayerTreeHostImpl(this);
}
       这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       从前面的分析可以知道,SingleThreadProxy类的成员变量layer_tree_host_指向的是一个LayerTreeHost对象。SingleThreadProxy类的成员函数
Start调用这个LayerTreeHost对象的成员函数CreateLayerTreeHostImpl创建了一个LayerTreeHostImpl对象,并且保存在成员变量layer_tree_host_impl_。

       从前面的分析就可以知道,Hardware Renderer在为Browser端创建CC Layer Tree的过程中,一共创建了LayerTreeHost、SingleThreadProxy和LayerTreeHostImpl三个对象。这三个对象一起描述了一个CC Layer Tree。

       从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,Render端的CC Layer Tree是通过调用LayerTreeHost类的静态成员函数CreateThreaded创建的。在创建的过程中,同样会创建一个LayerTreeHost对象和一个LayerTreeHostImpl对象。不过,它不会创建一个SingleThreadProxy对象,而是一个ThreadProxy对象。这三个对象同样是描述了一个CC Layer Tree。

       ThreadProxy类和SingleThreadProxy类都是从Proxy类继承下来的。它们的最大区别在于前者描述的CC Layer Tree在绘制的过程中,会使用到两个线程。一个称为Main线程,另一个称为Compositor线程。这两个线程通过一个CC调度器进行协作,完成绘制网页UI的任务。后者描述的CC Layer Tree在绘制的过程中,只会使用一个线程,因此它不需要使用到CC调度器。

       从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,对于复杂的UI来说,使用两个线程绘制效率会更高。不过,对Android WebView的Browser端来说,它的UI结构是非常简单的(CC Layer Tree只包含两个节点),因此,它就不需要使用两个线程来绘制了。

       回到前面分析的HardwareRenderer类的构造函数中,它除了为Browser端创建一个CC Layer Tree,还会调用Layer类的静态成员函数Create0创建一个CC Layer。这个CC Layer就作为Browser端的CC Layer Tree的根节点。

       确保Android WebView的Browser端已经具有一个Hardware Renderer之后,前面分析的AwContents类的成员函数DrawGL就会调用这个Hardware Renderer的成员函数DrawGL来合成网页的UI,如下所示:

void HardwareRenderer::DrawGL(bool stencil_enabled,
                              int framebuffer_binding_ext,
                              AwDrawGLInfo* draw_info) {
  ......

  {
    ......
    layer_tree_host_->Composite(gfx::FrameTime::Now());
  }
  
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc中。

       从前面的分析可以知道,HardwareRenderer类的成员变量layer_tree_host_指向的是一个LayerTreeHost对象。HardwareRenderer类的成员函数DrawGL调用这个LayerTreeHost对象的成员函数Composite合成网页的UI,如下所示:

void LayerTreeHost::Composite(base::TimeTicks frame_begin_time) {
  ......
  SingleThreadProxy* proxy = static_cast<SingleThreadProxy*>(proxy_.get());

  if (output_surface_lost_)
    proxy->CreateAndInitializeOutputSurface();
  ......

  proxy->CompositeImmediately(frame_begin_time);
}
        这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

        LayerTreeHost类的成员变量output_surface_lost_是一个布尔变量。当它的值等于true的时候,就表示CC模块还没有为当前正在处理的LayerTreeHost对象描述的CC Layer Tree创建过Output Surface,或者以前创建过,但是现在失效了。在这种情况下,LayerTreeHost类的成员函数Composite合成网页UI之前,先创建创建一个Output Surface。

       从前面的分析可以知道,LayerTreeHost类的成员变量proxy_指向的是一个SingleThreadProxy对象。LayerTreeHost类的成员函数Composite就是通过调用这个SingleThreadProxy对象的成员函数CreateAndInitializeOutputSurface为当前正在处理的LayerTreeHost对象描述的CC Layer Tree创建Output Surface的。

       有了Output Surface之后,LayerTreeHost类的成员函数Composite再调用上述SingleThreadProxy对象的成员函数CompositeImmediately合成网页的UI。这个过程我们在接下来一篇文章中再详细分析。

       接下来,我们继续分析SingleThreadProxy类的成员函数CreateAndInitializeOutputSurface为Browser端的CC Layer Tree创建Output Surface的过程。在创建的过程中,就会创建一个In-Process Command Buffer GL接口,如下所示:

void SingleThreadProxy::CreateAndInitializeOutputSurface() {
  ......

  scoped_ptr<OutputSurface> output_surface =
      layer_tree_host_->CreateOutputSurface();

  ......
}
       这个函数定义在文件这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       从前面的分析可以知道,SingleThreadProxy类的成员变量layer_tree_host_指向的是一个LayerTreeHost对象。SingleThreadProxy类的成员函数CreateAndInitializeOutputSurface调用这个LayerTreeHost对象的成员函数CreateOutputSurface为Browser端的CC Layer Tree创建一个Output Surface,如下所示:

scoped_ptr<OutputSurface> LayerTreeHost::CreateOutputSurface() {
  return client_->CreateOutputSurface(num_failed_recreate_attempts_ >= 4);
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       从前面的分析可以知道,LayerTreeHost类的成员变量client_指向的是一个HardwareRenderer对象。LayerTreeHost类的成员函数CreateOutputSurfaceBrowser调用这个HardwareRenderer对象的成员函数CreateOutputSurface为Browser端的CC Layer Tree创建一个Output Surface,如下所示:

scoped_ptr<cc::OutputSurface> HardwareRenderer::CreateOutputSurface(
    bool fallback) {
  ......

  scoped_refptr<cc::ContextProvider> context_provider =
      CreateContext(gl_surface_,
                    DeferredGpuCommandService::GetInstance(),
                    shared_renderer_state_->GetSharedContext());
  scoped_ptr<ParentOutputSurface> output_surface_holder(
      new ParentOutputSurface(context_provider));
  output_surface_ = output_surface_holder.get();
  return output_surface_holder.PassAs<cc::OutputSurface>();
}
       这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc。

       HardwareRenderer类的成员函数CreateOutputSurface为Browser端的CC Layer Tree创建的是一个Parent Output Surface。相应地,Render端的CC Layer Tree使用的Synchronous Compositor Output Surface也称为Child Output Surface。之所以将Render端的CC Layer Tree的Output Surface称为Child,而将Browser端的CC Layer Tree创建的是一个Parent,是因为Render端的CC Layer Tree的绘制结果会输出为Browser端的CC Layer Tree的一个节点的内容。这一点我们在接下来一篇文章分析Android WebView渲染网页UI的过程就会清楚地看到。

       HardwareRenderer类的成员函数CreateOutputSurface在为Browser端的CC Layer Tree创建Parent Output Surface的时候,需要用到一个ContextProvider对象。这个ContextProvider对象是通过调用函数CreateContext创建的,如下所示:

scoped_refptr<cc::ContextProvider> CreateContext(
    scoped_refptr<gfx::GLSurface> surface,
    scoped_refptr<gpu::InProcessCommandBuffer::Service> service,
    gpu::GLInProcessContext* share_context) {
  ......

  scoped_ptr<gpu::GLInProcessContext> context(
      gpu::GLInProcessContext::Create(service,
                                      surface,
                                      surface->IsOffscreen(),
                                      gfx::kNullAcceleratedWidget,
                                      surface->GetSize(),
                                      share_context,
                                      false /* share_resources */,
                                      in_process_attribs,
                                      gpu_preference));
  ......

  return webkit::gpu::ContextProviderInProcess::Create(
      WebGraphicsContext3DInProcessCommandBufferImpl::WrapContext(
          context.Pass(), attributes),
      "Parent-Compositor");
}
       这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc。

       函数ContextProvider首先是调用GLInProcessContext类的静态成员函数Create创建了一个In-Process Command Buffer GL接口。

       上述In-Process Command Buffer GL接口又会通过WebGraphicsContext3DInProcessCommandBufferImpl类的静态成员函数WrapContext封装在一个WebGraphicsContext3DInProcessCommandBufferImpl对象中。

       最后,前面得到的WebGraphicsContext3DInProcessCommandBufferImpl对象又会通过ContextProviderInProcess类的静态成员函数Create封装在一个ContextProviderInProcess对象中返回给调用者。调用者有了这个ContextProviderInProcess对象之后,就可以创建一个Output Surface了。

       GLInProcessContext类的静态成员函数Create、WebGraphicsContext3DInProcessCommandBufferImpl类的静态成员函数WrapContext和ContextProviderInProcess类的静态成员函数Create的实现,前面在分析Render端的CC Layer Tree使用的Synchronous Compositor Output Surface的创建过程时,已经分析过了,这里就不再复述。

       这样,我们就可以知道,Browser端的CC Layer Tree使用的Output Surface里面包含了一个In-Process Command Buffer GL接口。CC模块在绘制Browser端的CC Layer Tree时,就会通过这个In-Process Command Buffer GL接口来执行GPU命令。这与我们前面分析的Render端执行GPU命令的过程是一致的。

       至此,我们就分析了Android WebView为Render端和Browser端创建In-Process Command Buffer GL接口的过程,并且以Render端光栅化网页UI的情景为例,分析了Render端通过In-Process Command Buffer GL接口请求App的Render Thread执行GPU命令的过程。Browser端执行GPU命令的过程与Render端也是类似的。一旦Render端和Browser可以执行GPU命令,它们就可以使用硬件加速的方式渲染网页的UI。在接下来一篇文章中,我们就详细分析Android WebView使用硬件加速方式渲染网页UI的过程。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/12/12 1:05:00 原文链接
阅读:34289 评论:6 查看评论

Android WebView硬件加速渲染网页UI的过程分析

$
0
0

      Android WebView作为App UI的一部分,当App UI以硬件加速方式渲染时,它也是以硬件加速方式渲染的。Android WebView的UI来自于网页,是通过Chromium渲染的。Chromium渲染网页UI的机制与Android App渲染UI的机制是不一样的。不过,它们会一起协作完成网页UI的渲染。本文接下来就详细分析Android WebView硬件加速渲染网页UI的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

      从前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章可以知道,Android App在渲染UI一帧的过程中,经历以下三个阶段:

      1. 在UI线程中构建一个Display List,这个Display List包含了每一个View的绘制命令。

      2. 将前面构建的Display List同步给Render Thread。

      3. Render Thread对同步得到的Display List进行渲染,也就是使用GPU执行Display List的绘制命令。

      上述三个阶段如果能够在16ms内完成,那么App的UI给用户的感受就是流畅的。为了尽量地在16ms内渲染完成App的一帧UI,Android使用了以上方式对App的UI进行渲染。这种渲染机制的好处是UI线程和Render Thread可以并发执行,也就是Render Thread在渲染当前帧的Display List的时候,UI线程可以准备下一帧的Display List。它们唯一需要同步的地方发生第二阶段。不过,这个阶段是可以很快完成的。因此,UI线程和Render Thread可以认为是并发执行的。

      Android WebView既然是App UI的一部分,也就是其中的一个View,它的渲染也是按照上述三个阶段进行的,如下所示:


图1 Android WebView硬件加速渲染网页UI的过程

       在第一阶段,Android WebView会对Render端的CC Layer Tree进行绘制。这个CC Layer Tree描述的就是网页的UI,它会通过一个Synchronous Compositor绘制在一个Synchronous Compositor Output Surface上,最终得到一个Compositor Frame。这个Compositor Frame会保存在一个SharedRendererState对象中。

       在第二阶段,保存在上述SharedRendererState对象中的Compositor Frame会同步给Android WebView会对Browser端的CC Layer Tree。Browser端的CC Layer Tree只有两个节点。一个是根节点,另一个是根节点的子节点,称为一个Delegated Renderer Layer。Render端绘制出来的Compositor Frame就是作为这个Delegated Renderer Layer的输入的。

       在第三阶段,Android WebView会通过一个Hardware Renderer将Browser端的CC Layer Tree渲染在一个Parent Output Surface上,实际上就是通过GPU命令将Render端绘制出来的UI合成显示在App的UI窗口中。

       接下来,我们就按照以上三个阶段分析Android WebView硬件加速渲染网页UI的过程。

       从前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文可以知道,在App渲染UI的第一阶段,Android WebView的成员函数onDraw会被调用。从前面Android WebView执行GPU命令的过程分析一文又可以知道,Android WebView在Native层有一个BrowserViewRenderer对象。当Android WebView的成员函数onDraw被调用时,并且App的UI以硬件加速方式渲染时,这个Native层BrowserViewRenderer对象的成员函数OnDrawHardware会被调用,如下所示:

bool BrowserViewRenderer::OnDrawHardware(jobject java_canvas) {   
  ......  
  
  scoped_ptr<DrawGLInput> draw_gl_input(new DrawGLInput);  
  ......  
  
  scoped_ptr<cc::CompositorFrame> frame =  
      compositor_->DemandDrawHw(surface_size,  
                                gfx::Transform(),  
                                viewport,  
                                clip,  
                                viewport_rect_for_tile_priority,  
                                transform_for_tile_priority);  
  ......  

  frame->AssignTo(&draw_gl_input->frame);
  ......
  shared_renderer_state_->SetDrawGLInput(draw_gl_input.Pass()); 
 
  ......  
  return client_->RequestDrawGL(java_canvas, false);  
}  

       这个函数定义在文件external/chromium_org/android_webview/browser/browser_view_renderer.cc中。

       从前面Android WebView执行GPU命令的过程分析一文可以知道,BrowserViewRenderer类的成员变量compositor_指向的是一个SynchronousCompositorImpl对象。BrowserViewRenderer对象的成员函数OnDrawHardware会调用这个SynchronousCompositorImpl对象的成员函数DemandDrawHw对网页的UI进行绘制。

       绘制的结果是得到一个Compositor Frame。这个Compositor Frame会保存在一个DrawGLInput对象中。这个DrawGLInput对象又会保存在BrowserViewRenderer类的成员变量shared_renderer_state_指向的一个SharedRendererState对象中。这是通过调用SharedRendererState类的成员函数SetDrawGLInput实现的。

       BrowserViewRenderer类的成员变量client_指向的是一个AwContents对象。BrowserViewRenderer对象的成员函数OnDrawHardware最后会调用这个AwContents对象的成员函数RequestDrawGL请求在参数java_canvas描述的一个Hardware Canvas中增加一个DrawFunctorOp操作。这个DrawFunctorOp操作最终会包含在App的UI线程构建的Display List中。

       接下来,我们首先分析SynchronousCompositorImpl类的成员函数DemandDrawHw绘制网页的UI的过程,如下所示:

scoped_ptr<cc::CompositorFrame> SynchronousCompositorImpl::DemandDrawHw(
    gfx::Size surface_size,
    const gfx::Transform& transform,
    gfx::Rect viewport,
    gfx::Rect clip,
    gfx::Rect viewport_rect_for_tile_priority,
    const gfx::Transform& transform_for_tile_priority) {
  ......

  scoped_ptr<cc::CompositorFrame> frame =
      output_surface_->DemandDrawHw(surface_size,
                                    transform,
                                    viewport,
                                    clip,
                                    viewport_rect_for_tile_priority,
                                    transform_for_tile_priority);
  ......
  return frame.Pass();
}
      这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_impl.cc中。

      从前面Android WebView执行GPU命令的过程分析一文可以知道,SynchronousCompositorImpl类的成员变量output_surface_指向的是一个SynchronousCompositorOutputSurface对象。SynchronousCompositorImpl类的成员函数DemandDrawHw调用这个SynchronousCompositorOutputSurface对象的成员函数DemandDrawHw绘制网页的UI,如下所示:

scoped_ptr<cc::CompositorFrame>
SynchronousCompositorOutputSurface::DemandDrawHw(
    gfx::Size surface_size,
    const gfx::Transform& transform,
    gfx::Rect viewport,
    gfx::Rect clip,
    gfx::Rect viewport_rect_for_tile_priority,
    const gfx::Transform& transform_for_tile_priority) {
  ......

  InvokeComposite(transform,
                  viewport,
                  clip,
                  viewport_rect_for_tile_priority,
                  transform_for_tile_priority,
                  true);

  return frame_holder_.Pass();
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       SynchronousCompositorOutputSurface类的成员函数DemandDrawHw调用另外一个成员函数InvokeComposite绘制网页的UI。绘制完成后,就会得到一个Compositor Frame。这个Compositor Frame保存在SynchronousCompositorOutputSurface类的成员变量frame_holder_中。因此,SynchronousCompositorOutputSurface类的成员函数DemandDrawHw可以将这个成员变量frame_holder_指向的Compositor Frame返回给调用者。

       SynchronousCompositorOutputSurface类的成员函数InvokeComposite的实现如下所示:

void SynchronousCompositorOutputSurface::InvokeComposite(
    const gfx::Transform& transform,
    gfx::Rect viewport,
    gfx::Rect clip,
    gfx::Rect viewport_rect_for_tile_priority,
    gfx::Transform transform_for_tile_priority,
    bool hardware_draw) {
  ......

  client_->BeginFrame(cc::BeginFrameArgs::CreateForSynchronousCompositor());

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       SynchronousCompositorOutputSurface类的成员变量client_是从父类OutputSurface继承下来的。从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,它指向的是一个LayerTreeHostImpl对象。SynchronousCompositorOutputSurface类的成员函数InvokeComposite调用这个LayerTreeHostImpl对象的成员函数BeginFrame绘制网页的UI。 

       从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,当LayerTreeHostImpl类的成员函数BeginFrame被调用时,它就会CC模块的调度器执行一个BEGIN_IMPL_FRAME操作,也就是对网页的CC Layer Tree进行绘制。绘制的过程可以参考Chromium网页Layer Tree绘制过程分析Chromium网页Layer Tree同步为Pending Layer Tree的过程分析Chromium网页Pending Layer Tree激活为Active Layer Tree的过程分析这三篇文章。

       由于Android WebView的Render端使用的是Synchronous Compositor,当前线程(也就是App的UI线程)会等待Render端的Compositor线程绘制完成网页的CC Layer Tree。从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,Compositor线程在绘制完成网页的CC Layer Tree的时候,会调用网页的Output Surface的成员函数SwapBuffers。

       在我们这个情景中,网页的Output Surface是一个Synchronous Compositor Output Surface。这意味着当Compositor线程在绘制完成网页的CC Layer Tree时,会调用SynchronousCompositorOutputSurface类的成员函数SwapBuffers,如下所示:

void SynchronousCompositorOutputSurface::SwapBuffers(
    cc::CompositorFrame* frame) {
  ......

  frame_holder_.reset(new cc::CompositorFrame);
  frame->AssignTo(frame_holder_.get());

  ......
}
       这个函数定义在文件external/chromium_org/content/browser/android/in_process/synchronous_compositor_output_surface.cc中。

       参数frame指向的Compositor Frame描述的就是网页的绘制结果。从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,这个Compositor Frame包含了一系列的Render Pass。每一个Render Pass都包含了若干个纹理,以及每一个纹理的绘制参数。这些纹理是在Render端光栅化网页时产生的。Browser端的Hardware Renderer所要做的事情就是将这些纹理渲染在屏幕上。这个过程也就是Browser端合成网页UI的过程。

       SynchronousCompositorOutputSurface类的成员函数SwapBuffers会将参数frame描述的Compositor Frame的内容拷贝一份到一个新创建的Compositor Frame中去。这个新创建的Compositor Frame会保存在SynchronousCompositorOutputSurface类的成员变量frame_hodler_中。因此,前面分析的SynchronousCompositorOutputSurface类的成员函数InvokeComposite返回给调用者的就是当前绘制的网页的内容。

       这一步执行完成后,回到前面分析的BrowserViewRenderer类的成员函数OnDrawHardware中,这时候它就获得了一个Render端绘制网页的结果,也就是一个Compositor Frame。这个Compositor Frame会保存在一个DrawGLInput对象中。这个DrawGLInput对象又会保存在BrowserViewRenderer类的成员变量shared_renderer_state_指向的一个SharedRendererState对象中。这是通过调用SharedRendererState类的成员函数SetDrawGLInput实现的,如下所示:

void SharedRendererState::SetDrawGLInput(scoped_ptr<DrawGLInput> input) {
  ......
  draw_gl_input_ = input.Pass();
}
      这个函数定义在文件external/chromium_org/android_webview/browser/shared_renderer_state.cc中。

      SharedRendererState类的成员函数SetDrawGLInput将参数input指向的一个DrawGLInput对象保存成员变量draw_gl_input_中。

      这一步执行完成后,再回到前面分析的BrowserViewRenderer类的成员函数OnDrawHardware中,接下来它会调用成员变量client_指向的一个Native层AwContents对象的成员函数RequestDrawGL请求在参数java_canvas描述的一个Hardware Canvas中增加一个DrawFunctorOp操作,如下所示:

bool AwContents::RequestDrawGL(jobject canvas, bool wait_for_completion) {
  ......

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return false;
  return Java_AwContents_requestDrawGL(
      env, obj.obj(), canvas, wait_for_completion);
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       在前面Android WebView执行GPU命令的过程分析一文中,我们已经分析过Native层AwContents类的成员函数RequestDrawGL的实现了,它主要就是调用Java层的AwContents类的成员函数requestDrawGL请求在参数canvas描述的Hardware Canvas中增加一个DrawFunctorOp操作。

       Java层的AwContents类的成员函数requestDrawGL最终会调用到DrawGLFunctor类的成员函数requestDrawGL在参数canvas描述的Hardware Canvas中增加一个DrawFunctorOp操作,如下所示:

class DrawGLFunctor {
    ......

    public boolean requestDrawGL(HardwareCanvas canvas, ViewRootImpl viewRootImpl,
            boolean waitForCompletion) {
        ......

        if (canvas == null) {
            viewRootImpl.invokeFunctor(mDestroyRunnable.mNativeDrawGLFunctor, waitForCompletion);
            return true;
        }

        canvas.callDrawGLFunction(mDestroyRunnable.mNativeDrawGLFunctor);
        ......

        return true;
    }

    ......
}
       这个函数定义在文件frameworks/webview/chromium/java/com/android/webview/chromium/DrawGLFunctor.java中。

       在前面Android WebView执行GPU命令的过程分析一文中,DrawGLFunctor类的成员函数requestDrawGL是在Render端光栅化网页UI的过程中调用的。这时候参数canvas的值等于null,因此DrawGLFunctor类的成员函数requestDrawGL会通过调用参数viewRootImpl指向的一个ViewRootImpl对象的成员函数invokeFunctor直接请求App的Render Thread执行GPU命令。

       现在,当DrawGLFunctor类的成员函数requestDrawGL被调用时,它的参数canvas的值不等于null,指向了一个Hardware Canvas。在这种情况下,DrawGLFunctor类的成员函数requestDrawGL将会调用这个Hardware Canvas的成员函数callDrawGLFunction,将一个Native层DrawGLFunctor对象封装成一个DrawFunctorOp操作,写入到它描述一个Display List中去。

       被封装的Native层DrawGLFunctor对象,保存在Java层DrawGLFunctor类的成员变量mDestroyRunnable指向的一个DestroyRunnable对象的成员变量mNativeDrawGLFunctor中。这一点可以参考前面Android WebView执行GPU命令的过程分析一文。

       从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,参数canvas描述的Hardware Canvas是通过一个GLES20Canvas对象描述的,因此接下来它的成员函数callDrawGLFunction会被调用,用来将一个Native层DrawGLFunctor对象封装成一个DrawFunctorOp操作写入它描述一个Display List中去,如下所示:

class GLES20Canvas extends HardwareCanvas {
    ......

    @Override
    public int callDrawGLFunction(long drawGLFunction) {
        return nCallDrawGLFunction(mRenderer, drawGLFunction);
    }

    ......
}
       这个函数定义在文件frameworks/base/core/java/android/view/GLES20Canvas.java中。

       GLES20Canvas类的成员函数callDrawGLFunction调用另外一个成员函数nCallDrawGLFunction将参数drawGLFunction描述的一个Native层DrawGLFunctor对象封装成一个DrawFunctorOp操作写入到当前正在处理的GLES20Canvas对象描述一个Display List中去。

       GLES20Canvas类的成员函数nCallDrawGLFunction是一个JNI方法,它由C++层的函数android_view_GLES20Canvas_callDrawGLFunction实现,如下所示:

static jint android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
        jlong rendererPtr, jlong functorPtr) {
    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
    Functor* functor = reinterpret_cast<Functor*>(functorPtr);
    android::uirenderer::Rect dirty;
    return renderer->callDrawGLFunction(functor, dirty);
}
       这个函数定义在文件frameworks/base/core/jni/android_view_GLES20Canvas.cpp中。

       参数rendererPtr描述的是一个Native层的DisplayListRenderer对象。这个DisplayListRenderer对象负责构造App UI的Display List。函数android_view_GLES20Canvas_callDrawGLFunction所做的事情就是调用这个DisplayListRenderer对象的成员函数callDrawFunction将参数functionPtr描述的一个Native层DrawGLFunctor对象封装成一个DrawFunctorOp操作写入到App UI的Display List中去,如下所示:

status_t DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
    // Ignore dirty during recording, it matters only when we replay
    addDrawOp(new (alloc()) DrawFunctorOp(functor));
    mDisplayListData->functors.add(functor);
    return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
}
       这个函数定义在文件frameworks/base/libs/hwui/DisplayListRenderer.cpp中。

       DisplayListRenderer类的成员变量mDisplayListData指向的是一个DisplayListData对象。这个DisplayListData对象描述的就是App UI的Display List。因此,DisplayListRenderer对象的成员函数callDrawFunction就会将参数functor描述的一个Native层DrawGLFunctor对象封装成一个DrawFunctorOp操作写入到它里面去。

       这一步执行完成后,Android WebView就在App渲染一个帧的第一个阶段通知Render端绘制完成了网页的UI,并且往App UI的Display List写入了一个DrawFunctorOp操作。在第二阶段,App UI的Display List就会从App的UI线程同步给App的Render Thread。从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,在同步的过程中,RenderNode类的成员函数pushStagingDisplayListChanges地被调用,如下所示:

void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
    if (mNeedsDisplayListDataSync) {
        mNeedsDisplayListDataSync = false;
        ......
        if (mDisplayListData) {
            for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
                (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, NULL);
            }
        }
        ......
    }
}
       这个函数定义在文件frameworks/base/libs/hwui/RenderNode.cpp中。

       这时候包含在App UI的Display List中的每一个DrawFunctorOp操作关联的Native层DrawGLFunctor对象的重载操作符函数()都会被调用,目的是让它执行一些同步操作。在我们这个情景中,就是将Render端绘制出来的UI同步到给Browser端。

       在前面Android WebView执行GPU命令的过程分析一文中,我们已经分析过Native层DrawGLFunctor对象的重载操作符函数()的实现了,它最终会调用到Native层的AwContents类DrawGL将Render端绘制出来的UI同步到给Browser端,如下所示:

void AwContents::DrawGL(AwDrawGLInfo* draw_info) {
  if (draw_info->mode == AwDrawGLInfo::kModeSync) {
    if (hardware_renderer_)
      hardware_renderer_->CommitFrame();
    return;
  }

  ......
}
      这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

      这时候App的Render Thread处于AwDrawGLInfo::kModeSync状态,因此AwContents类的成员函数DrawGL接下来将会调用成员变量hardware_renderer_指向的一个HardwareRenderer对象的成员函数CommitFrame将Render端绘制出来的UI同步到给Browser端,如下所示:

void HardwareRenderer::CommitFrame() {
  scoped_ptr<DrawGLInput> input = shared_renderer_state_->PassDrawGLInput();
  ......

  if (!frame_provider_ || size_changed) {
    ......

    frame_provider_ = new cc::DelegatedFrameProvider(
        resource_collection_.get(), input->frame.delegated_frame_data.Pass());

    delegated_layer_ = cc::DelegatedRendererLayer::Create(frame_provider_);
    ......

    root_layer_->AddChild(delegated_layer_);
  } else {
    frame_provider_->SetFrameData(input->frame.delegated_frame_data.Pass());
  }
}
      这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc中。

      从前面的分析可以知道,Render端在第一阶段已经将绘制出来的网页UI,保存在一个DrawGLInput对象中。这个DrawGLInput又保存在一个SharedRendererState对象中。HardwareRenderer类的成员变量shared_renderer_state_描述的就是这个SharedRendererState对象。因此,HardwareRenderer类的成员函数CommitFrame可以通过调用这个SharedRendererState对象的成员函数PassDrawGLInput获得保存在它内部的DrawGLInput对象,如下所示:

scoped_ptr<DrawGLInput> SharedRendererState::PassDrawGLInput() {
  base::AutoLock lock(lock_);
  return draw_gl_input_.Pass();
}
      这个函数定义在文件external/chromium_org/android_webview/browser/shared_renderer_state.cc中。

      从前面的分析可以知道,用来描述Render端在第一阶段绘制出来的网页UI的DrawGLInput对象就保存在SharedRendererState类的成员变量draw_gl_input_中,因此SharedRendererState类的成员函数PassDrawGLInput就可以将这个员变量draw_gl_input_指向的DrawGLInput对象返回给调用者。

       回到前面分析的HardwareRenderer类的成员函数CommitFrame中,这时候它获得了一个Render端在第一阶段绘制出来的UI,也就是一个DrawGLInput对象,接下来它就会判断之前是否已经为Browser端的CC Layer Tree创建过一个Delegated Renderer Layer。

       如果还没有创建,或者以前创建过,但是现在Android WebView的大小发生了变化,那么HardwareRenderer类的成员函数CommitFrame就会创建用前面获得的DrawGLInput对象创建一个Delegated Renderer Layer,并且作为Browser端的CC Layer Tree的根节点的子节点。

       另一方面,如果已经创建,并且Android WebView的大小没有发生变化,那么HardwareRenderer类的成员函数CommitFrame就会使用前面获得的DrawGLInput对象更新Delegated Renderer Layer的内容。

       这一步执行完成后,Android WebView就在App渲染一个帧的第二个阶段将Render端绘制出来的网页UI同步给了Browser端。在第三阶段,Browser端就会将网页的UI合成在App的窗口中,这样就可以显示在屏幕中了。

       从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,App的Render Thread在第三阶段会通过一个OpenGL Renderer渲染从App的UI线程同步过来的Display List,也就是执行它里面包含的渲染操作。从前面的分析可以知道,Android WebView在第一阶段往这个Display List写入了一个DrawFunctorOp操作,OpenGL Renderer会通过调用它的成员函数callDrawGLFunction执行这个操作,如下所示:

status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
    ......

    mRenderState.invokeFunctor(functor, DrawGlInfo::kModeDraw, &info);
    ......

    return DrawGlInfo::kStatusDrew;
}
      这个函数定义在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

      参数funtor描述的就是与当前要执行的DrawFunctorOp操作关联的Native层DrawGLFunctor对象。OpenGLRenderer类的成员函数callDrawGLFunction会通过调用成员变量mRenderState指向的一个RenderState对象的成员函数invokeFunctor调用这个Native层DrawGLFunctor对象的重载操作符函数(),告知它App的Render Thread当前处于第三阶段,也就是DrawGlInfo::kModeDraw状态,它可以执行相应的GPU命令。

      在前面Android WebView执行GPU命令的过程分析一文中,我们已经分析过RenderState类的成员函数invokeFunctor的实现了,它最终会调用到Native层的AwContents类DrawGL将绘制Browser端的CC Layer Tree,如下所示:

void AwContents::DrawGL(AwDrawGLInfo* draw_info) {
  if (draw_info->mode == AwDrawGLInfo::kModeSync) {
    ......
    return;
  }

  ......

  if (draw_info->mode != AwDrawGLInfo::kModeDraw) {
    ......
    return;
  }

  ......

  hardware_renderer_->DrawGL(state_restore.stencil_enabled(),
                             state_restore.framebuffer_binding_ext(),
                             draw_info);
  ......
}
       这个函数定义在文件external/chromium_org/android_webview/native/aw_contents.cc中。

       由于当前App的Render Thread处于AwDrawGlInfo::kModeDraw状态,因此AwContents类DrawGL会调用成员变量hardware_renderer_指向的一个HardwareRenderer对象的成员函数DrawGL,用来绘制Browser端的CC Layer Tree,如下所示:

void HardwareRenderer::DrawGL(bool stencil_enabled,
                              int framebuffer_binding_ext,
                              AwDrawGLInfo* draw_info) {
  ......

  {
    ......
    layer_tree_host_->Composite(gfx::FrameTime::Now());
  }

  ......
}
      这个函数定义在文件external/chromium_org/android_webview/browser/hardware_renderer.cc中。

      从前面Android WebView执行GPU命令的过程分析一文可以知道,HardwareRenderer类的成员变量layer_tree_host_指向的是一个LayerTreeHost对象。这个LayerTreeHost对象描述的就是Browser端的CC Layer Tree。HardwareRenderer类的成员函数DrawGL将会调用这个LayerTreeHost对象的成员函数Composite,以便绘制Browser端的CC Layer Tree,如下所示:

void LayerTreeHost::Composite(base::TimeTicks frame_begin_time) {
  ......
  SingleThreadProxy* proxy = static_cast<SingleThreadProxy*>(proxy_.get());

  .....

  proxy->CompositeImmediately(frame_begin_time);
}
      这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

      从前面Android WebView执行GPU命令的过程分析一文可以知道,当前正在处理的LayerTreeHost对象的成员变量proxy_指向的是一个SingleThreadProxy对象,LayerTreeHost类的成员函数Composite调用这个SingleThreadProxy对象的成员函数CompositeImmediately绘制Browser端的CC Layer Tree,如下所示:

void SingleThreadProxy::CompositeImmediately(base::TimeTicks frame_begin_time) {
  ......

  LayerTreeHostImpl::FrameData frame;
  if (DoComposite(frame_begin_time, &frame)) {
    {
      ......
    }
    ......
  }
}
       这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       SingleThreadProxy类的成员函数CompositeImmediately主要是调用另外一个成员函数DoComposite绘制Browser端的CC Layer Tree,如下所示:

bool SingleThreadProxy::DoComposite(
    base::TimeTicks frame_begin_time,
    LayerTreeHostImpl::FrameData* frame) {
  ......

  bool lost_output_surface = false;
  {
    ......

    if (!layer_tree_host_impl_->IsContextLost()) {
      layer_tree_host_impl_->PrepareToDraw(frame);
      layer_tree_host_impl_->DrawLayers(frame, frame_begin_time);
      ......
    }
    
    ......
  }

  ......
} 
       这个函数定义在文件external/chromium_org/cc/trees/single_thread_proxy.cc中。

       从前面Android WebView执行GPU命令的过程分析一文可以知道,SingleThreadProxy类的成员变量layer_tree_host_impl_指向的是一个LayerTreeHostImpl对象。SingleThreadProxy类的成员函数DoComposite主要是调用这个LayerTreeHostImpl对象的成员函数PrepareToDraw和DrawLayers绘制Browser端的CC Layer Tree。

       LayerTreeHostImpl类的成员函数PrepareToDraw和DrawLayers绘制CC Layer Tree的过程可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。从前面Chromium硬件加速渲染的UI合成过程分析一文我们还可以知道,Chromium的Browser端在内部是通过一个Direct Renderer绘制CC Layer Tree的,而Render端是通过一个Delegated Renderer绘制CC Layer Tree的。Delegated Renderer并不是真的绘制CC Layer Tree,而只是将CC Layer Tree的绘制命令收集起来,放在一个Compositor Frame中。这个Compositor Frame最终会交给Browser端的Direct Renderer处理。Direct Renderer直接调用OpenGL函数执行保存在Compositor Frame的绘制命令。因此,当Browser端绘制完成自己的CC Layer Tree之后,加载在Android WebView中的网页UI就会合成显示在App的窗口中了。

      至此,我们就分析完成了Android WebView硬件加速渲染网页UI的过程,也完成了对Android基于Chromium实现的WebView的学习。重新学习可以参考Android WebView简要介绍和学习计划一文。更多的学习信息可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/12/19 0:58:18 原文链接
阅读:37899 评论:5 查看评论

《Android系统源代码情景分析》连载回忆录:灵感之源

$
0
0

       上个月,在花了一年半时间之后,写了55篇文章,分析完成了Chromium在Android上的实现,以及Android基于Chromium实现的WebView。学到了很多东西,不过也挺累的,平均不到两个星期一篇文章。本来想休息一段时间后,再继续分析Chromium使用的JS引擎V8。不过某天晚上,躺在床上睡不着,鬼使神差想着去创建一个个人站点,用来连载《Android系统源代码情景分析》一书的内容。

       事情是这样的,躺在床上睡不着,就去申请了一个域名,0xcc0xcd.com。域名申请到了,总不能不用吧。用来做什么呢?想起我写的那本书《Android系统源代码情景分析》,从2012年10月出版至今,也有四年多的时间了,得到了大家的厚受。不过网络上也逐渐的出现了一些盗版PDF。不用说,质量肯定很差。干脆我把这本书的内容在我的个人站点上放出来吧。后面征得了出版社的同意,就着手开始干了。

       网站名称为“进击的程序员”,主要是为了配合0xcc0xcd.com这个域名。从Windows时代过来的老司机可能一眼就能看出这个域名是什么意思。看不懂的,如果大家有兴趣,后面我也可以详细说说,怀念一下逝去的青春。

       从开始有想法,到把网站建好,以及将书前三章(准备知识、硬件抽象层、智能指针)的内容放上去,花了不到一个月的时间。在这不到一个月的时间里,学习到了挺多东西:申请域名、云服务器、域名解析、域名邮箱、网站备案以及开发网站等等。因为我一直都是做客户端开发,刚毕业几年做的是Windows客户端,后面做的是Android端,没有做过网站相关的开发,包含前端和后端,所以学习过程还是有些小波折。不过总体上来说还是比较顺利的。这也跟网站的技术选型有关吧。

       现在不是提倡做全栈工程师吗?这个建站过程也算是小小地实践了一把。怕时间久了会忘记一些关键细节和踩过的坑,所以就计划把建站连载书的过程记录下来。也希望能够帮助到有兴趣做全栈工程师的同学们。

       网站使用的是LNMP架构,如下图1所示:


图1 进击的程序员网站架构

       网站运行在云服务器上,系统装的是Ubuntu 14.04,除了Nginx、PHP和MySQL,还搭了一个GIT仓库,用来管理网站源码。这个GIT仓库除了用来管理网站源码,还用来将源码分布到网站中去。

       具体是这样的,在本地用自己的电脑开发网站(其实就是用vim编辑网页和PHP)。测试没有问题之后,就用git push命令将源码上传到GIT仓库。然后再登录到云服务器上,在网站根目录用git pull命令从GIT仓库中获得最新网站源码。

       此外,在本地还搭建了一个管理后台。这个管理后台就是用来给管理员管理网站的。主要就是操作一下数据库,例如查看数据、插入数据、更新数据等等。正规的网站会专门提供一些页面供管理员操作。鉴于这个网站不是很正规,管理员又是一个技术控,于是就直接使用Python脚本来实现这个管理后台了,想要什么功能就直接写个脚本。

      Oracle提供了一个Python版的MySQL数据库驱动库MySQL Connector/Python,通过它很容易用Python脚本操作MySQL中的数据。这样一个简单的管理后台就搭建起来了。

      整个网站的架构非常简单,可以非常快上手,同时它又五脏俱全。网站的前端主要用Ajax、jQuery开发,后端没有用什么高大尚的框架,基本上是徒手写的PHP。主要是考虑这个网站要做的事情很简单,就是连载《Android系统源代码情景分析》的内容,基本功能就是浏览和评论。所以就以最简单最快的方式实现。

      为了让大家利用碎片时间更好地阅读书的内容,网站在提供PC版的同时,也提供了移动版。移动版和PC版的功能是一样的,只是它们的页面表现形式不一样。所以网站在设计之初,就考虑了模块化和代码复用,用最小的成本获得同时实现PC端和移动端的功能。

      不知道为什么,说起PHP, 总是会想起“PHP是最好的语言”这句话。从这一个月的经历看,PHP是不是最好的语言不知道,但是用来建网站,PHP的确是最好的语言。用PHP和JS开发网站,效率比用Java/OC开发App,高多了。不过,网站的体验不如App。所以移动开发目前还是王道。

       接下来,我会用一个系列的文章分享整个建站过程,包括:

       1. 域名、云服务器、域名解析、网站备案、域名邮箱、CA证书申请

       2. LNMP开发环境搭建,包括如何配置SSL加密的HTTPS站点

       3. 支持SSH访问的GIT仓库搭建

       4. 网站基本功能开发,PC版和移动版代码复用

       5. 基于MySQL Connector/Python的管理后台开发

       欢迎大家关注!想在线阅读《Android系统源代码情景分析》一书的,点击进击的程序员进入!

作者:Luoshengyang 发表于2017/1/10 22:42:11 原文链接
阅读:44696 评论:28 查看评论

【蓝桥杯】【埃及分数】

$
0
0

题目

古埃及曾经创造出灿烂的人类文明,他们的分数表示却很令人不解。

古埃及喜欢把一个分数分解为类似: 1/a + 1/b 的格式。

这里,a 和 b 必须是不同的两个整数,分子必须为 1

比如,2/15 一共有 4 种不同的分解法(姑且称为埃及分解法):

1/8 + 1/120

1/9 + 1/45

1/10 + 1/30

1/12 + 1/20

那么, 2/45 一共有多少个不同的埃及分解呢(满足加法交换律的算同种分解)?

请直接提交该整数(千万不要提交详细的分解式!)。

请严格按照要求,通过浏览器提交答案。

注意:只提交分解的种类数,不要写其它附加内容,比如:说明性的文字



分析

2/45 = 1/a + 1/b , 假设a<b,很明显22<a<45。因为1/22>2/45,1/45 <= 2/45。

我们用2/45-1/a就可以求出1/b,进而求出b。



源码

public static void main(String[] args) {
		//新建2/45
		Rational r = new Rational(2, 45);
		//计数变量
		int counter = 0;
		for (int a = 23; a < 45; a++) {
			Rational ra = new Rational(1, a);
			Rational rb = r.sub(ra);
			if(rb.x == 1 && rb.y > a) {
				System.out.println(ra.toString() + "+" + rb.toString());
				counter++;
			}
		}
		System.out.println(counter);
	}


public class Rational {
	int x;
	int y;
	
	public Rational(int x, int y){
		int gcd = gcd(x,y);
		this.x = x/gcd;
		this.y = y/gcd;
	}
	
	//辗转相除法求最大公约数
	//(10,5) == (5,0) == 5
	private int gcd(int x, int y){
		if(y == 0) return x;
		return gcd(y,x%y);
	}
	
	//x1/y1 +x2/y2 == (x1y2+x2y1)/y1y2
	public Rational add(Rational r){
		return new Rational(this.x*r.y + r.x*this.y, this.y*r.y);
	}
	
	//x1/y1 - x2/y2 = (x1y2-x2y1)/y1y2
	public Rational sub(Rational r) {
		return new Rational(this.x*r.y - r.x*this.y, this.y*r.y);
	}
	
	@Override
	public String toString() {
		return this.x +"/" + this.y;
	}

}

结果

1/23+1/1035
1/24+1/360
1/25+1/225
1/27+1/135
1/30+1/90
1/35+1/63
1/36+1/60

7种




作者:bear_huangzhen 发表于2017/11/12 19:15:16 原文链接
阅读:187 评论:0 查看评论

在分类及预测任务中对高维类别(category)变量的预处理方法

$
0
0

引言

众所周知,数据挖掘中大约有80%的时间被用来做数据预处理。其中高维类别数据是数据挖掘算法(比如神经网络、线性or逻辑回归、SVM)最具挑战性的数据类型。事实上,在一些像决策树或者一些规则归纳的学习算法中,对类别数据并不需要额外的处理。但是一些回归算法却需要将每一个输入特征都转变成数值类型的特征。而且在现实生活中,我们需要解决的分类或者预测问题的数据集中,充满了类别属性,比如:ZIP码,SIC,IP,区号,域名等等。这就需要有种系统的方法能够将高维类别数据映射到可供回归任务学习的准连续标量属性上。我读了篇SIGKDD上的解决上述问题的文章:A Preprocessing Scheme for High-Cardinality Categorical
Attributes in Classification and Prediction Problems
,并总结了文章的方法,特分享如下。

文章方法综述

该论文提出了一种将高维类别属性的数据映射到可供回归任务学习的准连续标量属性上。转换的关键之处在于将高维类别属性的每个值映射成基于目标属性的概率估计

  • 在分类场景下,被映射成,给定类别属性值的条件下,目标属性的后验概率。
  • 在预测场景下,被映射成,给定类别属性值的条件下,目标的期望值。

该方法听起来是统计相关的,而且可以应用于分类与回归的任务场景中,它还优雅地处理了类别属性是缺失值的情况。这种方法听起来是不是牛掰爆了,大家会担心,这种方法是不是很难呀。在这里我要说的是:很简单~。论文的组织结构如下:第二节讲述了处理类别属性的最通用的方法;第三节讲述了本文要重点分析的方法;第四节描述了该方法在层次化数据上的应用拓展;第五节就总结了文章。接下来,本文也会按照论文的组织结构,给大家分析下这篇论文的。

处理类别属性的通用方法

这里处理类别属性的通用方法有两大类,这两大类按照数据的维度划分。因此可以分为低维类别数据及高维类别数据。

低维类别数据预处理方法

对于低维度的类别数据最普遍的做法就是二值化。我们简单举个例子:

ID Gender Color of cloth
1 male blue
2 female white
3 male red

上面的表格中存放了我们构造的三个样本数据,其中属性栏,有三个属性,ID,Gender,Color of cloth,其中很明显,Gender与Color of cloth是类别属性。我们将其二值化,从而将二值化后的新表格如下图所示:

ID male female blue white red
1 1 0 1 0 0
2 0 1 0 1 0
3 1 0 0 0 1

二值化编码的核心思想就是将第i个组成标记为1,其余标记为0。这是一种1到N的映射关系,当面对高维类别数据即N很大的时候,这种方法就不太适用(矩阵很稀疏)。那么为了解决高维类别属性,又该采用何种方法呢?

高维类别数据

现如今,处理高维类别数据的最常用方法就是聚类。核心思想就是将1到N的映射问题降维到1到K的映射问题上来(kN)。
因此该方法的工作流程就是:

*先采用诸如层次聚类的聚类算法,有效地将N个原始数据聚类成K个簇
*再将K个簇二值化
然后论文就说了下这些通用方法的缺点啊,不足啊,又吹捧了下自己提出的方法。

使用目标统计方法来对类别属性进行编码

终于来到正文了,首先说下,接下来提到的方法,就如我们章节标题所写,是个典型的统计学方法,而且历史悠久,应用领域颇多,但是,在类似这些分类预测任务中作为预处理方法的应用,其他文献没有提到过,所以文章还是有一定创新性的。
这一章节将按照目标变量的类型分成三个小节:二值型变量、连续型变量、多类别变量

二值型变量

当目标属性Y是二值的,Y{01},将高维类别属性X的一个值Xi映射成一个标量Si,其中Si就代表给定X=Xi的条件下Y=1的概率估计:

XiSiP(Y|X=Xi)(1)

有了这个映射关系,接下来就需要来算出这个“概率估计”了。这里我们假设训练集有nTR个样本,测试集有nTS个样本。既然概率估计是在训练模型过程中的一部分(即概率估计是发生在模型训练的过程中,而不是测试模型时),那么概率估计只与训练数据的nTR个样本有关。
假如训练样本足够大,那么概率估计直接可以用下面的公式计算:
Si=niYni(2)

其中niXi的数量,niYY=1的数量。
但是在实际场景中,ni的数量很小,因此公式(2)来计算后验概率并不是很可信。为了削弱某些样本个数较小带来的影响,文章提出了一种计算混合概率的方法:将公式(2)的后验概率与Y的先验概率同时利用起来。方法如下:
Si=λ(ni)niYni+(1λ(ni))nYnTR(3)

其中nYY=1的训练样本数,权重因子λ(ni)是边界为0,1的单调递增函数。
ni的数量很大的时候,λ1,我们给后验概率分配更多的权重,此时等价于公式(2);但是如果ni的数量很小,λ0,我们直接采用先验概率。
其中可以给权重因子指定一个函数,例如:
λ(n)=11+enkf(4)

很显然,该函数是个sigmoid函数变种。当n=k时,函数大小为0.5,此处为S型曲线的拐点。参数f控制该函数在拐点处的斜率。
公式(3)在统计与精算学中有着很长的一段历史。事实上公式(3)是贝叶斯概率的分支之一被称作经验贝叶斯(Empirical Bayesian)。
公式如下:
Pi=Biyi+(1Bi)y¯(5)

其中y¯指先验概率,yi指后验概率。Bi称作shrinkage因子(0<B<1)。假设数据及后验估计的概率分布均满足高斯分布,那么Bi采用如下形式:
Bi=niτ2σ2+niτ2(6)

其中σ2X=Xi数据集方差,τ2是整个样本的方差。niX=Xi数据集的大小。很显然Biλ(n)的一种特殊形式,Bi不仅考虑了样本大小,还考虑了数据集的方差。

处理缺失数据

在大多数情况下,数据集里会存在缺失数据。根据属性的特性及所采用的具体算法有不同的处理方法:

  • 如果X是数值型数据,一般通过均值或者基于其他属性的估计来填充空缺值
  • 如果X是低维类别数据,那么如果采用二值化编码(有m个元素的向量),那么空缺值可以初始化为m个0的向量。
  • 有些决策树算法会直接丢弃空缺值,或者用另外一个值来填充。

这篇论文提出了一种方法来补全空缺值:引入一个空缺值X0,然后再给定条件X=X0的情况下求:

S0=λ(n0)n0Yn0+(11λ(n0))nYnTR(7)

这个方法的好处是,如果缺失值与目标值有很大相关性,那么这个公式就能很好的表达出相关信息。如果缺失值与目标值没有太大关系,那么S0也会很快收敛于先验概率,这也是对缺失值的中立表示。

连续型变量

上面提出的方法,也能应用到目标变量是连续值得情况下。无非就是将概率估计转变成期望估计。现在我们就是要考虑期望了,公式如下:

Si=λ(ni)kLiYkni+(1λ(ni))NTRk=1YknTR(8)

多类别变量

当目标变量是多类别的,扩展的方法也很直接,就是直接求

P(Y=Yj|X=Xi)(9)

在具有层次结构类别数据上的应用

一些高维类别数据通常是定义好的层次结构,这样便于做聚合运算。一个典型的案例就是美国的邮政编码系统:一个5位的ZIP码,如果在一个大都市区域,那么该ZIP码的前三位是一样的。所以可以在基于ZIP3或ZIP4的基础上聚合ZIP5。其他的一些例子像手机号码,IP地址都有这样的层次编码结构。
论文提出的方法可以很好的利用层次结构这一特性来讲结构数据进行转换。直接举例说明,以ZIP5编码作为说明。ZIP5级别的公式如下:

S5i=λ(ni)nilni+(1λ(ni))nYnTR(10)

如果ZIP5类别很稀疏,那么可以“降维”成ZIP4,对ZIP4级别的类别数据进行估计。因为大多数估计都与先验概率近似,因此可以讲ZIP5级别的先验概率有ZIP4的估计代替:
S5i=λ(ni)nilni+(1λ(ni))S4i(11)

以此类推ZIP3,ZIP2。从上述公式可以看到这个公式可以自动根据数据的密度进行调节。

总结

这篇论文,基本介绍完了,就是提出了EB(经验贝叶斯)的通用方法,然后应用到了机器学习分类回归任务的预处理中,关于这个方法到底好用不好用,现在我还不清楚,因为在做Kaggle一个赛题,有人分享了这篇文章,我分析了下,准备试试具体效果,如果效果好,我会再来给本文写个附加的评论的~

作者:u010665216 发表于2017/11/12 19:20:48 原文链接
阅读:179 评论:0 查看评论

Docker Tomcat容器部署web应用(war)

$
0
0
  • 创建tomcat容器
docker run tomcat --name mytomcat -p 8080:8080 -d

  • 进入容器内部
交互式的进入tomcat容器。“docker exec”命令表示在运行中的容器中开启一个新的进程。


查看到webapps路径为:/usr/local/tomcat/webapps。等下需要把war包拷贝到该路径下。

  • 上传war包到容器
docker中也可以使用cp命令完成和宿主机的文件拷贝。


  • 重启容器
重启容器,然后就可以发现上传的war包已经自动解压部署了。进入webapps目录即可查看。


  • 访问应用
       要注意访问的路径可能和自己本机测试跑的路径不同。如果你本身是一个多module项目,本地测试跑起来的url路径与目前单独war包部署跑起来的路径肯定是不同的。
       在浏览器中通过url即可访问到该项目。可以发现,以上过程和实际Tomcat中部署过程是十分相似的。


作者:CHENYUFENG1991 发表于2017/11/12 19:49:15 原文链接
阅读:178 评论:0 查看评论

golang interface深度解析

$
0
0

 一 接口介绍

    如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口。Go语言的编程哲学因为有接口而趋于完美。C++,Java 使用"侵入式"接口,主要表现在实现类需要明确声明自己实现了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程中一个遭受相当多质疑的特性。Go语言采用的是“非侵入式接口",Go语言的接口有其独到之处:只要类型T的公开方法完全满足接口I的要求,就可以把类型T的对象用在需要接口I的地方,所谓类型T的公开方法完全满足接口I的要求,也即是类型T实现了接口I所规定的一组成员。这种做法的学名叫做Structural Typing,有人也把它看作是一种静态的Duck Typing。

     在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。一个接口变量可以存储任意实际值(非接口),只要这个值实现了接口的方法。

type Reader interface {
    Read(p []byte) (n int, err os.Error)
}

// Writer 是包裹了基础 Write 方法的接口。
type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
     有一个事情是一定要明确的,不论 r 保存了什么值,r 的类型总是 io.Reader,Go 是静态类型,而 r 的静态类型是 io.Reader。接口类型的一个极端重要的例子是空接口interface{},它表示空的方法集合,由于任何值都有零个或者多个方法,所以任何值都可以满足它。也有人说 Go 的接口是动态类型的,不过这是一种误解。 它们是静态类型的:接口类型的变量总是有着相同的静态类型,这个值总是满足空接口,只是存储在接口变量中的值运行时可能被改变。对于所有这些都必须严谨的对待,因为反射和接口密切相关。

二  接口类型内存布局

    在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。一个接口变量可以存储任意实际值(非接口),只要这个值实现了接口的方法。interface在内存上实际由两个成员组成,如下图,tab指向虚表,data则指向实际引用的数据。虚表描绘了实际的类型信息及该接口所需要的方法集。

type Stringer interface {
    String() string
}
 
type Binary uint64
 
func (i Binary) String() string {
    return strconv.FormatUint(i.Get(), 2)
}
 
func (i Binary) Get() uint64 {
    return uint64(i)
}
 
func main() {
    var b Binary = 32
    s := Stringer(b)
    fmt.Print(s.String())
} 

  

      观察itable的结构,首先是描述type信息的一些元数据,然后是满足Stringger接口的函数指针列表(注意,这里不是实际类型Binary的函数指针集哦)。因此我们如果通过接口进行函数调用,实际的操作其实就是s.tab->fun[0](s.data)。是不是和C++的虚表很像?但是他们有本质的区别。先看C++,它为每个类创建了一个方法集即虚表,当子类重写父类的虚函数时,就将表中的相应函数指针改为子类自己实现的函数,如果没有则指向父类的实现,当面临多继承时,C++对象结构里就会存在多个虚表指针,每个虚表指针指向该方法集的不同部分。我们再来看golang的实现方式,同C++一样,golang也为每种类型创建了一个方法集,不同的是接口的虚表是在运行时专门生成的,而c++的虚表是在编译时生成的(但是c++虚函数表表现出的多态是在运行时决定的).例如,当例子中当首次遇见s := Stringer(b)这样的语句时,golang会生成Stringer接口对应于Binary类型的虚表,并将其缓存。那么为什么go不采用c++的方式来实现呢?这根c++和golang的对象内存布局是有关系的。

   首先c++的动态多态是以继承为基础的,在对象构造初始化的时首先会初始化父类,其次是子类,也就是说一个对象的内存布局是虚表,父类部分,子类部分(编译器不同可能会有差异),当一个父类指针指向子类时,会发生内存的截断,截断子类部分(内存地址偏移),但是此时子类的虚表中的函数指针实际上还是指向了自己的实现,所以此时的指针才会调用到子类的虚函数,如果不是虚函数,因为内存已经截断没有子类的非虚函数信息了,所以只能调用父类的了,这种继承关系让c++的虚表的初始化非常清晰,在一个对象初始化时先调用父类的构造此时虚表跟父类是一样的,接下来初始化子类,此时编译器就会去识别子类有没有覆盖父类的虚函数,如果有则虚表中相应的函数指针改成自己的虚函数实现指针。

  那么go有什么不同呢,首先我们很清楚go是没有严格意义上的继承的,go的接口不存在继承关系,只要实现了接口定义的方法都可以成为接口类型,这给go的虚表初始化带来很大的麻烦,到底有多少类型实现了这个接口,一个类型到底实现了多少接口这让编译器很confused。举个例子,某个类型有m个方法,某接口有n个方法,则很容易知道这种判定的时间复杂度为O(mXn),不过可以使用预先排序的方式进行优化,实际的时间复杂度为O(m+n)这样看来其实还行那为什么要在运行时生成虚表呢,这不是会拖慢程序的运行速度吗,注意我们这里是某个类型,某个接口,是1对1的关系,如果有n个类型,n个接口呢,编译器难道要把之间所有的关系都理清吗?退一步说就算编译器任劳任怨把这事干了,可是你在写过程中你本来就不想实现那个接口,而你无意中给这个类型实现的方法中包含了某些接口的方法,你根本不需要这个接口(况且go的接口机制会导致很多这种无意义的接口实现),你欺负编译器就行了,这也太欺负人了吧。如果我们放到运行时呢,我们只要在需要接口的去分析一下类型是否实现了接口的所有方法就行了很简单的一件事。

三 空接口

    接口类型的一个极端重要的例子是空接口:interface{},它表示空的方法集合,由于任何值都有零个或者多个方法,所以任何值都可以满足它。 注意,[]T不能直接赋值给[]interface{}

//t := []int{1, 2, 3, 4}   wrong
//var s []interface{} = t 
t := []int{1, 2, 3, 4}   //right
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}
str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

在Go语言中,我们可以使用type switch语句查询接口变量的真实数据类型,语法如下:

type Stringer interface {
    String() string
}

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str //type of str is string
case Stringer: //type of str is Stringer
    return str.String()
}
也可以使用“comma, ok”的习惯用法来安全地测试值是否为一个字符串:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}
四 接口赋值

  package main

  import (
  "fmt"
  )

  type LesssAdder interface {
      Less(b Integer) bool
      Add(b Integer)
  }

  type Integer int

  func (a Integer) Less(b Integer) bool {
      return a < b
  }

  func (a *Integer) Add(b Integer) {
      *a += b
  }

  func main() {

      var a Integer = 1
      var b LesssAdder = &a
      fmt.Println(b)

      //var c LesssAdder = a
      //Error:Integer does not implement LesssAdder 
      //(Add method has pointer receiver)
  }
    go语言可以根据下面的函数:
func (a Integer) Less(b Integer) bool 
自动生成一个新的Less()方法

func (a *Integer) Less(b Integer) bool 
这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。 而根据
func (a *Integer) Add(b Integer)
这个函数无法生成以下成员方法:
func(a Integer) Add(b Integer) {
    (&a).Add(b)
}

因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响(值传递),这不符合用户的预期。所以Go语言不会自动为其生成该函数。因此类型Integer只存在Less()方法,缺少Add()方法,不满足LessAddr接口。(可以这样去理解:指针类型的对象函数是可读可写的,非指针类型的对象函数是只读的)将一个接口赋值给另外一个接口 在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就等同的,可以相互赋值。 如果A接口的方法列表时接口B的方法列表的子集,那么接口B可以赋值给接口A,但是反过来是不行的,无法通过编译。



    

作者:D_Guco 发表于2017/11/12 20:16:21 原文链接
阅读:277 评论:0 查看评论

Quartz-Spring集成Quartz通过注解配置的方式

$
0
0

概述

上一篇博文Quartz-Spring集成Quartz通过XML配置的方式中我们了解到了通过xml配置的方式集成Quartz,我们发现使用xml的方式,会配置很多bean的信息,但是如果使用注解的方式,会更方便,配置注解相对简单。


步骤

配置文件中增加task命名空间

xmlns:task="http://www.springframework.org/schema/task" 

http://www.springframework.org/schema/task   
http://www.springframework.org/schema/task/spring-task.xsd

配置Spring扫描和task扫描

 <!-- 定时任务扫描 -->
 <task:annotation-driven/>

编写带有注解的Job类

详见示例部分


示例

这里写图片描述

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/task
                           http://www.springframework.org/schema/task/spring-task.xsd">

    <!-- 扫描组件 -->
    <context:component-scan base-package="com.xgj.quartz.quartzWithSpring.anno"/>

    <!-- 定时任务扫描 -->
    <task:annotation-driven/>

</beans>

带有注解的Job类

package com.xgj.quartz.quartzWithSpring.anno;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MyAnnoJob {

    @Scheduled(cron = "*/5 * * * * ?")
    // 每隔5秒执行一次
    public void test() throws Exception {
        System.out.println("Spring集成Quartz 使用 Annotation的方式......");
    }
}

测试类

package com.xgj.quartz.quartzWithSpring.anno;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringQuartzAnnoTest {
    public static void main(String[] args) {
        // 启动Spring 容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/quartz/quartzWithSpring/anno/spring-quartz-anno.xml");
        System.out.println("initContext successfully");
    }
}

运行结果

2017-11-12 08:44:44,353  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6fbdd27a: startup date [Sun Nov 12 08:44:44 BOT 2017]; root of context hierarchy
2017-11-12 08:44:44,436  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/quartz/quartzWithSpring/anno/spring-quartz-anno.xml]
2017-11-12 08:44:45,435  INFO [main] (ScheduledAnnotationBeanPostProcessor.java:262) - No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
initContext successfully
Spring集成Quartz 使用 Annotation的方式......
Spring集成Quartz 使用 Annotation的方式......
Spring集成Quartz 使用 Annotation的方式......
Spring集成Quartz 使用 Annotation的方式......
Spring集成Quartz 使用 Annotation的方式......
......
......
......
......省略....

@Scheduled解读

我们来看下源码

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String cron() default "";

    String zone() default "";

    long fixedDelay() default -1L;

    String fixedDelayString() default "";

    long fixedRate() default -1L;

    String fixedRateString() default "";

    long initialDelay() default -1L;

    String initialDelayString() default "";
}

可配置属性说明

属性类型 属性 属性说明
String cron cron的表达式
String zone cron表达式将被解析的时区
long fixedDelay 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法。
String fixedDelayString 在最后一次调用结束和下一次调用开始之间的固定时间段执行注释方法。
long fixedRate 在调用之间以固定的时间段执行带注释的方法。
String fixedRateString 在调用之间以固定的时间段执行带注释的方法。
long initialDelay 在首次执行fixedRate()或fixedDelay()任务之前要延迟的毫秒数。
String initialDelayString 在首次执行fixedRate()或fixedDelay()任务之前要延迟的毫秒数。

示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

作者:yangshangwei 发表于2017/11/12 20:31:07 原文链接
阅读:21 评论:0 查看评论

从零开始前端学习[52]:js中的数据类型以及没有数据类型的数据类型typeof

$
0
0

js中没有数据类型的数据类型typeof

  • js的相关数据类型
  • 使用typeof来查看数据类型

提示:
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041


js的数据类型

对于js来说,因为其是弱类型的语言,所以对于数据类型一类来说,相对比较弱化一点,一个var即可基本上表示所有,但是并不是代表其没有数据类型,跟java,跟C语言一类的语言来说,没有那么具体,那js下有哪些数据类型呢??主要也有以下几个分类:

  • 数字类型
  • 字符串类型
  • 函数类型
  • boolean类型
  • object对象类型(数组类型以及json类型)
  • undefined类型

    我们可以通过typeof来进行相关的查看;

数字类型

    var a = 10;

boolean类型

    var a = true;

字符串类型

var string = "l like js";

函数类型

var a = function(){
    函数体
}

object对象类型

var s = document;

对于对象类型来说,一定要记住:只有对象可以打点调用方法和属性
对象中的数组类型,js的数组与其他强类型的语言相比,其杂合度相对来说比较高,定义一个数组的时候,里面可以有不同的数组类型格式,如

var a = [1,2,3,'字符串',document];

还有一个就是json类型
json算是一种数据结构,非常重要的一种数据接口,有其自己遵循的一些规则

  • JSON,里面放置键值对,由逗号隔开,最后一个可以不加逗号
  • 键值对:由引号引起的属性 冒号 值构成
  • 就属性而言:可以不用引号,但有些后台语言不认识没有冒号的JSON,因此我们统一要求加冒号
  • 就值而言:可以是任意语句,但是一般传到后台的是字符串数据,在js自身去使用的话可以是任意类型

    js中的json格式是可以放置函数的,所以在与后台进行交互的时候,可以直接返回一个函数形式,如

     var json = {
        "name":"dean",
        "sex":"男",
        "age":10,
        "class":10,
        "fn":function(){console.log("=====")}
    };
    console.log(json.name); //调用里面的属性
    console.log(json.fn()); //调用里面的函数
    

null对象模型

如:var a;//只有声明,但是没有定义的数据类型

使用typeof来进行数据类型的查看

使用如下所示:
var a = 10;
console.log(typeof a);
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta charset="UTF-8"><!--申明当前网页的编码集UTF-8-->
  <meta name="Generator" content="EditPlus®">   <!--编辑器的名称-->
  <meta name="Author" content="作者是谁">       
  <meta name="Keywords" content="关键词">
  <meta name="Description" content="描述和简介">
  <style type="text/css">                                        
        body,dl,dd,dt,p,h1,h2,h3,h4,h5,h6{ margin: 0;}
        ul,ol{margin: 0; list-style: none; padding: 0;}
        a{ text-decoration: none; }
        *{ margin: 0; padding: 0; }
  </style>
</head>
<body>
<script>
  var number = 10.1234;
  var boolean = false;
  var array = [1,2,"222","string"];
  var null_type;
  var object = document;
  function myfunction() {
      console.log("my function");
  }
  var function_type = myfunction;
  var jsontype  = {
      "name":"dean",
      "sex":"男",
      "age":10,
      "class":10,
      "fn":function(){console.log("=====")}
  }
  console.log("number_type:"+typeof number);
  console.log("boolean_type:"+typeof boolean);
  console.log("array_type:"+typeof array);
  console.log("null_type:"+typeof null_type);
  console.log("object_type:"+typeof object);
  console.log("function_type:"+typeof function_type);
  console.log("jsontype:"+typeof jsontype);
</script>
</body>
</html>

显示效果如下所示:很清晰的可以看到,其对应的数据类型
这里写图片描述

作者:qq_29924041 发表于2017/11/12 20:36:58 原文链接
阅读:142 评论:0 查看评论

Angular 4入门教程系列:12:TypeScript:类与接口

$
0
0

传统的javascript使用函数来创建可重用的组件,而熟悉面向对象设计和开发的则更加熟悉类/接口/集成等面向对象的方式。而这些在ECMAScript 2015得到了支持,javascript也可以使用面向对象的设计方法了,但是在ES5尚未完全得到全部支持的现在,通过使用typescript了先行使用此类特性在我们的项目之中。
由于面向对象的编程已经是极为基础,我们只需要看一下typescript中是如何实现的即可。我们将会通过几个简单的例子来研究一下相关的基本使用方法。

Animal类

这篇文章的例子,将会写在一个名为class-practice.ts的文件之中。首先我们先在这个文件之中实现一个Animal的类。

[root@angular proj]# grep class tsconfig.json 
        "src/class-practice.ts"
[root@angular proj]# 
[root@angular proj]# cat src/class-practice.ts 
class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

let animal = new Animal("Dog");
animal.move(100);
[root@angular proj]#

编译&结果确认

[root@angular proj]# gulp
[07:14:13] Using gulpfile /tmp/proj/gulpfile.js
[07:14:13] Starting 'default'...
[07:14:22] Finished 'default' after 9.17 s
[root@angular proj]# node dist/class-practice.js 
Dog moved 100m.
[root@angular proj]# 

确认编译生成的js代码:

[root@angular proj]# cat dist/class-practice.js 
var Animal = /** @class */ (function () {
    function Animal(theName) {
        this.name = theName;
    }
    Animal.prototype.move = function (distanceInMeters) {
        console.log(this.name + " moved " + distanceInMeters + "m.");
    };
    return Animal;
}());
var animal = new Animal("Dog");
animal.move(100);
[root@angular proj]#

Animal的类写的非常的简单,但是从这几行简单的代码中,我们可以了解到typescript的一些使用的特点:

  • 成员函数需要用this指针进行引用
  • 修饰符也是有public/private/protected三种
  • 缺省的情况下,成员时public的
  • private只能在定义的类中进行使用
  • protected能够在定义的类和继承的类中使用,但不能在实例中直接引用
  • 整体的类等相关使用方式和C#或者C++非常类似
  • readonly修饰符设定只读属性,只能在声明或者构造函数中被初始化
  • 虚函数的使用也同C++大同小异

Horse类

稍微对程序进行修改,创建一个Horse类继承Animal类,同时将Animal类的name设定为protected方式。具体代码如下:

[root@angular proj]# cat src/class-practice.ts 
class Animal {
    protected name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Horse extends Animal {
    public constructor(name: string) { super(name); }
    public move(distanceInMeters = 45) {
        console.log("super name: " + this.name);
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let animal = new Animal("Dog");
animal.move(100);

let horse = new Horse("Horse");
horse.move(200);
[root@angular proj]# 

执行和结果确认

[root@angular proj]# gulp
[07:27:38] Using gulpfile /tmp/proj/gulpfile.js
[07:27:38] Starting 'default'...
[07:27:52] Finished 'default' after 14 s
[root@angular proj]# node dist/class-practice.js 
Dog moved 100m.
super name: Horse
Galloping...
Horse moved 200m.
[root@angular proj]# 

get/set

就像javabean的get和set方法那样,typescript也支持get和set方式,只是写法稍有不同,比如如下一个例子,我们有一个private的成员变量,而对实例进行直接赋值的使用方式则是因为set和get存在的原因。

[root@angular proj]# grep 2 tsconfig.json
        "src/class-practice-2.ts"
[root@angular proj]# cat src/class-practice-2.ts 
let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}
[root@angular proj]#

执行和结果确认

[root@angular proj]# gulp
[07:39:42] Using gulpfile /tmp/proj/gulpfile.js
[07:39:42] Starting 'default'...
[07:39:55] Finished 'default' after 13 s
[root@angular proj]# node dist/class-practice-2.js 
Bob Smith
[root@angular proj]# 

interface

接口方式使用也比较类似,比如如下例子:

[root@angular proj]# cat src/interface-practice.ts 
interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
[root@angular proj]#

执行&结果确认

[root@angular proj]# gulp
[07:43:25] Using gulpfile /tmp/proj/gulpfile.js
[07:43:25] Starting 'default'...
[07:43:35] Finished 'default' after 10 s
[root@angular proj]# node dist/interface-practice.js 
Size 10 Object
[root@angular proj]#

总结

这篇文章练习和确认了typescript中如何进行面向对象编程方式进行设计和编码。

作者:liumiaocn 发表于2017/11/12 20:46:37 原文链接
阅读:165 评论:0 查看评论

【数据结构】线性结构——删除

$
0
0

    通过前面几次的博文,我们已经对线性结构的定义和一些基本运算,比如初始化、判空、插入,有了基本的了解,对于代码的熟悉程度也大大提高。本篇博文,小编将和大家一起学习继续学习线性结构运算——删除。

链式存储

(一)单链表

这里写图片描述

void DeleteLinklist(Linklist head,int i)
//链式存储——删除结点,删除表head的第i个结点
{
    Node *q;
    if(i==1) q=head;
    else q=GetLinklist(head,i-1);     //先找待删结点的直接前驱
    if(q!=NULL && q->next!=NULL)      //若直接前驱存在且待删结点存在
    {
        p=q->next;                    //p指向待删结点
        q->next=p->next;              //移出待删结点
        free(p);                      //释放已移出结点p的空间
    }
    else exit("找不到要删除的结点");  //结点不存在
}

    思路分析:删除单链表的某个结点需要先判断该结点和它的前一个结点是否存在,如果存在,就开始改变指针的指向。如图所示,本操作的指针共涉及三个,步骤共分为两步:(1)描述三个结点之间的链式关系 (2)将第ai-1个结点的指针指向第ai+1个结点,此时ai结点被删除。


(二)出栈

这里写图片描述

int Pop(LkStk *LS)
//链式存储——出栈,栈顶数据元素通过参数返回,它的直接后继成为新的栈顶
{
    LkStk *temp;
    if(!EmptyStack(LS))       //判断栈是否为空
    {   temp=LS->next;        //temp指向栈顶结点
        LS->next=temp->next;  //原栈顶的下一个结点成为新的栈顶
        free(temp);           //释放原栈顶结点空间
        return 1;
    }
    else return 0;
}

    思路分析:出栈操作始终是栈顶结点出栈,即删除除头结点之后的结点。因此,指针的变化体现在栈顶数据元素结点的变化,每出栈一个结点,指向栈顶数据元素的指针改变一次。


(三)出队

这里写图片描述

OutQueue(LkQue *LQ)
//链式存储——出队
{
    LkQueNode *temp;
    if(EmptyQueue(CQ))                 //判队列是否为空                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
        {error("队空");return 0;}      //队列为空
    else{                             //队列非空
        temp=(LQ->front)->next;       //使temp指向队列的首结点
        (LQ->front)->next=temp->next; //修改头结点的指针域指向新的首结点
        if(temp->next==NULL)
        LQ->rear=LQ->front;           //无首结点时,front和rear都指向头结点
        free(temp);
        return 1;   
    }
}

    思路分析:与出栈操作类似,差别在于栈是先进后出,队列是先进先出,因此删除结点后的指针变化有所不同,栈的指针变化表现为指向栈顶数据元素结点的指针,队列的指针变化表现为指向首结点的指针。


异同点

(一)同—整体思路:

1. 判断要删除的结点是否存在(判空)
2. 找到待删除的结点
3. 改变指针指向

(二)异

1. 结构不同->图形表示不同->代码表示
2. 线性表:找到待删结点和它的前一个结点,删除完成后,将待删结点的前一个结点指向待删结点的后一个结点;栈:栈顶结点出栈,即栈顶数据元素的指针改变;队列:先入队的结点先出队,即首结点的指针在改变。



顺序存储

(一)线性表

void DeleteSeqList(SeqList L,int i)
//顺序存储,线性表删除结点
{
    if(i<1||i>L.length)         //检查位置是否合法
        exit("非法位置");
    for(j=i;j<L.length;j++)     //第i个元素的下标为i-1
        L.data[j-1]=L.data[j];  //依次左移
    L.length--;                 //表长度减一
}

    思路分析:删除结点会导致两个变化:原结点位置,线性表长度。
(1)检查删除结点是否存在
(2)若存在,将该结点之后的结点依次左移
(3)线性表长度减一


(二)出栈

int Pop(SeqStk *stk)
//顺序存储——出栈
{
    if(EmptyStack(stk))            //判断是否下溢(栈空)
        {error("下溢");return 0;}
    else{                          //未下溢,栈顶元素出栈
        stk->top--;                
        return 1;
    }
}

    思路分析:(1)判断栈空 (2)栈顶元素出栈(栈顶指针指向最新的栈顶元素)


(三)出队

int OutQueue(CycQue CQ)
//顺序存储——出队
{
    if(EmptyQueue(CQ))                  //判队列是否为空
        {error("队列空");return 0;}      //队列为空,出队列失败
    else{
        CQ.front=(CQ.front+1)%maxsize;  //不为空,出队列
        return 1;                       //出队列成功
    }
}

    思路分析:(1)判断队空 (2)首结点出队,首指针指向最新的首结点。


异同点

(一)同—整体思路:

1. 判断要删除的结点是否存在(判空)
2. 相关结点删除

(二)异

1. 结构不同->图形表示不同->代码表示
2. 线性表:结点删除,通过该结点以后的结点依次左移,线性表长度减少完成;栈:通过栈顶元素指针变化完成;队列:通过首结点的指针变化完成。



小结

    删除操作的关键在于,首先我们要知道有没有这个结点,然后删除该结点,并且保证原存储结构所有内容保持正确。
   每一次总结都会发现对相关内容的掌握加深了一些,这就是总结的魅力所在。

作者:LJL55555 发表于2017/11/12 21:27:26 原文链接
阅读:35 评论:0 查看评论

Quartz-JobDataMap 参数传递

$
0
0

概述

我们在Quartz-Job 详解中,讲解了纯Quartz的参数传递,这里我们将通过与Spring结合的方式再次讲解一下Quartz中的参数传递。


JobDataMap

JobDataMap可用于保存任何数量的(可序列化的)数据对象,我们希望在执行时可以将其提供给作业实例。JobDataMap是Java Map接口的一个实现,并且有一些用于存储和检索原始类型的数据的方便方法。

我们来温习下纯Quartz的写法

// 具体任务 JobDetail
JobDetail job = JobBuilder.newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        .usingJobData("jobSays", "Hello World!")
        .usingJobData("myFloatValue", 3.141f)
        .build();

HelloJob如下

/**
 * 具体执行的任务
 */
public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String jobSays = dataMap.getString("jobSays");
        float myFloatValue = dataMap.getFloat("myFloatValue");
        System.out.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
}

运行结果

Instance group1.job1 of DumbJob says: Hello World!, and val is: 3.141
Instance group1.job1 of DumbJob says: Hello World!, and val is: 3.141

如果在Job类中定义与JobDataMap中键值一致的set和get方法,那么Quartz会自动将这些属性注入。如:

// 具体任务 JobDetail
JobDetail job = JobBuilder.newJob(HelloJob.class)
    .withIdentity("job1", "group1")
    .usingJobData("name", "artisan")
    .usingJobData("age", 24).build();

HelloJob

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 具体执行的任务
 */
public class HelloJob implements Job {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        System.out.println("Instance " + key + ",姓名:" + name + ",年龄:" + age);
    }
}

运行结果

Instance group1.job1,姓名:artisan,年龄:24
Instance group1.job1,姓名:artisan,年龄:24
Instance group1.job1,姓名:artisan,年龄:24

另外Trigger中也可以设置JobDataMap属性,这是为了在多个Trigger中使用相同的Job。

JobExecutionContext 将会合并JobDetail与Trigger的JobDataMap,如果其中属性名相同,后者将覆盖前者。

可以使用JobExecutionContext.getMergedJobDataMap()方法来获取合并后的JobDataMap。


Xml 配置方式的值的传递

在spring中,如果HelloJob的方法有参数,那么需要指定一些设定才可以,否则会在运行时有NoSuchMethodException异常发生。

简单示例

public class HelloJob {

    public void execute(String args){
        System.out.println("------ " + args + "------ ");
    }
}

xml配置

<!-- 配置JobDetail -->
<bean id="springQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <!-- 执行目标job -->
    <property name="targetObject" ref="helloJob"></property>

    <!-- 要执行的方法 -->
    <property name="targetMethod" value="execute"></property>

    <!-- 设置参数-->
    <property name="arguments" value="artisan"></property>
</bean>

增加一个arguments的变量,设置一个value即可,如果你点进去观察源代码,会发现,它其实是一个 Object[] 类型的参数

执行结果

------ artisan------ 
------ artisan------

如果你想传递2个参数,示例如下

public void execute(String name, int age) {
    System.out.println("------ " + name + ":" + age + "------ ");
}
<!-- 设置参数-->
<property name="arguments">
   <list>
       <value>artisan</value>
       <value>23</value>
   </list>
</property>

当然,我们也可以传递Map,Set,JavaBean对象等等,这里只是抛砖引玉。


Quartz官方示例

示例演示了静态变量和非静态变量的修改,只有静态成员才能改变

import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class ColorJob implements Job {

    public static final String FAVORITE_COLOR = "favorite color";
    public static final String EXECUTION_COUNT = "count";

    // 由于Quartz会在每次执行时重新实例化一个类,因此成员非静态成员变量不能用于维护状态!
    private int _counter = 1;

    public void execute(JobExecutionContext context) throws JobExecutionException {

        JobKey jobKey = context.getJobDetail().getKey();

        JobDataMap data = context.getJobDetail().getJobDataMap();
        String favoriteColor = data.getString(FAVORITE_COLOR);
        int count = data.getInt(EXECUTION_COUNT);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        System.out.println("任务Key: " + jobKey + " ,执行时间:  " + sdf.format(new Date()) + "\n" +
                "  传递参数(favorite color): " + favoriteColor + "\n" +
                "  传递参数(count):  " + count + "\n" +
                "  ColorJob非静态变量值: " + _counter + "\n");

        count++;
        data.put(EXECUTION_COUNT, count);
        data.put(FAVORITE_COLOR, "黄色");

        _counter++;
    }

}
package com.anson.examples.example4;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

import static org.quartz.DateBuilder.nextGivenSecondDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

public class JobStateExample {

    public void run() throws Exception {
        Logger log = LoggerFactory.getLogger(JobStateExample.class);
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

        //在当前时间10秒后运行
        Date startTime = nextGivenSecondDate(null, 10);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");


        //通过过JobDetail封装ColorJob,同时指定Job在Scheduler中所属组及名称,这里,组名为group1,而名称为job1。
        JobDetail job1 = newJob(ColorJob.class)
                .withIdentity("job1", "group1")
                .build();

        // 创建一个SimpleTrigger实例,指定该Trigger在Scheduler中所属组及名称。
        // 接着设置调度的时间规则.当前时间10秒后运行,每10秒运行一次,共运行4次
        SimpleTrigger trigger1 = newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(10)
                        .withRepeatCount(4))
                .build();

        //将参数传递入任务的数据Map中
        job1.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "绿色");
        job1.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);

        //注册并进行调度
        Date scheduleTime1 = sched.scheduleJob(job1, trigger1);

        log.error("任务key: " + job1.getKey() + ",执行运行时间: " + sdf.format(scheduleTime1) +
                ",触发器重复执行次数: " + trigger1.getRepeatCount() +
                ",触发器执行时间: " + trigger1.getRepeatInterval() / 1000 + "秒");


        //第二个任务
        JobDetail job2 = newJob(ColorJob.class)
                .withIdentity("job2", "group1")
                .build();

        SimpleTrigger trigger2 = newTrigger()
                .withIdentity("trigger2", "group1")
                .startAt(startTime)
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(10)
                        .withRepeatCount(4))
                .build();

        //传递数据
        job2.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "红色");
        job2.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);


        Date scheduleTime2 = sched.scheduleJob(job2, trigger2);

        log.error("第二个任务key: " + job2.getKey().toString() + ",执行运行时间: " + sdf.format(scheduleTime2) +
                ",触发器重复执行次数: " + trigger2.getRepeatCount() +
                ",触发器执行时间: " + trigger2.getRepeatInterval() / 1000 + "秒");


        //调度器启动
        sched.start();

        try {
            Thread.sleep(60L * 1000L);
        } catch (Exception e) {
        }

        //调度器停止
        sched.shutdown(true);

        SchedulerMetaData metaData = sched.getMetaData();
        log.error("Executed " + metaData.getNumberOfJobsExecuted() + " jobs.");

    }

    public static void main(String[] args) throws Exception {

        JobStateExample example = new JobStateExample();
        example.run();
    }

}

执行结果如下

[INFO] 11 九月 03:04:13.742 下午 main [com.anson.examples.example4.JobStateExample]
任务key: group1.job1,执行运行时间: 2017-09-11 03:04:20,触发器重复执行次数: 4,触发器执行时间: 10秒
[INFO] 11 九月 03:04:13.742 下午 main [com.anson.examples.example4.JobStateExample]
第二个任务key: group1.job2,执行运行时间: 2017-09-11 03:04:20,触发器重复执行次数: 4,触发器执行时间: 10秒
[INFO] 11 九月 03:04:13.742 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyScheduler_$_NON_CLUSTERED started.
任务Key: group1.job2 ,执行时间:  2017-09-11 03:04:20
  传递参数(favorite color): 红色
  传递参数(count):  1
  ColorJob非静态变量值: 1
任务Key: group1.job1 ,执行时间:  2017-09-11 03:04:20
  传递参数(favorite color): 绿色
  传递参数(count):  1
  ColorJob非静态变量值: 1
任务Key: group1.job1 ,执行时间:  2017-09-11 03:04:30
  传递参数(favorite color): 黄色
  传递参数(count):  2
  ColorJob非静态变量值: 1
任务Key: group1.job2 ,执行时间:  2017-09-11 03:04:30
  传递参数(favorite color): 黄色
  传递参数(count):  2
  ColorJob非静态变量值: 1
任务Key: group1.job1 ,执行时间:  2017-09-11 03:04:40
  传递参数(favorite color): 黄色
  传递参数(count):  3
  ColorJob非静态变量值: 1
任务Key: group1.job2 ,执行时间:  2017-09-11 03:04:40
  传递参数(favorite color): 黄色
  传递参数(count):  3
  ColorJob非静态变量值: 1
任务Key: group1.job1 ,执行时间:  2017-09-11 03:04:50
  传递参数(favorite color): 黄色
  传递参数(count):  4
  ColorJob非静态变量值: 1
任务Key: group1.job2 ,执行时间:  2017-09-11 03:04:50
  传递参数(favorite color): 黄色
  传递参数(count):  4
  ColorJob非静态变量值: 1
任务Key: group1.job1 ,执行时间:  2017-09-11 03:05:00
  传递参数(favorite color): 黄色
  传递参数(count):  5
  ColorJob非静态变量值: 1
任务Key: group1.job2 ,执行时间:  2017-09-11 03:05:00
  传递参数(favorite color): 黄色
  传递参数(count):  5
  ColorJob非静态变量值: 1
[INFO] 11 九月 03:05:13.747 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyScheduler_$_NON_CLUSTERED shutting down.
[INFO] 11 九月 03:05:13.747 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyScheduler_$_NON_CLUSTERED paused.
[INFO] 11 九月 03:05:14.142 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyScheduler_$_NON_CLUSTERED shutdown complete.
[INFO] 11 九月 03:05:14.142 下午 main [com.anson.examples.example4.JobStateExample]
Executed 10 jobs.

Job的状态与并发

@DisallowConcurrentExecution:同一时间将只有一个Job实例被执行。

@PersistJobDataAfterExecution:在Job被执行结束后,将会更新JobDataMap,这样下次Job执行后就会使用新的值而不是初始值。 上面的示例中,如果不用此注解,成员变量的值下次调用也不会有改变。

如果使用@PersistJobDataAfterExecution注解,推荐也使用@DisallowConcurrentExecution注解,这是为了避免并发问题导致数据紊乱。

其它属性

Durability,持久性;如果Job是非持久性的,一旦没有Trigger与其相关联,它就会从Scheduler中被删除。也就是说Job的生命周期和其Trigger是关联的。

RequestsRecovery,如果为true,那么在Scheduler异常中止或者系统异常关闭后,当Scheduler重启后,Job会被重新执行。

JobExecutionException

execute()方法只允许抛出JobExecutionException异常

作者:yangshangwei 发表于2017/11/12 22:07:49 原文链接
阅读:7 评论:0 查看评论

python基础-13-PyCharm工具常见问题

$
0
0

PyCharm工具使用技巧:

1、新建Python文件头部注释代码

File->settings->Editor->File and Code Templates->Python Script

模板:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : ${DATE} ${TIME}
# @Author  : DoubleChina
# @Site    : ${SITE}
# @File    : ${NAME}.py
# @Software: ${PRODUCT_NAME}

2、Python文件命名最好不要使用Test开头

使用Test开头命名的文件出现下面问题,无法运行,只能运行Unittests单元测试
出现该问题是Test开头的文件和系统单元测试文件重复,IDE默认当做单元测试运行了

这里写图片描述

作者:lianjiaokeji 发表于2017/11/12 22:51:51 原文链接
阅读:14 评论:0 查看评论

Python学习笔记(九)—Python中的赋值、浅拷贝和深拷贝(含图)

$
0
0

转载请注明出处:http://blog.csdn.net/sinat_14849739/article/details/78516061
本文出自Shawpoo的专栏
我的简书:简书

【Python学习笔记专栏】:http://blog.csdn.net/column/details/17658.html

在学习Python的过程中,经常会对变量进行“赋值”,有时候又要对变量的值做临时处理,而又不能改变原来变量的值,这就可能用到了“浅拷贝”和“深拷贝”。所以下面通过具体的例子来区分它们三者的不同。

另外,大多数面向对象的语言都会涉及到变量的“赋值”、“浅拷贝”和“深拷贝”,如Python,Java或者C++等。

一、赋值

在Python中,赋值很简单,例如,将一个变量的值赋值给另一个变量,其实就是将这两个变量指向同一个内存地址。所以如果这个变量的值改变了,那么另一个变量的值也会跟着改变,因为它们的内存地址始终相同。例:

a = [1, 2, 3, ['a', 'b', 'c']]
b = a
print(id(a), id(b)) #打印它们各自的内存地址
print(a)
print(b)
a.append(4)
print("-----------")
print(id(a), id(b))
print(a)
print(b)
a[3].append('d')
print("-----------")
print(id(a), id(b))
print(a)
print(b)
print(a is b)

执行结果:
2524323181384 2524323181384
[1, 2, 3, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b', 'c']]
-----------
2524323181384 2524323181384
[1, 2, 3, ['a', 'b', 'c'], 4]
[1, 2, 3, ['a', 'b', 'c'], 4]
-----------
2524323181384 2524323181384
[1, 2, 3, ['a', 'b', 'c', 'd'], 4]
[1, 2, 3, ['a', 'b', 'c', 'd'], 4]
True

通过上述的例子可以看出,在“赋值”的情况下,变量b的值始终随着变量a的值改变而改变,也就是内存地址始终相同。

图解: b = a: 赋值引用,a 和 b 都指向同一个对象。

直接赋值

二、浅拷贝

浅拷贝,只拷贝父对象,不会拷贝父对象中的子对象。如果该对象没有子对象的话,其浅拷贝和赋值的结果是一样的。浅拷贝可以通过导入copy模块中的copy()方法来完成,或者调用对象本身的copy()方法。例:

import copy # 导入copy模块
a = [1, 2, 3, ['a', 'b', 'c']]
b = copy.copy(a)   # a.copy()
print(id(a), id(b))
print(a is b)
print(a)
print(b)
a.append(4)
print("-----------")
print(id(a), id(b))
print(a)
print(b)
a[3].append('d')
print("-----------")
print(id(a), id(b))
print(a)
print(b)
print(a is b)
print(a[3] is b[3])

运行结果:
1558199464776 1558199464904
False
[1, 2, 3, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b', 'c']]
-----------
1558199464776 1558199464904
[1, 2, 3, ['a', 'b', 'c'], 4]
[1, 2, 3, ['a', 'b', 'c']]
-----------
1558199464776 1558199464904
[1, 2, 3, ['a', 'b', 'c', 'd'], 4]
[1, 2, 3, ['a', 'b', 'c', 'd']]
False
True

通过上述的例子可以看出,我们改变变量a中父对象的值,不会影响变量b,但是改变变量a中子对象的值,那么变量b也会随之改变,这就是浅拷贝,只拷贝父对象,而不拷贝子对象。

图解: b = copy.copy(a): 浅拷贝, a 和 b 是一个独立的对象,但它们的子对象还是指向同一对象(内存地址)。

浅拷贝

三、深拷贝

深拷贝,也就是完全拷贝,包括拷贝对象的父对象及其子对象。拷贝后是
两个完全独立的对象,互不影响。我们可以通过copy模块中的deepcopy()方法来完成。

import copy

a = [1, 2, 3, ['a', 'b', 'c']]
b = copy.deepcopy(a)
print(id(a), id(b))
print(a is b)
print(a)
print(b)
a.append(4)
print("-----------")
print(id(a), id(b))
print(a)
print(b)
a[3].append('d')
print("-----------")
print(id(a), id(b))
print(a)
print(b)
print(a is b)
print(a[3] is b[3])

执行结果:
3143729499336 3143729558024
False
[1, 2, 3, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b', 'c']]
-----------
3143729499336 3143729558024
[1, 2, 3, ['a', 'b', 'c'], 4]
[1, 2, 3, ['a', 'b', 'c']]
-----------
3143729499336 3143729558024
[1, 2, 3, ['a', 'b', 'c', 'd'], 4]
[1, 2, 3, ['a', 'b', 'c']]
False
False

通过上述的例子可以看出,不管我们改变变量a中父对象的值,还是改变子对象的值,都不会影响变量b的值,那么说明此时变量a和变量b已经完全独立,这就是深拷贝,拷贝父对象及其子对象。

图解: b = copy.deepcopy(a): 深拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

深拷贝

总结

  • 赋值:其实就是对象地址的引用。
  • 浅拷贝(copy):调用copy模块的copy()方法,只拷贝父对象,不会拷贝对象内部的子对象。
  • 深拷贝(deepcopy): 调用copy模块的deepcopy()方法,完全拷贝了父对象及其子对象。
作者:sinat_14849739 发表于2017/11/12 22:57:16 原文链接
阅读:39 评论:0 查看评论

HDU2027 统计元音【入门】

$
0
0

统计元音

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 77785    Accepted Submission(s): 30681


Problem Description
统计每个元音字母在字符串中出现的次数。
 

Input
输入数据首先包括一个整数n,表示测试实例的个数,然后是n行长度不超过100的字符串。
 

Output
对于每个测试实例输出5行,格式如下:
a:num1
e:num2
i:num3
o:num4
u:num5
多个测试实例之间由一个空行隔开。

请特别注意:最后一块输出后面没有空行:)
 

Sample Input
2 aeiou my name is ignatius
 

Sample Output
a:1 e:1 i:1 o:1 u:1 a:2 e:1 i:3 o:0 u:1
 

Author
lcy
 

Source

问题链接HDU2027 统计元音

问题简述:(略)

问题分析:这是一个字母统计问题,统计对象和统计结果放在数组中比较好,用循环处理即可。

程序说明

  ACM题通常是输入多组数据,所以需要注意循环控制!

  输出需要注意输出格式。

  EOF的值是-1,加上C语言的整数是补码表示,则作为整数其每一个二进制位均为1。~是按位非运算符,~EOF值为0。

题记:(略)

 

AC的C语言程序如下

/* HDU2027 统计元音 */

#include <stdio.h>
#include <string.h>

#define LEN 5
#define N 100
char s[N + 1];

int main(void)
{
    int n, i, j, k;
    char vowel[] = "aeiou";
    int count[LEN];

    scanf("%d", &n);
    getchar();
    for(i=1; i<=n; i++) {
        gets(s);

        memset(count, 0, sizeof(count));
        j = 0;
        while(s[j] != '\0') {
            for(k=0; k<LEN; k++)
                if(s[j] == vowel[k])
                    count[k]++;
            j++;
        }

        /* 输出结果 */
        if(i != 1)
            printf("\n");
        for(j=0; j<LEN; j++)
            printf("%c:%d\n", vowel[j], count[j]);
    }

    return 0;
}






作者:tigerisland45 发表于2017/11/12 23:30:18 原文链接
阅读:57 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>