Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

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 原文链接
阅读:38435 评论: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 原文链接
阅读:33981 评论: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 原文链接
阅读:34027 评论: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 原文链接
阅读:29404 评论: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 原文链接
阅读:31914 评论:4 查看评论

《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 原文链接
阅读:37116 评论:20 查看评论

xamarin android 打造ListView万能适配器

$
0
0

早些时候接触xamarin android 的列表,写了很多ListView的Adapter,建一个ListView就写一个Adapter,每一个Adapter里面还有去写一个ViewHolder的类来优化,自从看了hongyang博客的listview万能适配器的文章,学习良多,所以就写篇关于xamarin android ListView通用适配器的文章。

本章主要分为以下三点:

  1. 打造通用的ViewHolder优化ListView性能
  2. 使用泛型Adapter适应不同布局的ListView
  3. C#委托和Java匿名内部类的比较

打造通用的ViewHolder优化ListView性能

  public  class ViewHolder:Java.Lang.Object
    {
        private  SparseArray<View>  Views;
         View ConvertView;
        private Context context ;
         int mPosition;
        private ViewHolder(Context _context,ViewGroup  parent ,int itemLayoutId,int position)
        {
            this.mPosition = position;
            Views = new SparseArray<View>();
            ConvertView =  LayoutInflater.From(_context).Inflate(itemLayoutId,null);
            ConvertView.Tag = this;
        }
        public static ViewHolder Get(Context context , View convertView,ViewGroup  parent ,int itemLayoutId,int position)
        {
            if (convertView == null)
            {
                return new ViewHolder(context, parent, itemLayoutId, position);
            }
            else
            {
                ViewHolder holder = (ViewHolder)convertView.Tag;
                holder.mPosition = position;
                return holder;
            }
        }
        public T GetView<T>(int viewId) where T :View
        {
            View view = Views.Get(viewId);
            if (view == null)
            {
                view = ConvertView.FindViewById<T>(viewId);
                Views.Put(viewId,view);
            }
            return (T)view;
        }
        public View GetConvertView()
        {
            return ConvertView;
        }
        /// <summary>
        /// 给TextView 设置文本
        /// </summary>
        /// <param name="viewId"></param>
        /// <param name="text"></param>
        /// <returns></returns>
        public ViewHolder SetText(int viewId ,string  text)
        {
            TextView view = GetView<TextView>(viewId);
            view.Text = text;
            return this;
        }

        /// <summary>
        /// 给ImageView 设置图片
        /// </summary>
        public ViewHolder SetImageBitMap(int viewId , Bitmap bm)
        {
            ImageView view = GetView<ImageView>(viewId);
            view.SetImageBitmap(bm);
            return this;
        }
    }

稍微解释一下:
ViewHolder的构造方法中初始化变量,并将ListView的布局view的Tag设置成viewholder的实例
Get方法使用单例模式初始化ViewHolder
GetView泛型方法获取ListView布局中的空间
SetText、SetImageBitMap给一些常用的控件复值

使用泛型Adapter适应不同布局的ListView

CommonAdapter是一个泛型的基类,一些重复的重写方法都在这个基类,然后我们写一个子类继承这个CommonAdapter,就简化了很多代码

 public abstract class CommonAdapter<T> :BaseAdapter
    {
        Context mContext;
         List<T> mData;
         int mItemLayoutId;
        public CommonAdapter(Context context, List<T> data, int itemLayoutId):base()
        {
            this.mContext = context;
            mData = data;
            mItemLayoutId = itemLayoutId;
        }
        public override int Count
        {
            get
            {
                return mData.Count;
            }
        }
        public override  Java.Lang.Object GetItem(int position)
        {
            return null;

        }
        public override long GetItemId(int position)
        {
            return position;
        }
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            var item = mData[position];
            ViewHolder viewHolder = ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
            convert(viewHolder,mData[position]);

            System.Diagnostics.Debug.Write(position);
            return viewHolder.GetConvertView();
        }
        public abstract void convert(ViewHolder helper, T item);
        public ViewHolder GetViewHolder(int position, View convertView, ViewGroup parent)
        {
            return ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
        }
    }

写一个newsAdapter继承CommonAdapter,只需要将赋值的代码写在重写方法convert里面极客,这样就简化了很多的代码

    public class NewsAdapter<T> : CommonAdapter<T>
    {
        public NewsAdapter(Context context, List<T> data, int resId) : base(context, data, resId)
        {

        }

        public override void convert(ViewHolder helper, T item)
        {
            NewsViewModel model = (NewsViewModel)Convert.ChangeType(item, typeof(NewsViewModel));
            helper.SetText(Resource.Id.tv_news_title, model.Title);
            helper.SetText(Resource.Id.tv_news_id, model.NewsID.ToString());
            helper.SetText(Resource.Id.tv_news_desc, model.Desc);
        }
    }

C#委托和Java匿名内部类的比较

看来上面的例子,虽然简化了很多代码,但是每个ListView还是得新建一个Adapter,看来hongyang大神的博客在java中用匿名内部类实现一个Adapter万能通用,但是c#中没有匿名内部内的概念,这就尴尬了。。。。。
c#中虽然没有这种概念,但语言是相通的,但是委托可以实现。万能通用的adapter代码如下:

    public class Common1Adapter<T> : BaseAdapter
    {
        Context mContext;
        List<T> mData;
        int mItemLayoutId;
        public delegate View GetViewEvent(int position, View convertView, ViewGroup parent, T item, ViewHolder viewHolder);
        public event GetViewEvent OnGetView;
        public Common1Adapter(Context context, List<T> data, int itemLayoutId) : base()
        {
            this.mContext = context;
            mData = data;
            mItemLayoutId = itemLayoutId;
        }
        public override int Count
        {
            get
            {
                return mData.Count;
            }
        }
        public override Java.Lang.Object GetItem(int position)
        {
            return null;

        }
        public override long GetItemId(int position)
        {
            return position;
        }
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            var item = mData[position];
            ViewHolder viewHolder = ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
            if (OnGetView != null)
                return this.OnGetView(position,convertView,parent,item,viewHolder);
            return convertView;
        }
    }
}

在使用时,只需要在Activity中注册OnGetView 事件,这样多个ListView的Adapter就只需要一个通用的Adapter就行了

   View OnGetView(int position,View  convertView,ViewGroup parent,NewsViewModel model,ViewHolder viewHolder)
        {
            viewHolder.SetText(Resource.Id.tv_news_title, model.Title);
            viewHolder.SetText(Resource.Id.tv_news_id, model.NewsID.ToString());
            viewHolder.SetText(Resource.Id.tv_news_desc, model.Desc);
            return viewHolder.GetConvertView();
        }
   List<NewsViewModel> list_news = new List<Adaptes.NewsViewModel>() {
                new Adaptes.NewsViewModel () { NewsID =1,Title="测试标题124578cdascdas",Desc ="测acdsdas试内内容10c2da4s内容10c2da4s内容10c2da4s容10c2da4sc5das4cdas"},
                new Adaptes.NewsViewModel () { NewsID =2,Title="cascascda24578cdascdas",Desc ="测dasc试内容10c2da4s内容10c2da4s内容10c2da4s内容10c2da4sc5das4cdas"},
            };
            Common1Adapter<NewsViewModel> adapter = new Adaptes.Common1Adapter<Adaptes.NewsViewModel>(this, list_news, Resource.Layout.item_listview_news);
            adapter.OnGetView += OnGetView;
            lv_news.Adapter = adapter;

作者:张林
标题:xamarin android 打造ListView万能适配器
原文地址:http://blog.csdn.net/kebi007/article/details/77101947
转载随意注明出处

作者:kebi007 发表于2017/8/17 17:55:11 原文链接
阅读:927 评论:2 查看评论

Unity 代码实现表针转动

$
0
0
要求:
* 有时针、分针、秒针:
* 按照机械手表的转动方式转动(秒针每一秒走一格,分针每一分钟内走完一格, 时针一小时内走完一格大格)
public class ex1 : MonoBehaviour {

    float hour;
    float min;
    float sec;
	// Use this for initialization
	void Start () {
        InvokeRepeating("Rot", 1, 1);
    }
	
	// Update is called once per frame
	void Update () {
        
        if (sec % 60 == 0 && sec > 0)
        {
            min++;
            sec = 0;
        }

        if (min % 60 == 0 && min > 0)
        {
            hour++;
            min = 0;
        }
    }
    void Rot()
    {
        GameObject.Find("sco").GetComponent<Transform>().rotation = Quaternion.Euler(0, (sec++) * 6, 0);
        GameObject.Find("min").GetComponent<Transform>().rotation = Quaternion.Euler(0, min * 6, 0);
        GameObject.Find("hour").GetComponent<Transform>().rotation = Quaternion.Euler(0, hour * 6, 0);
    }
}

需要注意的是:每个指针都需要一个空对象作为父对象,然后将我们要转动的表针(子对象)调整到相对理想的位置(即调整相对于旋转中心的位置),因为我们要控制父对象使得子对象旋转,,,还有注意代码中的名字是和创建父对象的名字是一致的,,,



作者:Czhenya 发表于2017/8/17 18:32:15 原文链接
阅读:19 评论:0 查看评论

Unity 物体跟随鼠标点击移动,判断物体是否在移动

$
0
0

在场景中创建一个Plane和一个Sphere以及3CubePlane做为地面,Sphere作为人物,Cube作为敌人。用户通过点击地面,使Sphere移动到点击的位置(不能使用导航网格寻路)Sphere如果接近敌人一定距离后,敌人会跟随Sphere行动。如果敌人在主角旁边静止2s则主角消失。

public class cube : MonoBehaviour {

    public Sphere sphere;
    float des; //与主角之间的距离

    // Use this for initialization
    void Start () {
        
    }
	
	// Update is called once per frame
	void Update () {
       
        des = Vector3.Distance(sphere.transform.position, this.gameObject.transform.position);
        if(des < 3 && des > 2)
        {       //看向主角并跟随;
            this.gameObject.transform.LookAt(sphere.transform.position);
            this.gameObject.transform.Translate(Vector3.forward * Time.deltaTime);

        }
        
    }

public class Sphere : MonoBehaviour {

    Ray ray;
    RaycastHit rh;
    float dis;      //鼠标与球的距离
    Vector3 tar;   //目标点

    private Vector3 lastPos;    //上一次运动停止的位置
    private float lastTime;     //上一次运动停止的时间

    // Use this for initialization
    void Start () {
        lastPos = transform.position;
        lastTime = 0;
    }
	
	// Update is called once per frame
	void Update () {

        if (Input.GetMouseButtonDown(0))   
        {      //左键,获得鼠标射线
            ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out rh))     
            {               
                if (rh.collider.name == "Plane")   //判碰撞物体是否是地面
                {
                    tar = rh.point;
                    
                    //Debug.Log(rh.point+"2");
                }
            }

        }
        dis = Vector3.Distance(this.gameObject.transform.position, tar);
        if (dis > 0.5f)   //鼠标点击与球的距离
        {
            this.gameObject.transform.LookAt(tar);
            this.gameObject.transform.Translate(Vector3.forward * Time.deltaTime);
        }
        if (dis < 3)
        {
            if (lastPos != transform.position)  //如果上次静止的位置和当前位置不相同,就更新上次静止的位置和时间
            {
                lastTime = Time.time;
                lastPos = transform.position;
            }
            if (Time.time - lastTime > 2)       //如果静止时间>2s  
            {
                lastTime = Time.time;
                Destroy(this.gameObject);
            }
        }
    }
}

作者:Czhenya 发表于2017/8/17 18:42:15 原文链接
阅读:28 评论:0 查看评论

geopackage-android 开源的地理空间信息数据库存储

$
0
0

GeoPackage一个开放的地理空间信息的格式,基于标准的、平台独立的,可移植的、自描述、紧凑格式将地理空间信息。遵循OGC标准,数据库内核使用ormlite数据库。GeoPackage 数据库表可以转换成title进行地图绘制,有自已的表的边界范围等等。

github网址:https://github.com/ngageoint/geopackage-android

仓库地址:http://repo1.maven.org/maven2/mil/nga/geopackage/

这篇博客示例代码下载:http://download.csdn.net/download/qq_16064871/9937001

1、实现效果





2、简单的介绍

geopackage的数据库内核使用ormlite数据库

FeatureDao 带有Geometry的表,就是说这张表可以存储点,线,面,多边形等等的存储,当然还有其它属性。

AttributesDao 一般的数据库表。跟一般数据库字段基本是一样的。

数据库存储格式是gpkg。这种格式也是地图格式之一。不过一般存储的数据相当于数据库数据,不能直接加载与地图显示(osmdroid 加载gpkg格式的离线底图)。需要转换成title。或者自已拿出数据,进行屏幕上地图分辨率的转换,进行绘制也是可以的。在这里就先不做过多介绍。

下面主要介绍数据库的创建,增删查改。

3、数据库操作

创建数据库

        mGeoManager = GeoPackageFactory.getManager(mContext);

        File file = new File(FilePathManage.GetInstance().getMapDirectoryPath(), GeopackageConstants.CREATE_ELEVATION_TILES_DB_FILE_NAME); //有数据库,直接打开

        if (!file.exists()) {
            try {
                if (!mGeoManager.createAtPath(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME, FilePathManage.GetInstance().getMapDirectory())) {
                    Toast.makeText(mContext, "数据库创建失败", Toast.LENGTH_SHORT).show();
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(mContext, "open storage permission", Toast.LENGTH_SHORT).show();
                return;
            }
        }


创建FeatureDao 表,以及增删查改等示例代码

package com.custom.geopackage;


import android.content.ContentValues;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import mil.nga.geopackage.BoundingBox;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.attributes.AttributesCursor;
import mil.nga.geopackage.db.GeoPackageDataType;
import mil.nga.geopackage.features.columns.GeometryColumns;
import mil.nga.geopackage.features.user.FeatureColumn;
import mil.nga.geopackage.features.user.FeatureCursor;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.features.user.FeatureRow;
import mil.nga.geopackage.geom.GeoPackageGeometryData;
import mil.nga.geopackage.projection.ProjectionConstants;
import mil.nga.geopackage.schema.TableColumnKey;
import mil.nga.wkb.geom.Geometry;
import mil.nga.wkb.geom.GeometryType;
import mil.nga.wkb.geom.Point;

/**
 * 点的操作类
 */
public class PointGeoOperation extends GeoOperationBase {

    private FeatureDao mPointTableDao;

    public PointGeoOperation(GeoPackage geoPackage) {
        super(geoPackage);
    }

    public void init() {
        if (mGeoPackage != null)
        mPointTableDao = mGeoPackage.getFeatureDao(GeopackageConstants.POINT_TABLE);
    }

    public FeatureDao getSurveyDao() {
        return mPointTableDao;
    }

    public boolean createTable() {
        //不存在表才创建表
        if (mGeoPackage.isFeatureOrTileTable(GeopackageConstants.POINT_TABLE)){
            return true;
        }

        try {
            //创建表
            double minLat=-90+1; double maxLat=90-1;
            double minLon=-180+1;double maxLon= 180-1;

            BoundingBox boundingBox = new BoundingBox(minLon,
                    maxLon, minLat, maxLat);

            GeometryColumns geometryColumns = new GeometryColumns();
            //设置id
            geometryColumns.setId(new TableColumnKey(GeopackageConstants.POINT_TABLE,
                    "geom"));
            //todo GeometryType 可以选择 点 线 面 多边形等等
            geometryColumns.setGeometryType(GeometryType.POINT);
            geometryColumns.setZ((byte) 1);

            /**
             * 首先构建表的列,然后再构建表对象
             */
            int index = 0;
            List<FeatureColumn> columns = new ArrayList<FeatureColumn>();
            columns.add(FeatureColumn.createPrimaryKeyColumn(index++,
                    GeopackageConstants.FID));
            columns.add(FeatureColumn.createGeometryColumn(index++,
                    geometryColumns.getColumnName(), geometryColumns.getGeometryType(), false, null));
            columns.add(FeatureColumn.createColumn(index++, GeopackageConstants.POINT_NAME, GeoPackageDataType.TEXT, true, ""));
            columns.add(FeatureColumn.createColumn(index++, GeopackageConstants.CODE, GeoPackageDataType.TEXT, true, ""));

            if (mGeoPackage != null)
                mGeoPackage.createFeatureTableWithMetadata(geometryColumns, boundingBox, ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM, columns);
        }  catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean deleteTable() {
        if (mGeoPackage != null)
        mGeoPackage.deleteTable(GeopackageConstants.POINT_TABLE);
        return true;
    }

    public boolean  insertData(Geometry geometry,ContentValues values) {
        try {
            GeoPackageGeometryData geomData = new GeoPackageGeometryData(
                    mPointTableDao.getGeometryColumns().getSrsId());
            geomData.setGeometry(geometry);
            FeatureRow row = mPointTableDao.newRow();
            row.setGeometry(geomData);
            row.setValue(GeopackageConstants.POINT_NAME,values.getAsString(GeopackageConstants.POINT_NAME));
            row.setValue(GeopackageConstants.CODE,values.getAsString(GeopackageConstants.CODE));
            mPointTableDao.insert(row);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

    public boolean deleteData(int ID) {
        String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
        return mPointTableDao.delete(queSQL,null) > 0;
    }

    private List<ContentValues> getAllSurveyPoint() {
        List<ContentValues> arrayslist = new ArrayList<ContentValues>();
        if (mPointTableDao != null) {
            FeatureCursor cursor = mPointTableDao.queryForAll();
            while (cursor.moveToNext()) {
                ContentValues contentValues = cursor.getRow().toContentValues();
                arrayslist.add(0, contentValues);
            }
        }
        return arrayslist;
    }

    public List<FeatureRow> getAllPoint() {
        List<FeatureRow> arrayslist = new ArrayList<FeatureRow>();
        if (mPointTableDao != null) {
            FeatureCursor cursor = mPointTableDao.queryForAll();
            while (cursor.moveToNext()) {
                FeatureRow row = cursor.getRow();
                Point point = (Point) row.getGeometry().getGeometry();
                arrayslist.add(row);
            }
        }
        return arrayslist;
    }

    public void updateWithID(int ID,ContentValues values) {
        String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
        if (mPointTableDao != null)
        mPointTableDao.update(values,queSQL,null);
    }

    public boolean updateGeomtry(FeatureRow row)
    {
        return  mPointTableDao.update(row) > 0;
    }
}


创建AttributesDao 表,增删查改的示例代码

package com.custom.geopackage;

import android.content.ContentValues;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.attributes.AttributesColumn;
import mil.nga.geopackage.attributes.AttributesCursor;
import mil.nga.geopackage.attributes.AttributesDao;
import mil.nga.geopackage.attributes.AttributesRow;
import mil.nga.geopackage.db.GeoPackageDataType;

/**
 * 线操作类
 */
public class LineGeoOperation extends GeoOperationBase{

    //普通表
    private AttributesDao commonDao;

    public LineGeoOperation(GeoPackage geoPackage) {
        super(geoPackage);
    }

    public void init() {
        commonDao = mGeoPackage.getAttributesDao(GeopackageConstants.LINE_TABLE);
    }

    public AttributesDao getSurveyDao() {
        return commonDao;
    }

    public boolean createTable() {
        //不存在表才创建表
        if (mGeoPackage.isTable(GeopackageConstants.LINE_TABLE)){
            return true;
        }

        int index = 1;
        List<AttributesColumn> additionalColumns = new ArrayList<>();
        additionalColumns.add(AttributesColumn.createColumn(index++, GeopackageConstants.POINT_NAME,
                GeoPackageDataType.TEXT, false, ""));
        additionalColumns.add(AttributesColumn.createColumn(index++, GeopackageConstants.CODE,
                GeoPackageDataType.TEXT, false, ""));
        //根据需要在继续添加,等等
        mGeoPackage.createAttributesTable(GeopackageConstants.LINE_TABLE, GeopackageConstants.FID, additionalColumns);
        return true;
    }

    public boolean deleteTable() {
        mGeoPackage.deleteTable(GeopackageConstants.POINT_TABLE);
        return true;
    }

    public boolean  insertData(ContentValues values) {
        try {
            AttributesRow row = commonDao.newRow();
            row.setValue(GeopackageConstants.POINT_NAME,values.getAsString(GeopackageConstants.POINT_NAME));
            row.setValue(GeopackageConstants.CODE,values.getAsString(GeopackageConstants.CODE));
            long fid = commonDao.insert(row);

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public boolean deleteData(int ID) {
        String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
        return commonDao.delete(queSQL,null) > 0;
    }

    private List<ContentValues> getAllSurveyPoint() {
        AttributesCursor cursor = commonDao.queryForAll();
        List<ContentValues> arrayslist = new ArrayList<ContentValues>();
        while (cursor.moveToNext()) {
            ContentValues contentValues = cursor.getRow().toContentValues();
            arrayslist.add(0, contentValues);
        }
        return arrayslist;
    }

    public List<AttributesRow> getAllPoint() {
        AttributesCursor cursor = commonDao.queryForAll();
        List<AttributesRow> arrayslist = new ArrayList<AttributesRow>();
        while (cursor.moveToNext())
        {
            AttributesRow row = cursor.getRow();
            arrayslist.add(row);
        }
        return arrayslist;
    }

    public void updateWithID(int ID,ContentValues values) {
        String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
        commonDao.update(values,queSQL,null);
    }

    public boolean updateAttributesRow(AttributesRow row)
    {
        return  commonDao.update(row) > 0;
    }
}

这两个类的操作管理示例代码,就是一些初始化,打开数据库等

package com.custom.geopackage;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.widget.Toast;

import java.io.File;

import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageManager;
import mil.nga.geopackage.db.metadata.GeoPackageMetadata;
import mil.nga.geopackage.db.metadata.GeoPackageMetadataDb;
import mil.nga.geopackage.factory.GeoPackageFactory;

public class CustomGeoPackageManager {

    private static GeoPackageManager mGeoManager;
    private Context mContext;
    private static CustomGeoPackageManager myGeoPackageManager = null;
    GeoPackage mGeoPackage;
    PointGeoOperation mPointGeoOperation = null;
    LineGeoOperation mLineGeoOperation = null;
    PolygonGeoOperation mPolygonGeoOperation = null;

    private CustomGeoPackageManager(Activity context) {
        this.mContext = context;

        mGeoManager = GeoPackageFactory.getManager(mContext);

        File file = new File(FilePathManage.GetInstance().getMapDirectoryPath(), GeopackageConstants.CREATE_ELEVATION_TILES_DB_FILE_NAME); //有数据库,直接打开

        if (!file.exists()) {
            try {
                if (!mGeoManager.createAtPath(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME, FilePathManage.GetInstance().getMapDirectory())) {
                    Toast.makeText(mContext, "数据库创建失败", Toast.LENGTH_SHORT).show();
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(mContext, "open storage permission", Toast.LENGTH_SHORT).show();
                return;
            }
        }

        initGeopackageMetadata();

        mGeoPackage = mGeoManager.open(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME);

        if (mGeoPackage == null) return;
        getPointGeoOperation().createTable();
        getPointGeoOperation().init();

        getLineGeoOperation().createTable();
        getLineGeoOperation().init();
    }

    public static CustomGeoPackageManager getInstance(Activity context) {
        if (myGeoPackageManager == null && context != null)
            synchronized (CustomGeoPackageManager.class) {
                if (myGeoPackageManager == null)
                    myGeoPackageManager = new CustomGeoPackageManager(context);
            }

        return myGeoPackageManager;
    }

    public GeoPackageManager getGeoPackageManager() {
        if (mGeoManager == null) {
            GeoPackageFactory.getManager(mContext);
        }
        return mGeoManager;
    }

    public PointGeoOperation getPointGeoOperation() {
        if (mPointGeoOperation == null) {
            mPointGeoOperation = new PointGeoOperation(mGeoPackage);
        }
        return mPointGeoOperation;
    }

    public LineGeoOperation getLineGeoOperation() {
        if (mLineGeoOperation == null) {
            mLineGeoOperation = new LineGeoOperation(mGeoPackage);
        }
        return mLineGeoOperation;
    }

    public PolygonGeoOperation getPolygonGeoOperation() {
        if (mPolygonGeoOperation == null) {
            mPolygonGeoOperation = new PolygonGeoOperation(mGeoPackage);
        }
        return mPolygonGeoOperation;
    }

    private void initGeopackageMetadata() {
        //TODO 卸载软件时候会把geopackage数据库内部存储的数据路径卸载掉,这里为了查找加上去
        File file = new File(FilePathManage.GetInstance().getMapDirectoryPath());
        File[] projectList = file.listFiles();

        GeoPackageMetadataDb dbHelper = new GeoPackageMetadataDb(mContext);
        dbHelper.open();
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        if (projectList == null ) return;
        for (File file1 : projectList) {
            String projectName = file1.getName();
            Cursor query = db.query(GeoPackageMetadata.TABLE_NAME, null, "name=?", new String[]{projectName}, null, null, null);
            if (query.getCount() <= 0) {
                ContentValues values = new ContentValues();
                values.put(GeoPackageMetadata.COLUMN_NAME, projectName);
                values.put(GeoPackageMetadata.COLUMN_EXTERNAL_PATH, FilePathManage.GetInstance().getMapDirectoryPath() + "/" + projectName + ".gpkg");
                db.insert(GeoPackageMetadata.TABLE_NAME, null, values);
            }
            query.close();

        }
    }
}

看看activity怎么进行数据示例调用

   @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button: //删除
                String temp = mIDeditText.getText().toString().trim();
                if(temp.isEmpty()) return;
                int id = Integer.parseInt(temp);
                if(id>0){
                    if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().deleteData(id)){
                        Toast.makeText(this,"delete succeed",Toast.LENGTH_SHORT).show();
                    }else
                        Toast.makeText(this,"not exit id",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.button1: //增加
                Geometry geometry = new Point(23,113);
                ContentValues values = new ContentValues();
                values.put(GeopackageConstants.POINT_NAME,"point_name");
                values.put(GeopackageConstants.CODE,"test_code");
                if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().insertData(geometry,values)){
                    Toast.makeText(this,"add succeed",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.button2: //修改
                //编辑点返回参数
                if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().size()>3){
                    FeatureRow row = CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().get(2);
                    GeoPackageGeometryData geomData = new GeoPackageGeometryData(
                            CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getSurveyDao().getGeometryColumns().getSrsId());
                    Point point = new Point(true,false,24, 114);
                    point.setZ(new Double(114));
                    geomData.setGeometry(point);
                    row.setGeometry(geomData);
                    row.setValue(GeopackageConstants.POINT_NAME,"point_name2");
                    row.setValue(GeopackageConstants.CODE,"test_code");
                    if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().updateGeomtry(row)){
                        Toast.makeText(this,"update succeed",Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            case R.id.button3: //查询
                String strResult = "the table have "
                        + String.valueOf(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().size());

                Toast.makeText(this,strResult,Toast.LENGTH_SHORT).show();
                break;
            /////////////////////////////////////////////////////////////////////////////
            //普通表操作

            case R.id.button5: //删除
                String temp1 = mIDeditText1.getText().toString().trim();
                if(temp1.isEmpty()) return;
                int id1 = Integer.parseInt(temp1);
                if(id1>0){
                    if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().deleteData(id1)){
                        Toast.makeText(this,"delete succeed",Toast.LENGTH_SHORT).show();
                    }else
                        Toast.makeText(this,"not exit id",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.button4: //增加
                ContentValues values1 = new ContentValues();
                values1.put(GeopackageConstants.POINT_NAME,"line_name");
                values1.put(GeopackageConstants.CODE,"test_code");
                if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().insertData(values1)){
                    Toast.makeText(this,"add succeed",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.button6: //修改
                if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().size()>3){
                    AttributesRow row = CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().get(2);
                    row.setValue(GeopackageConstants.POINT_NAME,"point_name2");
                    row.setValue(GeopackageConstants.CODE,"test_code");
                    if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().updateAttributesRow(row)){
                        Toast.makeText(this,"update succeed",Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            case R.id.button7: //查询
                String strResult1 = "the table have "
                        + String.valueOf(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().size());

                Toast.makeText(this,strResult1,Toast.LENGTH_SHORT).show();
                break;
        }
    }

数据库存储涉及到在机器上存储读写的权限

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

就先介绍到这里








作者:qq_16064871 发表于2017/8/18 8:59:12 原文链接
阅读:143 评论:0 查看评论

Unity 代码实现锁定手机横屏

$
0
0
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
   


public class groy : MonoBehaviour {	
	void Awake()
        {
                //设置屏幕正方向在Home键右边
                Screen.orientation = ScreenOrientation.LandscapeRight;
        }

        void Start () 
        {
                //设置屏幕自动旋转, 并置支持的方向
                Screen.orientation = ScreenOrientation.AutoRotation;
                Screen.autorotateToLandscapeLeft = true;
                Screen.autorotateToLandscapeRight = true;
                Screen.autorotateToPortrait = false;
                Screen.autorotateToPortraitUpsideDown = false;
        }
}

作者:Czhenya 发表于2017/8/18 10:09:10 原文链接
阅读:22 评论:0 查看评论

React Native入门(八)之网络请求Fetch

$
0
0

前言

最近有些事情要忙,RN相关的内容没有更新,现在就来了解一下RN中进行网络请求相关的内容!

介绍

React Native提供了和web标准一致的Fetch API,用于满足开发者访问网络的需求。
关于Fetch API的相关内容,可以到下边网站了解:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
我们这先大致说一下在RN中使用Fetch API 进行网络请求的例子,然后再去了解Fecth的相关内容!

使用Fetch

Fetch API 提供一个fetch()方法,要从任意地址获取内容的话,只需简单地将网址作为参数传递给fetch方法即可。

fetch('https://facebook.github.io/react-native/movies.json')

Fetch API 是基于 Promise 设计,那么关于 Promise的内容可以参考下面的教程:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
这里简单提一下:
我们知道网络请求是一个天然的异步操作,Fetch 方法会返回一个Promise,这种模式可以简化异步风格的代码。Promise 对象是一个返回值的代理,它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的promise 对象来替代原返回值。

Promise有两个重要的方法,分别是resolve方法和reject方法。如果异步操作成功,则用resolve方法将Promise对象的状态变为“成功”(即从pending变为resolved);如果异步操作失败,则用reject方法将状态变为“失败”(即从pending变为rejected)。

好了,知道了这些之后,我们来说一下如果处理网络请求返回的结果!

Fetch 方法会返回一个Promise,一个 Promiseresolve时会回传 Response 对象,也就是说请求成功,会回传一个Response 对象,这个Response 对象就包含了所有返回结果的信息!

好,下面看一个例子,也是RN中文网的例子,这里做了一些改动!
首先,我们在浏览器看一下网络请求到的内容:

{
  "title": "The Basics - Networking",
  "description": "Your app fetched this from a remote endpoint!",
  "movies": [
    { "title": "Star Wars", "releaseYear": "1977"},
    { "title": "Back to the Future", "releaseYear": "1985"},
    { "title": "The Matrix", "releaseYear": "1999"},
    { "title": "Inception", "releaseYear": "2010"},
    { "title": "Interstellar", "releaseYear": "2014"}
  ]
}

这个一个请求到一些电影信息的json数据。
这里我们使用fetch发送网络请求,然后将得到的json数据使用列表展示出来,下面看一下代码:

class NetText extends Component {
  constructor(props) { //构造器
    super(props);
    this.state = {  //定义的三个状态
      title: '',
      description: '',
      movies: ''
    };
  }

  //列表点击事件
  itemClick(item, index) {
    alert('点击了第' + index + '项,电影名称:' + item.title + '上映年份:' + item.releaseYear);
  }

  //FlatList的key
  _keyExtractor = (item, index) => index;

  //子item渲染
  _renderItem = ({item, index}) => {
    return (
      <TouchableOpacity
        activeOpacity={0.5}
        onPress={this.itemClick.bind(this, item, index)}>
        //这里index为奇偶数给不同的背景
        <View style={[WebTestStyles.item, {backgroundColor: index % 2 == 0 ? 'lightgray' : 'whitesmoke'}]}>
          <Text style={WebTestStyles.itemTitle}>{item.title}</Text>
          <Text style={WebTestStyles.itemYear}>上映年份:{item.releaseYear}</Text>
        </View>

      </TouchableOpacity>
    );
  }

  //列表分割线
  _itemDivide = () => {
    return (
      <View style={{height: 1, backgroundColor: 'dimgray'}}/>
    );
  }

  //getMoviesFromApiAsync() {
  _fetchData = () => {
    fetch('https://facebook.github.io/react-native/movies.json')
      .then((response) => response.json())//把response转为json格式
      .then((jsondata) => {    //上面的转好的json
        //alert(jsondata.movies[0].title);
        this.setState({ //将获取到的数据更新到state中
          title: jsondata.title,
          description: jsondata.description,
          movies: jsondata.movies,
        })
      })
      .catch((error) => {
        console.error(error);
      });
  };

  render() {
    return (
      <View style={WebTestStyles.container}>
        <Text style={WebTestStyles.title}>{this.state.title}</Text>
        <Text style={WebTestStyles.description}>{this.state.description}</Text>
        <FlatList   //列表组件
          style={{marginTop: 20}}
          data={this.state.movies}
          keyExtractor={this._keyExtractor}
          renderItem={this._renderItem}
          ItemSeparatorComponent={this._itemDivide}/>
        <Button title="请求网络" onPress={this._fetchData}/>
      </View>
    );
  }
}
;

const WebTestStyles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column'
  },

  title: {
    fontSize: 20,
    fontWeight: 'bold',
    alignSelf: 'center'

  },

  description: {
    fontSize: 12,
    alignSelf: 'center'
  },

  item: {
    padding: 10
  },
  itemTitle: {
    fontSize: 18,
    fontWeight: 'bold'
  },
  itemYear: {
    fontSize: 16,
  }
})

然后看一下效果:
这里写图片描述

代码的话,基本上没有什么好说的,就是点击一个按钮去请求网络,然后拿到json格式的数据,两个文本字段,一个数组,然后把数组设置为FlatList的data,将电影以列表形式展示!

Fetch相关API

请求参数的设置

上面我们在请求网络的时候,只是用fetch(url)这样的形式,我们也可以设置其他的参数来请求网络,如下所示:

  • method: 请求使用的方法,如 GETPOST
  • headers: 请求的头信息,形式为 Headers 对象或 ByteString。
  • body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息
  • mode: 请求的模式,如 corsno-cors 或者 same-origin
  • credentials: 请求的 credentials,如 omitsame-origin 或者 include
  • cache: 请求的 cache 模式: default, no-store, reload, no-cache, force-cache, 或者 only-if-cached

类似这样:

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  })
})

如果你的服务器无法识别上面POST的数据格式,那么可以尝试传统的form格式,示例如下:

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'key1=value1&key2=value2'
})

这个得话,就需要和做后台的同事提前沟通好就行了!

自定义Header

关于请求头Header的话,还可以通过 Headers() 构造函数来创建一个你自己的 headers 对象。

var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", content.length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");

也可以传一个多维数组或者对象字面量:

myHeaders = new Headers({
  "Content-Type": "text/plain",
  "Content-Length": content.length.toString(),
  "X-Custom-Header": "ProcessThisImmediately",
});

自定义Request

除了上面的用法,我们还可以通过使用Request()构造函数来创建一个request对象,然后再作为参数传给fetch()。这里就相当于把url和header或者其他的参数综合起来!
比如:

var url='https://mywebsite.com/endpoint/';
var myOptions = { method: 'GET',
               headers: myHeaders,
               mode: 'cors',
               cache: 'default' };
var myRequest = new Request(url, myOptions );

fetch(myRequest)
  .then()
  ...

Response

属性:

  • status (number) - HTTP请求结果参数,在100–599 范围
  • statusText (String) - 服务器返回的状态报告
  • ok (boolean) - 如果返回200表示请求成功则为true
  • headers (Headers) - 返回头部信息,下面详细介绍
  • url (String) - 请求的地址

方法:

  • text() - 以string的形式生成请求text
  • json() - 生成JSON.parse(responseText)的结果
  • blob() - 生成一个Blob
  • arrayBuffer() - 生成一个ArrayBuffer
  • formData() - 生成格式化的数据,可用于其他的请求

其他方法:

  • clone()
  • Response.error()
  • Response.redirect()

上边就是Fetch常用的API,其他的API可以参考Fetch_API,也就是上边的网站!

参考文章
【译】fetch用法说明
传统 Ajax 已死,Fetch 永生

作者:aiynmimi 发表于2017/8/18 11:56:47 原文链接
阅读:59 评论:0 查看评论

Android Retrofit报错NoClassDefFoundError异常

$
0
0

使用Retrofit 2时踩了个坑,自己封装的Retrofit工具在4.4的手机上跑崩了,测试了下机型,发现在Android 5.x以上的手机就不会出现这样的情况,而在Android 4.x手机上这个问题是必现的

错误信息

java.lang.NoClassDefFoundError: retrofit2.Retrofit$Builder

一看到这个错误信息,马上联想到是否是类缺失了,但是这里指向的是Retrofit2.Retrofit$Builder,导入第三方类库怎么还会缺少类呢,而且如果是类缺失,那么在程序编译期间就会报错的,仔细一看是自己眼花了,NoClassDefFoundError错误和ClassNotFoundException经常容易被搞混,这两个错误类型虽然都指的是找不到类,但是差异还是挺大的

ClassNotFoundExceptio

这个错误比较好解决,就是程序找不到指定的class类,就会出现这个问题,通常发生在程序的编译期间

NoClassDefFoundError

这个错误就比较难找了,在程序正常编译后还报这个未找到类的异常,也就是说这个class类在程序的编译阶段并没有缺失,但是在程序运行阶段,无法把它正常的从内存中加载出来,我们通常使用new方法来实例化一个类,如果这个类在程序运行的时候“失踪”了,那么就会抛出这个异常。

    class A {
        public String getMethod(){
            return "method";
        }
    }
    public class B {
        public static void main(String[] args)
        {
            A a = new A();
            System.out.println(a.getMethod());
        }
    }

就比如说,我们有两个类A和B,B调用A中的方法,我们编译好后,实际上是生成了两个.class文件,这个时候如果我们把A.class文件删掉,再运行程序,那么就会出现NoClassDefFoundError异常了。

解决方法

了解了异常产生的原因,就开始对症下药了,居然在编译期间没有出错,那么说明类文件并没有缺失,而是在编译完成后,运行程序时无法正常的获取到Retrofit$Builder类。

在Android中,我们经常会使用到分包方法,即multiDexEnabled,尤其是当我们的程序应用了大量第三方框架的时候,这个参数就更重要了,因为Android中编译的单个.dex文件最多支持65536个方法,而如果大于这个方法数,就需要使用到multiDexEnabled方法对Dalvik可执行文件.dex进行分包了,而在分包的时候就可能把Retrofit需要使用到的方法分到不同的dex文件中,所以问题应该就出在multiDexEnabled的使用上了。

android {
    ...
    defaultConfig {
        ...
        multiDexEnabled true
    }
    ...
}

这个分包方法,是在Android 5.0之后提供的,如果程序运行在5.0以上的手机,版本设置为minSdkVersion 21以上的应用来说,以上这么配置的方式就可以了

而我在程序中指定的最低支持到的版本是API 19,所以为了兼容Android 5.0以下的手机,需要在build.gradle添加个依赖库com.android.support:multidex:1.0.1

android {
    ...
    defaultConfig {
        ...
        multiDexEnabled true
    }
    ...
}

dependencies {
  compile 'com.android.support:multidex:1.0.1'
}

同时,对于被我们替换的Application类,需要继承的是MultiDexApplication,而不是Application

public class AppApplication extends MultiDexApplication {...}

通过以上的正确方法配置好了之后,再次运行,就没有再报NoClassDefFoundError错误了。

  • 如果你不想继承MultiDexApplication类,而需要继承其它类型的Application类,那么可以通过重写其attachBaseContext方法,在其中
    手动调用 Dalvik 可执行文件分包方法MultiDex.install()
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(context);
        MultiDex.install(this);
    }
  • 如果没有替换Application类,那么就需要在配置文件AndroidManifest.xml中指定<application>标签的name为android.support.multidex.MultiDexApplication
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.text">
    <application
            android:name="android.support.multidex.MultiDexApplication" >
            ...
    </application>
</manifest>

总结

事实上这个错误并不是由于Retrofit本身造成的,而是错误的使用multiDexEnabled方法造成的,对于多Android版本的兼容性处理的不够好,导致出现了上述问题

当应用程序指定了最小支持的版本号时,应该以这个最小版本号为准,最好适配工作,使用兼容包去处理高低版本之间的差异性问题。

作者:zhuwentao2150 发表于2017/8/18 14:49:47 原文链接
阅读:144 评论:0 查看评论

一个界面管理多种状态方案

$
0
0

前言

很多项目中进行会出现,一个状态对应一种现实样式。是统一在一个界面处理,还是分开n多页面处理呢,
这篇给出了一点个人建议,及是实现方案。

效果图

这里写图片描述

方案一: 一个activity 全部进行控制。所有的状态。完全靠堆代码。(不推荐)
方案二 一个activity 中包含BaseFragment 然后 subfragment中实现不同状态。(推荐)
方案3

方案一:

优点:

 代码量少,处理简单ui问题。完全可以写到一个activity中。

缺点:

 后期维护,绝对是噩梦,7,8种状态,来回变换,各种逻辑穿插。导致类膨胀。

方案二:

优点:

 结构简单,单个类代码量少,清晰。便于维护,扩展性高,符合单一职责

缺点:

 前期工作量相对大于方案一

2.方案二 uml类图:

这里写图片描述

抽取基类就是为了复用。 能复用的尽量放入基类,统一入口。

基类职责:

  1.N多subFragment 公用一套 xml。(如果使用N个布局对应N个subfragment,重复工作量太大。)初始化root布局

2.特殊布局,分发给subFragment去处理,公共区域更新由基类完成。

3.统一处理一些逻辑。这里是处理状态对应文案,以及文案颜色等

4.作为工厂类,生成对应样式的subFragment

subFragmnet 职责:

1.更新ui
2.隐藏不需要的xml 组件。默认展示所有xml组件。

Activity :

1.作为BaseFragment容器,当然这是常识,fragment无法单独用来展示ui,必须依附于Activity。
2. 作为Mvp模式中的view,网络请求成功会先走到Activity中,然后通过baseFramgnet,分发数据到具体subFragment中。(fragmen同样可以作为View,这里为了统一处理,较小难度。用Activity做为View)

Eg:mvp模式不在这里介绍。有需要了解的自行前往google 官方sample地址查看。

说道这里觉得少了点:

贴代码:

subFragment

public class subFragment1 extends BaseBorrowFragment {

    private static BorrowCancelFragment instance;

    public static BorrowCancelFragment getInstance() {
        if (instance == null) {
            instance = new BorrowCancelFragment();
        }
        return instance;
    }

    @Override
    public View initLayout(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return super.initLayout(inflater, container, savedInstanceState);
    }

    @Override
    void updateUi(LoanDetailResponse loanDetailResponse) {
        LoanApplyItemEntity loanApplyItem = loanDetailResponse.loanApplyItem;
        nyBorrowMoney.setRightString(getUnitString(loanApplyItem.applyAmount));
        nyBorrowDeadline.setRightString(getDataStyle(loanApplyItem));
        nyBorrowDate.setRightString(loanApplyItem.lendTime);

        nyPayWay.setRightString(loanApplyItem.repaymentTypeStr);
        nyPayCancelReason.setRightString(loanApplyItem.cancelReason);
    }

    @Override
    public void init() {

    }

    @Override
    public void initView() {
        super.initView();
    }

    @Override
    public void initValue() {
        nyBorrowProtocol.setVisibility(View.GONE);
        nyNeedPayDate.setVisibility(View.GONE);
        nyAlreadyMoney.setVisibility(View.GONE);
        nyWaitMoney.setVisibility(View.GONE);
        nyRealPayDate.setVisibility(View.GONE);
        nyRealPayMoney.setVisibility(View.GONE);
    }

    @Override
    public void initListener() {

    }
}

baseFragment

public abstract class BasexFragment extends BaseFragment {

    protected Activity baseActivity;
    protected OnContactSelectedListener mListener; //回调监听
    protected LinearLayout lineLayer1, lineLayer2, lineLayer3;
    protected NyCompatView nyBorrowMoney, nyBorrowDeadline, nyBorrowDate,
            nyBorrowProtocol, nyPayWay, nyNeedPayDate, nyAlreadyMoney,
            nyWaitMoney, nyRealPayDate, nyRealPayMoney, nyPayCancelReason;

    private TextView txtCommAccount;
    private TextView txtCommDeadline;
    private TextView txtCommBorrowState;
    private TextView txtChangeValue;


    @Override
    public View initLayout(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_common_borrow_detail, container, false);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.baseActivity = activity;
        try {
            mListener = (OnContactSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + "must implement OnContactSelectedListener");
        }
    }

    @Override
    public void initView() {

        txtChangeValue = findView(R.id.txt_change_value);
        txtCommAccount = findView(R.id.txt_comm_account);
        txtCommDeadline = findView(R.id.txt_comm_deadline);
        txtCommBorrowState = findView(R.id.txt_comm_borrow_state);


        lineLayer1 = findView(R.id.line_layer1);
        lineLayer2 = findView(R.id.line_layer2);
        lineLayer3 = findView(R.id.line_layer3);
        nyBorrowMoney = findView(R.id.ny_borrow_money);
        nyBorrowDeadline = findView(R.id.ny_borrow_deadline);
        nyBorrowDate = findView(R.id.ny_borrow_date);
        nyBorrowProtocol = findView(R.id.ny_borrow_protocol);
        nyPayWay = findView(R.id.ny_pay_way);
        nyNeedPayDate = findView(R.id.ny_need_pay_date);
        nyAlreadyMoney = findView(R.id.ny_already_money);
        nyWaitMoney = findView(R.id.ny_wait_money);
        nyRealPayDate = findView(R.id.ny_real_pay_date);
        nyRealPayMoney = findView(R.id.ny_real_pay_money);
        nyPayCancelReason = findView(R.id.ny_pay_cancel_reason);
    }

    /**
     * 更新ui
     *
     * @param loanDetailResponse
     */
    protected void updateUi(LoanDetailResponse loanDetailResponse) {
        refreshHeaderUi(loanDetailResponse);
    }

    public void refreshHeaderUi(LoanDetailResponse response) {
        String[] strings = getShowTextValue(response.loanApplyItem);
        txtCommAccount.setText(strings[1]);
        txtChangeValue.setText(strings[0]);

        txtCommDeadline.setText(response.loanApplyItem.repaymentTime);
        txtCommBorrowState.setText(response.loanApplyItem.sItemStatus);
        txtCommBorrowState.setTextColor(getStateColor(response.loanApplyItem.itemStatus));
    }


    protected String getFormatData(LoanApplyItemEntity loanApplyItemEntity) {
        int period = loanApplyItemEntity.period;
        int period_unit = loanApplyItemEntity.periodUnit;
        return FormatUtil.getProTimeStr(period, period_unit);
    }



    protected String getUnitString(String applyAmount) {
        return "¥" + applyAmount;
    }


    public static BaseBorrowFragment borrowFactory(int state) {
        BaseBorrowFragment baseBorrowFragment = null;
        switch (state) {
            case 1:
                baseBorrowFragment = subFragment1.getInstance();
                break;
            case 2:
                baseBorrowFragment = subFragment2.getInstance();
                break;
            case 3:
                baseBorrowFragment = subFragment3.getInstance();
                break;
            case 4:
                baseBorrowFragment = subFragment4.getInstance();
                break;
            case 5:
                baseBorrowFragment = subFragment5.getInstance();
                break;
            case 6:
                baseBorrowFragment = subFragment6.getInstance();
                break;
            case 7:
                baseBorrowFragment = subFragment7.getInstance();
                break;
        }
        return baseBorrowFragment;
    }


    private int getStateColor(int currentStateValue) {
        int color = 0;
        switch (currentStateValue) {
            case 1:
            case 2:
                color = getResources().getColor(R.color.color_00B0FF);
                break;
            case 3:
            case 4:
                color = getResources().getColor(R.color.color_ff6f00);
                break;
            case 5:
            case 6:
            case 7:
                color = getResources().getColor(R.color.txt_color_333333);
                break;

            default:
        }
        return color;

    }


}
作者:o279642707 发表于2017/8/18 17:05:11 原文链接
阅读:209 评论:0 查看评论

React Native入门(九)之导航组件React Navigation(1)StackNavigator

$
0
0

前言

本篇文章了解一下RN中导航组件的使用,且使用的是官方推荐的一个单独的导航库react-navigation,来分别了解一下这个库里边的StackNavigatorTabNavigatorDrawerNavigator这三个导航组件的基本使用!需要更加深入的了解的可以去官网查看其他相关的API:
React Navigation

使用

安装react-navigation库

首先呢,这个库是单独的,所以需要我们把这个库添加到我们的项目中去,在我们的项目目录执行:

yarn add react-navigation

这里写图片描述
然后等待下载安装成功即可!

StackNavigator

使用

这个导航组件主要用于不同页面之间的切换!,这个非常类似于Android中的Intent,来进行不同页面的挑战和数据的传值!
而且如果一个页面添加了StackNavigator,就在页面顶部多了一个导航栏,相当于Android中的Toolbar!
下面来看一下具体的使用:

//首先需要将StackNavigator引入
import {
  StackNavigator,
} from 'react-navigation';

class MainScreen extends Component{
  //设置navigationOptions
  static navigationOptions = {
    title: '主页面',//标题
  };
  render() {
    //navigation属性
    const { navigate } = this.props.navigation;
    return (
      <View>
        <Text style={{fontSize:20}}>我是MainScreen!</Text>
        <Button title="跳到SecondScreen"
                //点击跳转,参数为下边RouteConfigs中要跳转的routeName
                onPress={()=>navigate('Sencond')}/>
      </View>

    );
  }
}

class SecondScreen extends Component{
  static navigationOptions ={
    title: 次页面 
  };

  render() {
    return (
      <View >
        <Text style={{fontSize:20}}>我是SecondScreen!</Text>
      </View>

    );
  }
}

//设置StackNavigator(RouteConfigs, StackNavigatorConfig)
const App = StackNavigator({
  Main: {screen: MainScreen},
  Sencond: {screen: SecondScreen},
});

AppRegistry.registerComponent('AwesomeProject', () => App);

上边就是StackNavigator的基本用法,如果要在页面跳转的时候传递一些数据要这样写:

onPress={()=>navigate('Sencond',{ hello: '你好!' })}

navigate()方法中添加第二个参数,格式为{ key: value }的形式!然后在第二个页面中接收数据:

render() {
  const { params } = this.props.navigation.state;
  return (
    <View >
      <Text style={{fontSize:20}}>{params.hello}我是SecondScreen!</Text>
    </View>
  );
}

要先拿到传值页面中navigation属性的state:
const { params } = this.props.navigation.state;
然后取值params.key的形式拿到传递的值,例子中为params.hello

另外如果我们在第二个接收页面设置navigationOptions的时候需要传递过来的参数,该怎么做呢?有两种方式都可以拿到:
①在接收页面中:

static navigationOptions = ({ navigation }) => ({
    title: `次页面 ${navigation.state.params.hello}`,
});

可以将navigationOptions定义为这个页面属性的函数,比如这里在设置title属性的时候,使用${navigation.state.params.hello}这个变量表示传递的值,需要注意的是外边需要`` 包围,这个是键盘Esc下边的键!这点要注意!
②我们在设置StackNavigator的时候,可以配置RouteConfigs!

StackNavigator({
  Main: {screen: MainScreen},
  Sencond: {
    screen: SecondScreen,
    navigationOptions: ({navigation}) => ({
      title: `次页面 ${navigation.state.params.hello}`,
    }),
  },
});

直接在配置第二个页面的RouteConfigs时,去设置navigationOptions,相当于重写了我们在SecondScreen中设置的navigationOptions,那么当然在跳转到第二个页面时会显示这里配置的navigationOptions,而不是在SecondScreen中设置的!

可以试一下:

class SecondScreen extends Component{
  static navigationOptions = {
    title: '我就不信我显示不了!',
  };
  ...
}

运行一下:
这里写图片描述
是吧,你就是显示不了!哈哈哈…

配置

那么说到这里,我们在设置StackNavigator的时候都可以设置哪些东东?

StackNavigator(RouteConfigs, StackNavigatorConfig)

RouteConfigs:路由设置

这里参照官网:

StackNavigator({
  // For each screen that you can navigate to, create a new entry like this:
  //路由名字
  Profile: {

    // `ProfileScreen` is a React component that will be the main content of the screen.
    //具体的页面
    screen: ProfileScreen,
    // When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.

    // Optional: When deep linking or using react-navigation in a web app, this path is used:
    //页面的路径
    path: 'people/:name',
    // The action and route params are extracted from the path.

    // Optional: Override the `navigationOptions` for the screen
    //导航栏选项
    navigationOptions: ({navigation}) => ({
      title: `${navigation.state.params.name}'s Profile'`,
    }),
  },

  ...MyOtherRoutes,
});

StackNavigatorConfig

Options for the router:

  • initialRouteName - Sets the default screen of the stack. Must match one of the keys in route configs. 设置默认页面
  • initialRouteParams - The params for the initial route.设置默认页面的传值
  • navigationOptions - Default navigation options to use for screens. 设置默认的导航栏选项
  • paths - A mapping of overrides for the paths set in the route configs. 重新路由设置中的path

还有一些视觉效果上面的配置,比如设置页面切换模式mode ,设置过渡动画transition等!可以参考官方文档!

那么具体到一个页面中,navigationOptions都可以设置哪些参数?

  • title设置标题(默认,在没有设置header相关属性下显示)
  • header 设置导航栏头部,接收参数为React Element或者是一个返回React Element的给定HeaderProps参数的方法。如果设置为null,则整个标题栏就不显示了!
    这个属性的存在,我们就可以自定义我们的Header!
  • headerRight 显示在header右边的组件,比如Android中常见的菜单按钮等,都可以通过这一属性设置!

其他的属性参数的设置,就不再说了,可以参考官方文档:
StackNavigator

结语

本篇文章主要介绍了React Navigation导航库中StackNavigator的使用,剩余的两个导航组件,我们放在下篇博客中了解!
好了,就这样,我们下篇文章再见!

作者:aiynmimi 发表于2017/8/18 19:18:46 原文链接
阅读:215 评论:0 查看评论

基于GPU Skin的骨骼动画Instance的实现

$
0
0


1、GPU Skin与GPU Instance

骨骼动画是指通过定义骨架结构,然后在上面蒙皮,然后对骨架做动作驱动模型运行的动画,游戏中大部分的角色表现是通过骨骼动画进行的。骨骼动画本质上最终是通过Skin和Skeleton的Animation变换模型的顶点数据。骨骼动画既可以在CPU端实现,也可以在Gpu端实现。
在现有的很多游戏引擎中,骨骼动画的实现大多在CPU端进行实现,例如Unity,原因有很多:比如引擎希望在骨骼动画中加入一些复杂的如动作融合, 骨骼部位分离动画,IK,重定向,骨骼蒙版,跟骨骼动画等等复杂的动作机制,来表现更高质量的动作表现。

为什么需要GPU Skin?
但是我们知道Skin是一个对每个mesh上的点进行变换的并行问题,在CPU端实现会带来极大的性能限制,尤其在手游中,很多要求同屏人数多的游戏到了后期会受制于同屏的角色数。我在过往项目的统计在Unity中,一般中端机器可以支撑8-10W面左右的普通mesh,但是如果是Skinnedmesh,大约1.5W面就会遇到瓶颈。
总的来说CPU断的Skin可以做复杂的动画机制,但是效率很低。这时我们想到了GPU Skin。GPU Skin在GPU上并行的对mesh的点进行Skin变换,可以极大的提升效率,另外它节省了相对来说更宝贵的CPU资源。不过支持复杂的动画机制相对难度较大。
所以在一些游戏的实现中,可以思考对于怪物以及不重要的玩家对象采取GPU Skin,因为这些单元大多也不需要很复杂的融合,blend tree这种机制,但是却可以快速的渲染,而对重要的主角这种才保留GPU Skin的形式。

GPU Skin与GPU Instance
一旦当我们在GPU上实现了Skin,那么一个更诱人的特性GPU Instance就可以进一步引入进来,因为对于同一种类的怪物,不管他们做什么动作,他们在做动作的某一帧,他们输送给GPU的数据其实是几乎一样的,都是一份相同的处于T-POSE的顶点buffer,那么不同的只是每个角色当前处于的动作帧。这很容易结合GPU Instance,只提交一份T-POSE给GPU,然后整理每个角色各自不同的帧号作为per instance数据给显卡,那么就完成了支持骨骼动画的Instance,这样场景里所有一个种类的怪(可以分别在做不同的动作)可以通过一个Draw Call完成可以,这才是令人激动的。事实上笔者也正是为了想解决骨骼动画物体的GPU Instance的目的才来实现GPU Skin的。

2、基于GPU Skin的GPU Instance实现

Skin的原理
假设我们有一个骨架Skeleton,骨架是由一些有父子关系的节点(即骨头)组成,每个骨头代表一个坐标系。一段骨骼动画通常记录了每个骨头在其父空间相对于初始姿态的几何变换(根骨骼的父空间可以认为就是模型的局部空间)。
在初始的时候,Skeleton被摆到一个初始的姿态,即T-POSE,同时和这个骨骼对其建立角色的模型。
这样对于任意一帧动画,模型上任何一个顶点P最终在模型局部坐标系下的位置Pl就等于
Pl = Mroot-local ..*M2-3 * M1-2 * MBind1 * PLT + Mroot-local ..*M2-3 * M1-2 * MBind2 * PLT + ...
这里面Plt是TPOSE下这个顶点的坐标。
MBind1是影响这个顶点的第一根骨骼的从模型局部坐标系到这根骨骼的TPOSE下的坐标系的转换矩阵(也被简单称为这根骨骼的绑定矩阵,它意味着把顶点从局部坐标变换到这根骨骼坐标系下面的坐标)。
Mm-n表示在m的父骨骼n的坐标系下,m骨骼在此运动帧相对于TPOSE时的运动,因此M1-2 * MBind1 * PLT就相当于将局部坐标系TPOSE下的一个模型点变换到了它的父骨骼坐标系下此帧时的坐标。
这样级联的网上乘,最终得到了这个运动帧下这个顶点在局部坐标系下的位置。
我们可以把Mroot-local ..*M2-3 * M1-2 * MBind1 用一个矩阵Mf来表示,即Pl = Mf1*PLT + Mf2*PLT+... 。
这个Mf就是在第f帧,将模型的TPOSE的局部坐标转变为此时姿态的局部坐标的转换,更简洁的说,这个Mf就是第f帧的运动变换矩阵,知道了这个Mf,我们就可以方便的将任意顶点进行动画驱动。
由于通常一个顶点会被很多根骨骼影响,例如两个手指间的某个顶点会同时受到两根甚至三根骨骼影响,所以这里要后面再加几根骨骼的影响,不过通常对于效率考虑,引擎会限制收影响的骨骼根数,如1,2或4等。

CPU Skin与GPU Skin
1)纯CPU Skin:

通常CPU Skin的做法就是,在CPU内实时的计算上面的运动矩阵,然后循环的对每一个顶点进行矩阵相乘,然后在进行必要的融合等高级动作处理技术得到最终这个顶点的局部坐标,传给gpu。
显然从性能上,CPU要每帧去计算每根骨头的变换矩阵,优化后是O(N)次矩阵乘法(N是骨骼数量),然后再进行顶点蒙皮在进行O(v)次矩阵乘法(v是顶点数量),这里面最容易称为瓶颈的是后者,因为场景的角色顶点量经常是数以万计的。但是它可以方便的实现一些复杂的动作效果
2)Unity的内置GPU Skin:
Unity内置的GPU Skin做法为了不影响用户的vs的书写以及兼容复杂的animator机制,因此做了一套半GPU Skin的方法。它利用了现代硬件API所具有的一些transform feed back这种机制,骨骼变换矩阵这里还是每一帧在GPU上做,但是蒙皮那里放到GPU上,然后蒙皮之后利用transform feed back得到蒙皮后的顶点位置,再次传入用户的vs,或者在CPU内进行一些融合等操作。
这种做法还是需要每帧去计算骨骼变换矩阵(这个开销不大),节省了Skin,但是它要多走一次客户的vs处理,并且如果用到cpu内的复杂动作处理的话,还涉及到将vertex buffer从GPU到CPU之间的传递。
3)纯GPU Skin:
这也是本文的做法,我们将完全在GPU上做整个skin 流程,由于上文的Pl = Mf1*PLT,我们预先保存每个骨骼在每一帧的Mf1。我们将这些变换矩阵伪装成一个贴图传递给显卡,然后只要将TPOSE的VBO和这张贴图给显卡,上面的蒙皮过程就完全在显卡计算了,先根据当前的帧和骨骼sample贴图获取这个矩阵m,然后做矩阵变换进行蒙皮,然后走其他的vs处理。
这里的一个关键问题是如果把这些矩阵伪装成一张贴图,我们知道这个pose变换矩阵由4*4=16个float组成(其实去掉最后一列的其次化表示是3*4),而对于RGBfloat这样的贴图格式,一个像素可以存储3个float,这样我们就可以用4个像素去表示一个数组。我们可以按照我们自己定义的顺序,预先将一个角色的每一个动作的每一帧的每一个骨骼的转换矩阵用这4个像素排布在这张图上,就等于我们用一张图而不是一些animationclip来存储了这个角色的所有动作。(在我们的测试中,一个具有10几个动作的普通怪物所占用的贴图大约比一张256*256的图稍大一下,256*256的格式为RGBFLOAT的贴图大约占用0.75M内存)。
这种方式要比传统个骨骼动画存储方式要稍微浪费一些内存,但是完全省去了CPU上的处理时间,并且在GPU上并行蒙皮,骨骼矩阵的计算采用预先烘焙后采样的方式,效率是非常高的,更重要的是,这种方式的Skin可以支持Instance。
当然这种方式要求Vertex的textrue fetch,要求GPU支持float类型的贴图,一般需要gles3.0以上级别的机器(当然也已经算比较普遍)。

GPU Skin与GPU Instance
本文讨论在Unity下面实现基于GPU Skin的GPU Instance。为了Instance,我们需要提供一份顶点buffer给GPU,这个很好办,对于一个外形的角色,根据上面的原理,它的运动后的顶点位置
P = M1 * Ptpose,即多个同外形的角色,无论他们在做什么动作,他们所需的原始的Vertex buffer都是相同的一份TPOSE,而不同之处在于每个物体正在使用的动作转换矩阵M不同,而在我们gpu skin的实现中,一个角色的所有动作的矩阵都藏在了一张texture上,所以我们把这个texture也提供给shader 之后,每个物体不同之处就是texture上的offset了(或者说就是一个动作号和一个帧号)。而根据Instance的原理,我们把每个物体的动作号,帧号当成per instance data组成数组给GPU,那样GPU就可以对多个骨骼动画物体一次提交而在GPU上一次绘制了。
通过同时运用GPU Skin和 GPU Instance,我们对同一个角色的多个对象,一次提交给GPU,在GPU上绘制多个动作,同时进行蒙皮操作。如本文最开始的场景一样,我们用一个Draw Call来绘制大量相同角色的动画。

作者:leonwei 发表于2017/8/18 20:39:14 原文链接
阅读:224 评论:0 查看评论

教你轻松修改React Native端口(如何同时运行多个React Native、8081端口占用问题)

$
0
0

尊重版权,未经授权不得转载
本文出自:贾鹏辉的技术博客http://blog.csdn.net/fengyuzhengfan/article/details/77389953

当我们运行一个React Native项目的时候,React Native会启动一个默认端口号为8081的本地服务,该8081的服务就是React Native项目的一个本地服务器,用于提供JSBundle包和一些静态资源。这一切都是React Native帮开发者配置好的,一切都是那么简便,但如果出现如下几种情况,那么你需要认真阅读本文了:

  1. 8081端口被其他程序占用(比如杀毒软件),导致React Native无法正常启动8081的服务;
  2. 如果你想同时运行多个React Native项目;
  3. 如果你好奇React Native的默认8081是如何设置的,想修改它;

修改React Native监听端口

启动React Native服务默认会监听8081端口,那么如何修改这个默认的端口呢?接下来就跟着我一步一步的来修改React Native服务默认监听的端口吧!

第一步:修改Server端口

所谓Server端口,也就是我们通过npm start或者react-native run-xxx命令启动的React Native本地服务器的端口号,如图:
server_port

临时修改Server端口

查看最新的server.js的源码,不难发现一下一段代码:

...
command: '--port [number]',
default: 8081,
parse: (val: string) => Number(val),
...

从上述代码中可以看出,我们在启动react native服务的时候可以为它指定一个端口号:

react-native start --port=8082

提示:上述代码,是2017年8月1号提交的一个pull request,所以说在2017年8月1之前的React Native版本是没有这个功能的。

永久修改Server端口

要永久修改这个默认端口号,我们需要修改server.js文件,server.js所在位置,在:

你的项目名称/node_modules/react-native/local-cli/server/server.js

找到这个文件,打开它,然后将默认的8081端口修改为你想要的端口号即可:

server_port

修改好之后,需要验证一下有么有生效,怎么验证呢,方法很简单,在项目根目录下运行npm start即可:
server_port
从上图可以看出,这里我们已经将react-native的默认端口修改为了8082。

第二步:修改iOS项目的端口号

在修改了React Native 服务的端口号之后,我们要需改iOS项目的端口号让它从新端口获取jsbundle,否则的话会出现No bundle URL present错误。

提示:如果你的React Native项目没有iOS模块可以忽略此步骤;

  • 通过XCode打开React Native项目中的iOS项目;
  • 修改以下文件的端口号:
    • RCTWebSocketExecutor.m
    • RCTBridgeDelegate.h
    • RCTBundleURLProvider.m
    • RCTInspectorDevServerHelper.mm
    • RCTPackagerConnectionBridgeConfig.m

为了方便查找这些文件并定位到端口号,你可以借助XCode的Show the Find navigator功能来查找文本8081
Show the Find navigator

接下就可以通过react-native run-ios来运行iOS项目并从新的端口读取jsbundle了。

第三步:修改Android项目的端口号

在修改了React Native 服务的端口号之后,我们要需改Android项目的端口号让它从新端口获取jsbundle,否则的话会出现No bundle URL present错误。

提示:如果你的React Native项目没有Android模块可以忽略此步骤;

  • 打开调试工具(Dev Settings),可以通过(command+M) 完成;
  • 点击 Debug server host & port for device,添加localhost:xxx其中xxx为新的端口地址;
    localhost.png
  • 重新reload一下,就可以从新的端口加载jsbundle了。

另外,如果你的项目是源码级依赖React Native的话,也可以通过修改AndroidInfoHelpers.java文件来完成对Android项目的端口的修改。


public class AndroidInfoHelpers {
  public static final String EMULATOR_LOCALHOST = "10.0.2.2";
  public static final String GENYMOTION_LOCALHOST = "10.0.3.2";
  public static final String DEVICE_LOCALHOST = "localhost";

  private static final int DEBUG_SERVER_HOST_PORT = 8081;
  private static final int INSPECTOR_PROXY_PORT = 8081;
  ...

AndroidInfoHelpers.png

如何同时运行多个react native项目

因为端口绑定的缘故,默认情况下react native是不支持同时运行多个项目的。

如果我们要同时运行多个react native项目的话,需要为同时运行的多个项目分配不同的端口号。这样以来,我们就可以让react native支持同时运行多个项目了。关于如何为不同项目分配端口号,查看上文 [修改React Native监听端口](#修改React Native监听端口) 的教程即可。

如果大家对修改React Native端口还有不明白的地方,可以在文章下方给我留言,我看到了后会及时回复的哦。
另外也可以关注我的新浪微博@CrazyCodeBoy,或者关注我的Github来获取更多有关React Native开发的技术干货

告诉大家一个好消息,为大家精心准备的React Native视频教程发布了,大家现可以看视频学React Native了。

如果,大家在开发原生模块中遇到问题可以在本文的下方进行留言,我看到了后会及时回复的哦。
另外也可以关注我的新浪微博,或者关注我的Github来获取更多有关React Native开发的技术干货

推荐学习:视频教程《React Native开发跨平台GitHub App》

参考

作者:fengyuzhengfan 发表于2017/8/19 0:19:22 原文链接
阅读:124 评论:0 查看评论

Android开源框架greenDAO 3.X的使用

$
0
0

前言

 greenDAO是greenrobot Open Source Libraries的一个开源框架,同时greenrobot Open Source Libraries还有一个优秀的开源框架EventBus。greenDAO是用来替代Android原始的SQLite数据库操作以便节省开发者开发成本的一款优秀的ORM(object/relational mapping)框架,将SQLite数据库的操作封装成简单的面向对象API,避免了写sql语句,同时还进行了数据库操作性能的优化。

借用官网一张图:

这里写图片描述

greenDAO框架地址:

官网:http://greenrobot.org/greendao/

文档:http://greenrobot.org/greendao/documentation/

集成greenDAO

在root build.gradle(project的build.gradle)中如下配置:

buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

在app build.gradle中添加:compile 'org.greenrobot:greendao:3.2.2'

apply plugin: 'org.greenrobot.greendao' // apply plugin

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
    compile 'org.greenrobot:greendao:3.2.2' // add library
}

ok,重新同步一下项目即可。

初始化greenDAO

(1) 创建实体类,如创建一个Book.java

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Index;
import org.greenrobot.greendao.annotation.NotNull;

/**
 * Created by LT on 2017/8/19.
 */
@Entity(indexes = {
       @Index(value = "name,author DESC",unique = true)
})
public class Book {
    @Id
    private Long id;
    @NotNull
    private String name;
    @NotNull
    private String author;
}

greenDAO 3使用注解进行Entity的配置,接着我们需要在Android Studio中Make Project让框架的触发器生成相关类。

Make Project后Book.java将变成:

package com.lt.greendao.entity;

import android.renderscript.Sampler;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Index;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Generated;

/**
 * Created by LT on 2017/8/19.
 */
@Entity(indexes = {
       @Index(value = "name,author DESC",unique = true)
})
public class Book {
    @Id
    private Long id;
    @NotNull
    private String name;
    @NotNull
    private String author;

    @Generated(hash = 423030529)
    public Book(Long id, @NotNull String name, @NotNull String author) {
        this.id = id;
        this.name = name;
        this.author = author;
    }
    @Generated(hash = 1839243756)
    public Book() {
    }
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return this.author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
}

同时还会在build/generated/source/…下生成BookDao,DaoMaster,DaoSession这3个类,如图:

这里写图片描述

(2)初始化greenDAO

一般我们会在我们自己的Application中初始化greenDAO,如:

package com.lt.greendao;

import android.app.Application;

import com.lt.greendao.entity.DaoMaster;
import com.lt.greendao.entity.DaoSession;

import org.greenrobot.greendao.database.Database;

/**
 * Created by LT on 2017/8/19.
 */

public class App extends Application {

    public static final boolean ENCRYPTED = true; // 一个用于是否切换标准的数据库和加密后的数据库的标志
    private DaoSession mDaoSession;

    @Override
    public void onCreate() {
        super.onCreate();
        initGreenDAO();
    }

    public void initGreenDAO(){
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, ENCRYPTED ? "books-db-encrypted" : "books-db");
        Database db = ENCRYPTED ? helper.getEncryptedWritableDb("super-secret") : helper.getWritableDb();
        mDaoSession = new DaoMaster(db).newSession();
    }

    public DaoSession getDaoSession() {
        return mDaoSession;
    }
}

注意:

  1. 我们使用的是greenDAO加密的数据库,需要在App build.gradle中添加如下库,不然运行的时候会报java.lang.NoClassDefFoundError错误:

    // This is only needed if you want to use encrypted databases
    compile 'net.zetetic:android-database-sqlcipher:3.5.6'
  2. 别忘了在AndroidManifest.xml中修改Application的name属性。

(3)得到Entity对应的Dao对象,然后通过这个Dao(Data Access Object)对象操作数据库,如:

// 得到BookDao
DaoSession daoSession = ((App) getApplication()).getDaoSession();
mBookDao = daoSession.getBookDao();

使用greenDAO操作数据库

数据的基本操作增删改查(CRUD)

(1)添加

Book book = new Book();
book.setName(name);
book.setAuthor(author);
mBookDao.insert(book);
Log.i(TAG,"添加了一本书,id为:"+book.getId());

注意别自己设置Entity的id,greenDAO会自动生成id。

(2)删除(通过Entity的Dao对象执行deleteX()方法可以根据条件进行删除)

mBookDao.deleteAll(); // 删除所有       

(3)更新

book.setName("luotong");
mBookDao.update(book);

(4)查找(还有一些系查找方法,支持原始sql查询,loadAll()…)

Query<Book> build = mBookDao.queryBuilder().build();
List<Book> list = build.list();

遇到的问题

(1)问题描述

  • Error:Unable to find method ‘org.gradle.api.tasks.TaskInputs.file(Ljava/lang/Object;)Lorg/gradle/api/tasks/TaskInputFilePropertyBuilder;’.
    Possible causes for this unexpected error include:In the case of corrupt Gradle processes, you can also try closing the IDE and then killing all Java processes.

解决办法:

将project的build.gradle中的gradle的classpath替换成classpath 'com.android.tools.build:gradle:2.3.1

如:

buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        // classpath 'com.android.tools.build:gradle:2.0.0'
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin        
    }
}

(2)问题描述

  • A problem occurred evaluating project ‘:app’.
    java.lang.UnsupportedClassVersionError: com/android/build/gradle/AppPlugin : Unsupported major.minor version 52.0

解决办法:

Android Studio高版本需要JDK 1.8以上版本,将Android Studio的JDK版本改成1.8即可。

这里写图片描述

这里写图片描述

总结

 greenDAO号称是最快的Android ORM框架,具有许多的优点,高性能,小体积,简单易用…,可以完成大部分原生sql可以完成的操作,具体需求可以查看[官方文档](http://greenrobot.org/greendao/documentation/)及API,greenDAO还提供了RxJava的实现,同时相比2.0它使用更加简单,不再需要新建一个DaoGenerator的Java项目生成Dao,Master及Session类了 。

作者:ydxlt 发表于2017/8/19 5:40:08 原文链接
阅读:205 评论:0 查看评论

Android 7.0脱坑指南

$
0
0

这篇文章所写的前三个问题是自己现实遇到的,后三个是收集来的没有验证,作为记录以备不时之需。

1.安装时解析错误

我们的App通常会有检查更新的功能。用户在收到提示更新并且下载完后,会自动打开安装页面让用户来去安装。这时就会出现安装错误的问题,这类的问题的可能性比较多。比如较低版本的App想要覆盖已有的较高版本App会提示安装未完成,或是签名不一致导致的。不过7.0上常见的有以下两种情况。

1.应用间共享文件

targetSdkVersion大于等于的24的App中,但是我们没有去适配7.0。那么在调用安装页面,或修改用户头像操作时,就会失败。那么就需要你去适配7.0或是将targetSdkVersion改为24以下(不推荐)。适配的方法这里就不细讲,大家可以看鸿洋大神的 Android 7.0 行为变更 通过FileProvider在应用间共享文件吧 这篇文章。

2.APK signature scheme v2

Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。详细看安卓官方说明

简单地说就是任何方式的篡改APK 文件,在利用了V2签名的apk上会失效。

我所用的Android Studio目前是2.3.3 在 Gradle 2.2.3时。打包页面是这样

这里写图片描述

可以看到默认是V1 和V2选中的。

1)只勾选v1签名就是传统方案签署,但是在7.0上不会使用V2安全的验证方式。
2)只勾选V2签名7.0以下会显示未安装,7.0上则会使用了V2安全的验证方式。
3)同时勾选V1和V2则所有版本都没问题。

这里问题就来了,默认全部勾选,按道理所有版本是没有问题的。那么我们为什么还是安装错误?其实是因为我们项目采用了美团的快速生成渠道包方案。这种方案不适用于V2的签名方案。(因为实现思路就是给已有的apk文件中添加空的渠道文件)

解决办法:

1.如果你的渠道较少,可以用gradle方式的多渠道打包。渠道多的话就不适用了。

2.毕竟V2不是强制的,那么我们要用传统方案签署,可以打开模块级build.gradle 文件,然后将行v2SigningEnabled false 添加到您的版本签名配置中:

android {
    ...
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false  //<--这里
      }
    }
  }

或者将Gradle 升级为2.3以上。那么打包页面是这样

这里写图片描述

我们可以不勾选V2选项。

3.前两种方法是比较快速的可以解决问题,但是一旦这种安全措施被强制(毕竟我们可以感受到安卓在安全方面的努力,比如权限控制、应用间共享文件),我们怎么办。其实美团早早发现了这个问题,具体看这篇 新一代开源Android渠道包生成工具Walle。里面有深度的原理讲解,满满的干货。

2.PopupWindow位置不正确

7.0系统的手机上,PopupWindow弹出位置不正确。有两种可能:

1.我们使用了update方法,同时设置了GravityGravity.NO_GRAVITY没事)。因为在update方法中有调用computeGravity方法去获取Gravity。(7.0以下没有获取Gravity进行更新判断)

    public void update() {

        // 省略部分代码

        final int newGravity = computeGravity();
        if (newGravity != p.gravity) {
            p.gravity = newGravity;
            update = true;
        }

        if (update) {
            setLayoutDirectionFromAnchor();
            mWindowManager.updateViewLayout(mDecorView, p);
        }
    }

Android 7.0computeGravity方法源码

private int computeGravity() {
    int gravity = Gravity.START | Gravity.TOP;
    if (mClipToScreen || mClippingEnabled) {
        gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
    }
    return gravity;
}

Android 7.1computeGravity方法

private int computeGravity() {
    int gravity = mGravity == Gravity.NO_GRAVITY ?  Gravity.START | Gravity.TOP : mGravity;
    if (mIsDropdown && (mClipToScreen || mClippingEnabled)) {
        gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
    }
    return gravity;
}

很显然在7.0上我们设置的Gravity被覆盖了。解决就很简单了,不使用update方法。如果你真的要使用可以参考这篇文章的方法。

2.PopupWindow高度为MATCH_PARENT,在显示的时候调用showAsLocation方法时,PopupWindow并没有在指定控件的下方显示。如果使用showAsDropDown,会全屏显示。

解决方法:

1.最简单的解决方法就是指定 PopupWindow 的高度为 WRAP_CONTENT, 调用 showAsDropDown方法。

2.或者弹出时做一下判断处理(代码来自PopupWindowCompat

if (Build.VERSION.SDK_INT >= 24) { // Android 7.x中,PopupWindow高度为match_parent时,会出现兼容性问题,需要处理兼容性
    int[] location = new int[2]; // 记录anchor在屏幕中的位置
    anchor.getLocationOnScreen(location);
    int offsetY = location[1] + anchor.getHeight();
    if (Build.VERSION.SDK_INT == 25) { // Android 7.1中,PopupWindow高度为 match_parent 时,会占据整个屏幕
        // 故而需要在 Android 7.1上再做特殊处理
        int screenHeight = ScreenUtils.getScreenHeight(context); // 获取屏幕高度
        popupWindow.setHeight(screenHeight - offsetY); // 重新设置 PopupWindow 的高度
    }
    popupWindow.showAtLocation(anchor, Gravity.NO_GRAVITY, 0, offsetY);
} else {
    popupWindow.showAsDropDown(anchor);
}

3.后台优化

小伙伴们都知道在Android中有一些隐式广播,使用这些隐式广播可以做一些特定的功能,如,当手机网络变成WiFi时自动下载更新包等。 但,这些隐式广播会在后台频繁启动已注册侦听这些广播的应用,从而带来很大的电量消耗,为缓解这一问题来提升设备性能和用户体验,在Android 7.0中删除了三项隐式广播,以帮助优化内存使用和电量消耗。

Android 7.0 应用了以下优化措施:

  • 在 Android 7.0上 应用不会收到 CONNECTIVITY_ACTION 广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。 但,在前台运行的应用如果使用BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。
  • 在 Android 7.0上应用无法发送或接收 ACTION_NEW_PICTUREACTION_NEW_VIDEO 类型的广播。

应对策略:Android 框架提供多个解决方案来缓解对这些隐式广播的需求。 例如,JobScheduler API
提供了一个稳健可靠的机制来安排满足指定条件(例如连入无线流量网络)时所执行的网络操作。 您甚至可以使用 JobScheduler API 来适应内容提供程序变化。

另外,大家如果想了解更多关于后台的优化可查阅后台优化

移动设备会经历频繁的连接变更,例如在 Wi-Fi 和移动数据之间切换时。 目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播,
让应用能够监控这些变更。 由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。

以上内容来自这里

4.多语言特性

首先是官方的API指南:语言和语言区域

变化对比: Android 7.0多语言支持开发浅析

实现功能: Android 实现应用内置语言切换

5.通知栏适配

这里有一篇非常详细的通知栏介绍与适配,分享给大家:Android通知栏介绍与适配总结

6.WebView问题

最后觉得不错,点个赞吧!

作者:qq_17766199 发表于2017/8/19 10:26:08 原文链接
阅读:223 评论:0 查看评论

Unity 简单手机小游戏(源码)

$
0
0

游戏效果图



通过陀螺仪使得小球有运动的力

public class groy : MonoBehaviour {

        float x;
        float y;
        Gyroscope go;
        void Start()
        {
            go = Input.gyro;
            go.enabled = true;
        }
        void Update()
        {
            x = Input.acceleration.x;
            y = Input.acceleration.y;
            this.GetComponent<Rigidbody>().AddForce(new Vector3(-x, 0, -y) * 20);
        }

}

开始的UI场景,应用于按钮(开始,和游戏结束)

public class StartUi : MonoBehaviour {

    public void Reaply()
    {
        Time.timeScale = 1;
        Application.LoadLevel(1);
       
    }
    public void Quit()
    {
        Application.Quit();
    }
}

用触发器时刻判断,进洞重新开始,到终点弹出重玩,下一关按钮进行选择,用GUI制作了简易放回主菜单(回到场景0)按键,,

public class Mover : MonoBehaviour {

    public GameObject go;
  
    private void OnTriggerStay(Collider other)
    {
        if (other.name == "target")
        {
            go.gameObject.SetActive(true);
            Time.timeScale = 0;
        }
        else
        {
            Application.LoadLevel(1);
        }
    }
    private void OnTriggerEnter(Collider other)
    {
        if (other.name == "target")
        {
            go.gameObject.SetActive(true);
            Time.timeScale = 0;
        }
        else
        {
            Application.LoadLevel(1);
        }
    }

    private void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.fontSize = 36;
        if (GUI.Button(new Rect(50,30, 200,80 ), "返回菜单",style)){
            Application.LoadLevel(0);
        }    
      
    }
}




作者:Czhenya 发表于2017/8/19 11:49:20 原文链接
阅读:55 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>