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

timeline,一个简单精美的自定义时间轴

$
0
0

H-Express Library——–timeline

这是母项目 H-Express 中的那个自定义时间轴view,现抽取出来分享给大家,这个自定义view比较简单,作为学习自定义view入门的朋友可不能错过了。

一般可以应用在单号追踪、历史事件记录、任务完成度显示等方面,作为Recyclerview或者listview的item布局即可。


先上图:

这里写图片描述         这里写图片描述
左边为H-Express中的效果,右边为原始效果,横向和竖向。


import

    dependencies {
            compile 'com.qht1003077897.timeline:timelinelibrary:0.1'
    }

使用

    <com.bt.mylibrary.TimeLineMarkerView
        android:id="@+id/time1"
        android:layout_width="150dp"
        android:layout_height="100dp"
        android:paddingTop="20dp"
        app:beginLine="@color/blue_sky"
        app:endLine="@color/blue_sky"
        app:oritation="true"
        app:marker="@drawable/timeline_bg_blue"/>

看完了怎么用下来就该看看源码实现了

timeline的实现很简单,我这里以竖向为例,横向的类似。总结为2点:

  1. 首先以整个view的正中心然后靠左边界为基准绘制中间的圆,如图。为什么要靠左边呢,因为放在左边界再加上合适的paddingLeft就很容易能控制圆的位置了(同理,横向的靠上边,使用paddingTop控制圆的位置)。
  2. 然后以这个圆为基准,再绘制上面的线条和下面的线条。

    下面是6个属性,以供xml中进行设置。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TimeLineMarker">
        <!--圆点大小-->
        <attr name="markerSize" format="dimension" />
        <!--圆点颜色或者图片-->
        <attr name="marker" format="color|reference" />
        <!--上面/左边线段颜色或者图片-->
        <attr name="beginLine" format="color|reference" />
        <!--下面/右边线段颜色或者图片-->
        <attr name="endLine" format="color|reference" />
        <!--线段粗细-->
        <attr name="lineSize" format="dimension" />
        <!--横向还是竖向-->
        <attr name="oritation" format="boolean" />
    </declare-styleable>
</resources>

很简单的 onMeasure() 方法,如果你的自定义 view 没有什么特殊需求,直接把这个拿去用,通用版。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecSize= MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize= MeasureSpec.getSize(heightMeasureSpec);
        int widthSpecMode= MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode= MeasureSpec.getMode(heightMeasureSpec);
        //判断横向还是竖向,true为横向
        if(oritation){
            if(widthSpecMode== MeasureSpec.AT_MOST && heightSpecMode== MeasureSpec.AT_MOST){
                setMeasuredDimension(120,80);//针对wrap情况做处理
            }else if(widthSpecMode== MeasureSpec.AT_MOST ){
                setMeasuredDimension(120,heightSpecSize);
            }else if(heightSpecMode== MeasureSpec.AT_MOST ){
                setMeasuredDimension(widthSpecSize,80);
            }
        }else{
            if(widthSpecMode== MeasureSpec.AT_MOST && heightSpecMode== MeasureSpec.AT_MOST){
                setMeasuredDimension(80,120);//针对wrap情况做处理
            }else if(widthSpecMode== MeasureSpec.AT_MOST ){
                setMeasuredDimension(80,heightSpecSize);
            }else if(heightSpecMode== MeasureSpec.AT_MOST ){
                setMeasuredDimension(widthSpecSize,120);
            }
        }
    }

因为我们使用的元素为3个 drawable,是现成的 view,所以已经不需要在 ondraw()方法中进行绘制了,而更多的是调整每个 drawable 的位置,所以下面的方法写在 onSizeChanged() 中更合适。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        initDrawableSize();
        if (mBeginLine != null) {
            mBeginLine.draw(canvas);
        }
        if (mEndLine != null) {
            mEndLine.draw(canvas);
        }
        if (mMarkerDrawable != null) {
            mMarkerDrawable.draw(canvas);
        }
    }

主要看一下 initDrawableSize() 方法:

 private void initDrawableSize() {
        int pLeft=getPaddingLeft();
        int pRight=getPaddingRight();
        int pTop=getPaddingTop();
        int pBottom=getPaddingBottom();
        int width=getWidth();
        int height=getHeight();
        int cWidth=width-pLeft-pRight;
        int cHeight=height-pTop-pBottom;
        Rect bounds;
        int mMarkerSizepx=ResourceUtil.dip2px(getContext(),mMarkerSize);
        int mLineSizepx=ResourceUtil.dip2px(getContext(),mLineSize);
        //以竖向为例
if(mMarkerDrawable!=null){
                int marksize= Math.min(Math.min(cWidth,cHeight), mMarkerSizepx);
                mMarkerDrawable.setBounds(pLeft,pTop+height/2-marksize/2,pLeft+marksize,pTop+height/2-marksize/2+marksize);
                bounds=mMarkerDrawable.getBounds();
            }else{
                bounds=new Rect(pLeft+mLineSizepx/2,pTop+height/2,pLeft+mLineSizepx/2,pTop+height/2);
            }
            int halfLine=mLineSizepx >> 1;
            int lineLeft=bounds.centerX()-halfLine;
            if(mBeginLine!=null){       mBeginLine.setBounds(lineLeft,0,lineLeft+mLineSizepx,bounds.top);
            }
            if(mEndLine!=null){
mEndLine.setBounds(lineLeft,bounds.bottom,lineLeft+mLineSizepx,height);
  }
}

先了解一下 setBounds() 方法:

这里写图片描述

这句话的意思是:为Drawable 指定一个矩形区域,在onDraw() 方法被调用时使用。

这个矩形区域参数要求:矩形左边距view左边界的距离,矩形上边距view上边界的距离,矩形右边距view左边界的距离,矩形下边距view上边界的距离。

这里写图片描述

首先设置 mMarkerDrawable 的 bounds,左边的距离为pleft,上下为中心位置,确定了这个 bounds 为基准位置之后,再去绘制上下线条。

如果 mMarkerDrawable 为空,即在 xml 中不要这个圆,那么我们此处是将它看做一个点,一个左边的距离为pleft,上下为中心位置的点,作为我们的基准点,然后再去绘制上下线条,最终绘制出来的即为一条线段。

对于上下线的绘制很简单,这里以上线条为例,线条上端紧挨view的顶端,下端紧挨这个bounds的顶部,线条的长度为 bounds 的顶端距离上边界的距离。线条长度和 bounds 挂上钩之后,我们如果在 xml 中设置 paddingTop ,则 bounds.top 变大,线条长度变长,圆相应的往下移动。bounds.top= pTop +(height/2-mMarkerSize/2)。

 //下来提供几个方法。以供代码动态设置
    public void setMarkerSize(int markerSize) {
        if (this.mMarkerSize != markerSize) {
            mMarkerSize = markerSize;
            invalidate();
        }
    }

    public void setBeginLine(Drawable beginLine) {
        if (this.mBeginLine != beginLine) {
            this.mBeginLine = beginLine;
            if (mBeginLine != null) {
                mBeginLine.setCallback(this);
            }
            invalidate();
        }
    }
    ·············

源码github地址:

https://github.com/qht1003077897/timeline

我的QQ: 1003077897
我的csdn:http://blog.csdn.net/u012534831

欢迎交流。

作者:u012534831 发表于2017/8/1 11:21:12 原文链接
阅读:138 评论:0 查看评论

优雅设计封装基于Okhttp3的网络框架(五):多线程、单例模式优化 及 volatile、构建者模式使用解析

$
0
0

关于多线程下载功能,前四篇博文所讲解内容已经实现,接下来需要对代码进行优化。开发一个新功能并不复杂,难的是考虑到代码的扩展性和解耦性,后续需要进行的bug修复、完善功能等方面。此篇内容主要讲解代码优化,将从线程优化、单例优化、设计优化这三个方面进行讲解。

此篇内容将涉及到以下知识:

  • 线程优化及Linux系统中线程调度介绍
  • Android中常用的5种单例模式解析
  • volatile关键字底层原理及注意事项
  • 构建者模式介绍及使用

(建议阅读此篇文章之前,需理解前两篇文章的讲解,此系列文章是环环相扣,不可缺一,链接如下:)
优雅设计封装基于Okhttp3的网络框架(一):Http网络协议与Okhttp3解析
优雅设计封装基于Okhttp3的网络框架(二):多线程下载功能原理设计 及 简单实现
优雅设计封装基于Okhttp3的网络框架(三):多线程下载功能核心实现 及 线程池、队列机制解析
优雅设计封装基于Okhttp3的网络框架(四):多线程下载添加数据库支持(greenDao)及 进度更新


一. 线程优化

1. 根本问题

在Java语言中本身存在线程的优先级,是否直接操作设置这些线程的优先级就可以控制Android程序中的线程?

并非如此,实际上Java提供的一些线程优先级设置对于Android而言并非起太大作用,因为Android系统是基于Linux,而Linux系统对于线程调度管理有一套自己的法则。


2. Linux的线程调度法则

在Linux中使用nice value(以下成为nice值)来设定一个进程的优先级,系统任务调度器根据nice值合理安排调度。在Android系统中,也是采用此值来进行优化,特点如下:

  • nice的取值范围为-20到19。
  • 通常情况下,nice的默认值为0。
  • nice的值越大,进程的优先级就越低,获得CPU调用的机会越少,nice值越小,进程的优先级则越高,获得CPU调用的机会越多。
  • 一个nice值为-20的进程优先级最高,nice值为19的进程优先级最低。

以上便是 nice值的特点介绍,那么如何在代码中进行使用?首先来查看一个系统类AsyncTask,在它的内部实现中就使用到了相关代码:

这里写图片描述

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

此行代码作用相关于将该线程的优先级设置成THREAD_PRIORITY_BACKGROUND,继续查看其优先级别:

这里写图片描述

查看源码可知优先级别为10,将它设置为后台线程的好处是(查看注释):减少系统调度时间,UI线程会得到更多响应时间。


3. DownloadRunnable中的run方法优化

经过以上讲解后,将设置线程优先级至后台线程这行代码添加至run方法的第一行即可。(此行代码设置虽简单,但背后逻辑操作紧密联系Linux线程调度,有兴趣者后续可多了解)

    @Override
    public void run() {   

//设置线程优先级别为后台线程,为了 减少系统调度时间,使UI线程会得到更多响应时间 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        ......
        }



二. 单例模式优化

此点将对于Android中使用的单例模式进行优化,单例模式使用的场景往往是:程序中的某些对象的创建和消耗是比较耗费资源的,此时可以考虑将其对象设置为单例模式。

该模式算是设计模式中最常用、基本的一种,在目前已实现的网络框架编码中已多次使用。按照Java语言详细划分,单例模式可分为7种,但在Android中常用的只有以下5种。

1. 五大种类

(1)饿汉式

【饿汉式】

public class DownloadManager {
    private static DownloadManager sManager = new DownloadManager();

    private DownloadManager() {
    }

    public static DownloadManager getInstance() {
        return sManager;
    }

实现方式

  • 将该类的对象设置为静态成员变量;
  • 类的构造方法设置为私有,意味着外界无法创建该对象;
  • 直接创建对象赋值给静态成员变量。

创建时机

当此类加载进来的时候,该类的对象已经创建成功了。


(2)懒汉式

【懒汉式】

public class DownloadManager {
    private static DownloadManager sManager;

    private DownloadManager() {
    }

    public static DownloadManager getInstance() {
        if (sManager == null) {
               sManager = new DownloadManager();
            }
        }
        return sManager;
    }

实现方式

懒汉式的单例模式算是对饿汉式的优化:

  • 将该类的对象设置为静态成员变量;
  • 类的构造方法设置为私有,意味着外界无法创建该对象;
  • 在对外提供的public静态getInstance方法进行对象创建操作。

创建时机

当需要该类的对象时,调用此类暴露出来的getInstance方法,才会去创建对象。


(3)Double Check

问题解析

单例模式的每一种方式衍生可以看作是一次次的完善。在懒汉式中的getInstance方法创建类对象时,若遇到多线程的情况,便会出问题,即多个线程同时在操纵创建这行代码,这样对象并非是单例模式了。

改善代码

最简单的修改方法就是给getInstance方法加上synchronized 关键字,锁住此方法,但是锁住这整个方法消耗颇多,并非最佳,实际上只需锁住创建对象这行代码即可。

于是,有了如下Double Check方式:

【Double Check】

public class DownloadManager {
    private static DownloadManager sManager;

    private DownloadManager() {
    }

    public static DownloadManager getInstance() {
        if (sManager == null) {
            synchronized (DownloadManager.class) {
                if (sManager == null) {
                    sManager = new DownloadManager();
                }
            }
        }
        return sManager;
    }

实现方式

  • 将该类的对象设置为静态成员变量;
  • 类的构造方法设置为私有,意味着外界无法创建该对象
  • 在对外提供的public静态getInstance方法中判断,当对象为空时,使用synchronized 关键字,锁住DownloadManager.class,在其代码块中再加一个判断:若对象为空时创建对象。

创建时机

当需要该类的对象时,调用此类暴露出来的getInstance方法,才会去创建对象。

优缺点

优点:只会在第一次创建对象的时候去加锁,之后无需加锁直接返回已存在对象,节省了不必要的性能消耗。

缺点:其实这种方法也不能保证程序的完整性,它在某些虚拟机上运行会出现空指针问题,背后原因比较复杂,简单而言是因为创建对象这行代码并非原子性操作,虚拟机在编译字节码时将这行代码分为几步操作。

举个例子,当A线程访问对象为空,获取到锁,在其中进行对象创建过程中,线程B访问该方法,判断对象不为空,获取到此时返回的对象进行其它操作。注意此时线程B获取的对象仅是不为空,但它还未初始化完成,所以会出现空指针问题。原因就是字节码在执行时并非是原子性操作。(这里稍作了解即可,后续volatile介绍会继续讲解)

虽然空指针异常的发生几率不大,但毕竟是一个显示问题,存在太大隐患,一旦发生异常,进行排除问题都难以想到此层面。此种方式不太推荐。


(4)静态内部类

问题解析

面对最后 Double Check而言的问题,又有一种方式出现来解决此问题 —— 静态内部类它可以起到延迟加载的作用,并且能保证创建对象的完整性。

【静态内部类】

    public static class Holder {

        private static DownloadManager sManager = new DownloadManager();

        public static DownloadManager getInstance() {
            return sManager;
        }
    }
//外界访问

DownloadManager.Holder.getInstance();

实现方式

  • 编写访问权限为public 的静态内部类;
  • 在其内部类中将对象设置为私有的静态成员变量;
  • 在其内部类中对外提供 public 获取对象的静态方法getInstance返回对象。

创建时机

调用该静态内部类的getInstance时才会初始化对象。

原理分析

虽然它只是一个静态内部类,但虚拟机在编译class时会单独将它放到一个文件。看起来跟饿汉式似乎有点相像,但是当虚拟机加载此类时,并不会初始化该对象,只有调用该静态内部类的getInstance时才会初始化对象,起到了延迟加载作用,保证了创建对象的操作原子性。

此种方式较为推荐。


(5)枚举

其实枚举实现单例模式这种方式在Java语言中较为推崇,枚举在虚拟机层面已经保证了创建的唯一性,但是在Android系统中并不推荐,因为枚举会导致占用内存过多,可以尝试反编译枚举的Class,会发现随着枚举定义类型的增多,它所占用的内存是成倍增长,每一个枚举类型都生成相应的Class,它的每一个成员变量都是单独一份,所以此方式并不推荐!



2. volatile 关键字解析

在上一点中介绍 Double Check的单例模式使用中,会出现空指针问题,根本原因上述已简单讲解,此点将结合 volatile 关键字,从虚拟机底层原理详细探究。

博主声明: volatile 关键字向来是多线程并发中的重点,此点讲解涉及到大量虚拟机底层相关原理知识,若想真正了解透彻,仅以此点远远不够,推荐读者能够先查看以下文章链接,这是Java虚拟机中相关部分,学习之后再来理解Double Check单例模式中的空指针异常,会更加容易。

JVM高级特性与实践(十二):高效并发时的内外存交互、三大特征(原子、可见、有序性) 与 volatile型变量特殊规则

(1)异常定位—— 空指针异常

【Double Check】

public class DownloadManager {
    private static DownloadManager sManager;

    public static DownloadManager getInstance() {
        if (sManager == null) {
            synchronized (DownloadManager.class) {
                if (sManager == null) {
                    //出错处
                    sManager = new DownloadManager();
                }
            }
        }
        return sManager;
    }

代码异常定义

出现异常来源于创建对象那行代码,看似只是一个对象创建并赋值操作,但是当虚拟机编译成字节码文件,这行代码的对象创建操作不是一个原子性操作,会生成多条字节码指令。

例子讲解

再来回顾这个例子:线程A进入到getInstance() 方法时首先判断对象为空,拿到DownloadManager的锁,准备对象创建操作。此时线程B进入该方法,该对象已经不为空了(线程A已创建该对象实例),线程B理所当然获取到对象,但是此时对象并不完整,它只是不为空,初始化阶段可能尚未完成,所以当线程B使用该对象操作时必然会出现空指针异常。

对象创建代码 分解

那行代码的对象创建操作不是一个原子性操作,通过伪码的形式可分成以下几步:

  • 1)sManager 分配内存
  • 2) sManager 调用构造方法进行初始化操作
  • 3)sManager 对象进行赋值操作,使它指向在第一步分配的内存区域

(2)根本原因——JVM中的字节码指令集的重排序

所以说一个简单的对象创建在Java虚拟机中会被划分为这3个步骤,其实这些步骤也很正常,需要注意的是Java虚拟机会对代码步骤进行重排序,即字节码指令集的重排序

例如以上步骤二可能被虚拟机重排序到最后,这样意味着步骤一结束后,对象确实不为空了,但是在步骤三初始化之前被线程B获取到对象实例,而导致空指针异常!

代码执行顺序

虚拟机执行代码的顺序并非是按照我们所写的,而是以字节码文件为准。而JVM会自动优化代码,打乱字节码执行顺序!注意:这里的顺序打乱它不会故意破坏而导致异常产生,例如代码上下之间有依赖关系,JVM不会进行重排序。


(3)问题解决 —— volatile关键字

其实以上问题在单线程中并不会出现,只会在多线程中出现,为了避免JVM中的字节码指令集重排序问题,JDK 1.5中引入了一个关键字 —— volatile,它有两个重要作用:

  • 禁止JVM进行重排序
  • 保证变量的可见性(可见性:指当一个线程修改了共享变量的值,其他能够立即得知这个修改

Java开发者应当知道当一个变量被volatile关键字修饰时,它拥有可见性,但是另外一个作用 —— 禁止JVM进行重排序,却鲜为人知。当次变量被修饰后,JVM不会打乱字节码执行顺序,而出现步骤二在最后执行的情况。

指令重排序例子再论

为了更好的理解指令重排序,再举个例子来了解,例如在以下代码定义了这三个变量:

int a = 12;
boolean flag = false;
long c = 23;

JVM在执行以上代码时会以字节码文件为准,即打乱顺序,可能先操作boolean变量赋值,然后再是int,最后long。代码原本顺序的确被打乱了,但是JVM并不会无故打乱而导致异常产生,例如以下示例:

int a = 12;
int b = 10;
int c = a+23;

JVM在进行重排序时绝对不会将int c = a+23;操作放到int a = 12;之前,在程序编译时JVM已经考虑到了这些变量之间的依赖(还有其它考虑原则),所以变量a的赋值一定在变量c之前完成,不过变量a、b的初始化顺序无法保证。


(4)先行发生原则(happens-before)

JVM在处理相关的字节码文件时,所考虑到的原则是规定好的,例如上述中变量之间的依赖,这些判断的依据就是先行发生原则,由以下几个规则组成:

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作,准确地说,应该是控制流顺序。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作;这里必须强调的是同一个锁,而后面是指时间上的先后顺序。
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面是指时间上的先后顺序。
  • 线程启动规则(Thread Start Rule): Thread对象的start() 方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Temination Rule):线程中的所有操作都先行发生于对此线程的终止检测,可以通过Thread.join() 方法结束,Thread.isAlive() 的返回值等手段检测到线程已经终止运行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 Thread.interrrupted() 方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成先行发生于它的finalize() 方法的开始。
  • 传递性(Transitivity):如果操作A 先行发生于操作B, 操作B 先行发生于操作C,那就可以得出操作A 先行发生于 操作C的结论。

如果编写的代码中满足以上规则,JVM不会对字节码指令集进行优化,即重排序。

最后,在《深入Java虚拟机》中从底层原理部分详细解析了volatile关键字,若要透彻了解,可查看此书(12章的3.3节)或博主写的记录博客。




三.设计优化——构建者(Build)模式

若读者对构建者模式的组成及使用不熟悉,建议先看以下博文,以下博文详细讲解了构建者模式的构造及使用,举例说明学习。

Android : Builder模式 详解及学习使用

目前有个需求,在DownloadManager管理类中,想要提供一些灵活的参数来控制此类中的线程池、执行服务对象创建,例如核心、最大线程数这些参数等。如此而言就需要在此类中设计多个set方法,而不同参数的组合可能导致set方法需求量的增加,代码冗杂,所以采用构建者模式来解决这种需求带来的问题。

1. 构建者(Build)模式

(1)定义及作用

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
作用:减少对象创建过程中引入的多个重载构造函数、可选参数以及setter过度使用导致不必要的复杂性。

(2)组成

这里写图片描述

一般而言,Builder模式主要由四个部分组成:

  • Product :被构造的复杂对象,ConcreteBuilder 用来创建该对象的内部表示,并定义它的装配过程。
  • Builder :抽象接口,用来定义创建 Product 对象的各个组成部分的组件。
  • ConcreteBuilder : Builder接口的具体实现,可以定义多个,是实际构建Product 对象的地方,同时会提供一个返回 Product 的接口。
  • Director : Builder接口的构造者和使用者。

(3)实例讲解

这里举的例子应该算是构建者模式的进化版,精简了一些不必要的接口,若想要了解标准的模式编码,可以看第三点开头给出的链接,在此不多言。

public class User {
    private final String mName;     //必选
    private final String mGender;   //可选
    private final int mAge;         //可选
    private final String mPhone;    //可选

    public User(UserBuilder userBuilder) {
        this.mName = userBuilder.name;
        this.mGender = userBuilder.gender;
        this.mAge = userBuilder.age;
        this.mPhone = userBuilder.phone;
    }

    public String getName() {
        return mName;
    }

    public String getGender() {
        return mGender;
    }

    public int getAge() {
        return mAge;
    }

    public String getPhone() {
        return mPhone;
    }


    public static class UserBuilder{
        private final String name;     
        private String gender;   
        private int age;         
        private String phone;

        public UserBuilder(String name) {
            this.name = name;
        }

        public UserBuilder gender(String gender){
            this.gender = gender;
            return this;
        }

        public UserBuilder age(int age){
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone){
            this.phone = phone;
            return this;
        }

        public User build(){
            return new User(this);
        }   
    }


}

从以上代码可以看出这几点:

  • User类的构造函数是私有的,这意味着调用者不可直接实例化这个类。
  • User类是不可变的,其中必选的属性值都是 final 的并且在构造函数中设置;同时对所有的属性取消 setters函数,只保留 getter函数。
  • UserBuilder 的构造函数只接收必选的属性值作为参数,并且只是将必选的属性设置为 fianl来保证它们在构造函数中设置。

接下来,User类的使用方法如下:

    public User getUser(){
        return new
                User.UserBuilder("gym")
                .gender("female")
                .age(20)
                .phone("12345678900")
                .build();
    }

以上通过 进化的Builder模式形象的体现在User的实例化。



2. DownloadConfig(采用构建者模式)

创建一个配置类采用构建者模式对外提供参数配置:

public class DownloadConfig {
    private int coreThreadSize;
    private int maxThreadSize;
    private int localProgressThreadSize;

    private DownloadConfig(Builder builder) {
        coreThreadSize = builder.coreThreadSize == 0 ? DownloadManager.MAX_THREAD : builder.coreThreadSize;
        maxThreadSize = builder.maxThreadSize == 0 ? DownloadManager.MAX_THREAD : builder.coreThreadSize;
        localProgressThreadSize = builder.localProgressThreadSize == 0 ? DownloadManager.LOCAL_PROGRESS_SIZE : builder.localProgressThreadSize;
    }

    public int getCoreThreadSize() {
        return coreThreadSize;
    }

    public int getMaxThreadSize() {
        return maxThreadSize;
    }

    public int getLocalProgressThreadSize() {
        return localProgressThreadSize;
    }


    public static class Builder {
        private int coreThreadSize;
        private int maxThreadSize;
        private int localProgressThreadSize;

        public Builder setCoreThreadSize(int coreThreadSize) {
            this.coreThreadSize = coreThreadSize;
            return this;
        }

        public Builder setMaxThreadSize(int maxThreadSize) {
            this.maxThreadSize = maxThreadSize;
            return this;
        }

        public Builder setLocalProgressThreadSize(int localProgressThreadSize) {
            this.localProgressThreadSize = localProgressThreadSize;
            return this;
        }


        public DownloadConfig builder() {
            return new DownloadConfig(this);
        }
    }
}


3. 代码整合

(1)DownloadManager

在完成采用构建者模式的配置类,那么DownloadManager类中线程池的创建可直接调用配置类,需要在DownloadManager类中稍作修改。

       private static ExecutorService sLocalProgressPool;

    private static ThreadPoolExecutor sThreadPool;

    public void init(DownloadConfig config) {
        sThreadPool = new ThreadPoolExecutor(config.getCoreThreadSize(), config.getMaxThreadSize(), 60, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(), new ThreadFactory() {
            private AtomicInteger mInteger = new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "download thread #" + mInteger.getAndIncrement());
                return thread;
            }
        });

        sLocalProgressPool = Executors.newFixedThreadPool(config.getLocalProgressThreadSize());

    }

(2)Application初始化

        DownloadConfig config = new DownloadConfig.Builder()
                .setCoreThreadSize(2)
                .setMaxThreadSize(4)
                .setLocalProgressThreadSize(1)
                .builder();
        DownloadManager.getInstance().init(config);

以上,这种动态的设置配置参数处理方式相较于构造方法,灵活得多,减少了大量不必要的冗杂代码,扩展性较强,可链式增添参数配置。





四. 总结

1. 本篇总结

本篇内容是对前四篇博文完成的编码工作进行的优化,需要修改的地方并不多,但是为了程序的扩展性、解耦性、线程安全性考虑,分别从线程优化、单例优化、设计优化这三个层面对代码进行优化。其实完成一个功能并不难,重要的是前期设计部分一定要思考清楚功能可行性、程序扩展性等问题,而优化工作更是必不可少,切勿全部编程完再来一次“重构”,这样开发周期会被无限拖长,代码质量并不会因为所谓的“重构”而提高,适时的时候停下来对已完成的功能进行优化。

EasyOkhttp网络框架封装源码(对应第五篇博文优化后地代码)


2. 下篇预告

到目前为止,多线程下载功能设计、编写、优化工作已经完成,但是网络框架功能并没有完成,下篇将编写的新功能还是围绕在http请求上:

  • httpHeader的接口定义和实现
  • http请求头和响应头访问编写
  • http状态码定义
  • http中的 response封装、request接口封装和实现


若有错误,虚心指教~

作者:ITermeng 发表于2017/8/1 11:45:21 原文链接
阅读:54 评论:0 查看评论

React Native 中的JSX学习

$
0
0

    JSX是什么

字面上来看JSX即 JavaScript XML取首字母结合,所以JSX并不是一门新语言,仅仅是个语法糖。

React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。

我们在浏览器运行的时候,JSX语法会通过Babel转换成浏览器认识的JS。

Babel:我们装RN的时候你会再目录看到很多Babel文件夹,JSX语法依赖于Babel进行解析翻译。


那么问题来了,这种虚拟DOM的方式会不会影响性能呢。

答案肯定是不会的,反而会比我们操作DOM性能更好,其实我们也可以自己用JS写一个虚拟DOM,一般水平有限的写还是有困难的,就算写出来,也不一定有别人的性能强,React 虚拟DOM用了Diff算法,降低了频发而发展的操作DOM,所以性能是增强的。


JSX的优点:

1☞类XML语法,容易扩展。
2☞增强JS语义
3☞结构清晰
4☞抽象程度高
5☞代码模块化

    JSX在React Native

 该文章主要介绍JSX在React Native 中的实际使用,没有详细介绍JSX语法。

注意:demo中所有函数应写在class内。

1.JSX 中嵌入变量,表达式,样式

   这段Demo在标签里嵌入了变量,表达式和样式两种写法
var testName  =  'zzy';

render() {

    return(
        return(
        <View style={styles.container}>
         <Text>    
          {testName}
         </Text>
          <Text style={{marginTop:20}}>    
          {'这是一个表达式:'+ (5+3)}
         </Text>
        </View>
      );
      );
    }

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});




2.函数调用

函数和JS函数写法是一样的格式如下:
 
funcName (参数){实现内容}

需要注意RN 标签中的函数调用,简要的说下载render()函数中的调用规则。

①.如果你需要在render中直接调用的话和普通js调用规则相同。如下面的例子:

render() {
    this.testFunc()
}

testFunc(){
      alert('我不需要框框');
}


②.如果您在render中直接调用函数得到标签去搭建界面的话。如下面的例子:
render() {
      return this.renderLoadingView();
}

 renderLoadingView() {
    return (
      <View style={styles.container}>
        <Text>
          Loading movies...
        </Text>
      </View>
    );
  }

③.如果需要循环创建页面,render中的标签下面调用的话你需要在{}中调用函数,如下面的例子:

var heros =  ['yasuo','ruiwen','liqing'];

render() {
      return(
        <View style={styles.container}>
            {this.renderLoadForView()}
        </View>
      );
}

 renderLoadForView(){
    var forView = [];
    for (var i = 0; i < heros.length; i++) {
      forView.push(
        <Text key={i}>
           {heros[i]}
        </Text>
      );
    }
    return forView;
  }


3. render中渲染固定标签数组,如下面的例子:

render() {
      return(
        <View style={styles.container}>
            {this.loadArrayView()}
        </View>
      );
}

loadArrayView(){
  return(
     [
       <Text key={1}>
           数组1
        </Text>,
         <Text key={2}>
          数组2
        </Text>
     ]
  );
}




    总结

 JSX语法可以当做加强版的JS,在React中使用,依赖Babel编译。
 JSX最明显的特点就是可以在JS中写标签,并不用加引号,在标签里使用JS变量也十分方便 ,在标签里当遇到{}当做JS解析。
 JSX在ReactNative还有很多特点,今后慢慢的探究,学习消化。
作者:ZY_FlyWay 发表于2017/8/1 14:23:54 原文链接
阅读:8 评论:0 查看评论

Flutter进阶—自定义主题风格

$
0
0

质感设计的Theme类将主题应用于后代控件,主题描述了应用程序的颜色和排版选择。后代控件使用Theme.of获取当前主题的ThemeData对象,当控件使用Theme.of时,如果主题稍后更改,则会自动重建,以便可以应用更改。我们可以通过Theme.of查看当前应用程序的配色方案。

class _MyHomePageState extends State<MyHomePage> {
  Widget _colorDisplayBox(String explanation, String name, Color color) {
    return new Column(
      children: <Widget>[
        new Row(
          children: <Widget>[
            new Text("$explanation\n$name\t\t"),
            new Flexible(
                child: new Container(
                    height: 30.0, decoration: new BoxDecoration(color: color)))
        ],),
        new Divider()
  ],);}

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      //...
      body: new Center(
        child: new ListView(
          padding: new EdgeInsets.all(8.0),
          children: <Widget>[
            _colorDisplayBox("突出颜色", "highlightColor", Theme.of(context).highlightColor),
            _colorDisplayBox("提示颜色", "hintColor", Theme.of(context).hintColor),
            _colorDisplayBox("文本选择手柄颜色", "textSelectionHandleColor", Theme.of(context).textSelectionHandleColor),
            _colorDisplayBox("文字选择颜色", "textSelectionColor", Theme.of(context).textSelectionColor),
            _colorDisplayBox("背景颜色", "backgroundColor", Theme.of(context).backgroundColor),
            _colorDisplayBox("强调颜色", "accentColor", Theme.of(context).accentColor),
            _colorDisplayBox("画布颜色", "canvasColor", Theme.of(context).canvasColor),
            _colorDisplayBox("卡片颜色", "cardColor", Theme.of(context).cardColor),
            _colorDisplayBox("按钮颜色", "buttonColor", Theme.of(context).buttonColor),
            _colorDisplayBox("对话框背景颜色", "dialogBackgroundColor", Theme.of(context).dialogBackgroundColor),
            _colorDisplayBox("禁用颜色", "disabledColor", Theme.of(context).disabledColor),
            _colorDisplayBox("分频器颜色", "dividerColor", Theme.of(context).dividerColor),
            _colorDisplayBox("错误颜色", "errorColor", Theme.of(context).errorColor),
            _colorDisplayBox("指示灯颜色", "indicatorColor", Theme.of(context).indicatorColor),
            _colorDisplayBox("原色", "primaryColor", Theme.of(context).primaryColor),
            _colorDisplayBox("脚手架背景颜色", "scaffoldBackgroundColor", Theme.of(context).scaffoldBackgroundColor),
            _colorDisplayBox("次标头颜色", "secondaryHeaderColor", Theme.of(context).secondaryHeaderColor),
            _colorDisplayBox("选择行颜色", "selectedRowColor", Theme.of(context).selectedRowColor),
            _colorDisplayBox("飞溅颜色", "splashColor", Theme.of(context).splashColor),
            _colorDisplayBox("未选择的控件颜色", "unselectedWidgetColor", Theme.of(context).unselectedWidgetColor),
],),),);}}

这里写图片描述

我们可以自定义配色,比如设置primaryColor为红色。还可以使用primaryColorBrightness设置primaryColor的亮度,用于设置放置在原色顶部的文本和图标的颜色。

需要注意这里的亮度是描述颜色的对比度需求,有两个常量。Brightness.dark表示颜色很暗,需要浅色文字颜色才能实现可读性,例如颜色可能是深灰色,需要白色文字。Brightness.light表示颜色很浅,需要深色文字颜色来实现可读性,例如颜色可能是明亮的白色,需要黑色文字。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primaryColor: Colors.red,
        primaryColorBrightness: Brightness.dark,
      ),
      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  }
}

这里写图片描述

我们还可以根据不同平台设置主题,比如我们在iOS上设置白色和灰色主题,在Android上设置紫色和橙色主题。判断平台类型需要使用defaultTargetPlatform来识别当前系统平台,然后根据平台类型设置主题。

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

void main() {
  runApp(new MyApp());
}

final ThemeData kIOSTheme = new ThemeData(
  primarySwatch: Colors.orange,
  primaryColor: Colors.grey[100],
  primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = new ThemeData(
  primarySwatch: Colors.purple,
  accentColor: Colors.orangeAccent[400],
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: defaultTargetPlatform == TargetPlatform.iOS
          ? kIOSTheme
          : kDefaultTheme,

      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  }
}

这里写图片描述

在上面的代码中,有一段大家可能有疑问:primarySwatch: Colors.orange。颜色(Color)是ARGB格式的不可变的32位颜色值,具有相关颜色的小表的颜色称为样本(Swatch),颜色和样本的常量,表示质感设计的调色板。

这里写图片描述

大多数色板的颜色从100到900,增量为100,加上颜色50。数字越小,颜色越浅,数字越大,颜色越暗。强调色调(例如redAccent)只有数值100、200、400和700。

这里写图片描述

此外,还有一系列具有普遍不透明度的黑色和白色。例如,black54是具有54%不透明度的纯黑色。

这里写图片描述

要从其中一个色板中选择特定颜色,需要使用所需特定颜色的整数索引到样本。每个颜色样本常量是一种颜色,可以直接使用。

Color selection = Colors.green[400];

new Container(
  color: Colors.blue,
)

关于更多调色板,可以访问Flutter API的相关文档进一步了解。

作者:hekaiyou 发表于2017/8/1 15:35:03 原文链接
阅读:0 评论:0 查看评论

Android 开源之StickyHeaderListView 标题渐变、吸附悬停、筛选分类、动态头部

$
0
0

StickyHeaderListView 是基于实际需求做出的灵活可定制的UI功能,具体实现功能如下:
一、支持无限循环的广告位。
二、高度可动态配置的Header2和Header3(使用GridView实现)。
三、主要功能:分类、排序和筛选布局滑动到顶部后吸附、悬停。
四、自定义FilterView筛选控件,支持动画显示与动画隐藏。
五、支持标题栏背景颜色渐变、字体颜色渐变。
六、数据不足一屏动态添加空数据占位。
七、数据为空时,ListView加载暂无数据视图。
八、思路清晰、界面优美,添加ripple点击效果。
九、支持下拉刷新和上拉加载更多功能。

动态效果图:


stickyheader.gif

stickyheader2.gif

GitHub开源地址

APK下载地址

实现思路

StickyHeaderListView 主要是通过 ListView 添加头部实现,将复杂的头部分解为若干部分,如下图:Header 1(广告位)、Header 2(频道位)、Header 3(运营位)、Header 4(分割线) 和 Header 5(筛选头部),这样各个Header部分的UI和逻辑可以单独拿出去处理,具体可以参考我的 开源代码


StickyHeaderListView_sumary.png

Header 1: 它的高度影响标题栏的颜色渐变。

Header 2: 使用GridView实现,自定义FixedGridView,高度不受ListView的影响,一行显示几个自己可以根据需求设置。

Header 3: 和Header 2一样的实现方式,要注意的地方就是分割线的设置,我实现的思路是设置GridView的背景颜色的分割线的颜色,再设置如下的四个属性:paddingTop、paddingBottom、horizontalSpacing、verticalSpacing为1px,这样分割线就均等了。

android:background="@color/font_black_5"
android:paddingTop="1px"
android:paddingBottom="1px"
android:horizontalSpacing="1px"
android:verticalSpacing="1px"

Header 4: 这个头部布局是需求上的,UI加上整体更加好看,为什么我要单独拿出来,主要考虑到以下的原因:如果让Header 5达到吸附悬停的效果,需要知道Header 5到顶部的距离,如果把分割线加到Header 5上,那在移动的时候还需要减去这个高度;而如果加到Header 3上,Header 3是服务器动态配置的,如果没有Header 3的头部怎么办,那就加到Header 2上等,这样逻辑就比较麻烦,干脆我直接单独拿出来,作为一个头部布局动态添加。

Header 5: 这个筛选头部是个假的布局,主要处理未吸附悬停时的点击事件,点击之后滑动到顶部这时顶部的隐藏的筛选布局显示出来达到吸附悬停的效果。同时我将这个筛选布局定义一个 FilterView,将分类、排序和筛选的UI处理和逻辑封装起来,方便其它页面的二次使用。

还有两点需要特别注意:

一、如果数据不满一屏,比如就一条数据,那点击筛选它是没办法滑动到顶部的,因为她的高度不够,我的解决方法是添加若干个空数据,空数据的size是根据实际一屏要显示的个数减去现在的个数,这样可以达到整体可以滑动的高度,参考 TravelingAdapter 文件。

二、如果数据为空时并且我还需要无数据的占位图,如果在 ListView 底部加上无数据的布局这样的效果是不好的,所以我还在这个Adapter上做文章,让它加载一个无数据的视图布局,同样参考 TravelingAdapter 文件,每一个Item的高度: height = 屏幕的高度 - 标题栏高度 - 筛选View高度,这样设置一个这样的高度的Adapter,再 notifyDataSetChanged() 一下,整体的视图不会变化,无数据的占位图也自然而然的显示了。

最后

具体实现代码移步 GitHub,下载 APK 体验,感谢你的关注,欢迎star,希望对你有帮助,如遇到问题请联系我,最后再贴几张截图方便你查看。

滑动到一半时标题栏渐变


StickyHeaderListView2.png

滑动到顶部,FilterView 吸附悬停


StickyHeaderListView3.png

FilterView 动画显示与隐藏


StickyHeaderListView4.png

数据为空时的占位图


StickyHeaderListView5.png
Github地址:
https://github.com/sfsheng0322/StickyHeaderListView
作者:system_err 发表于2017/8/1 13:59:24 原文链接
阅读:13 评论:0 查看评论

Android性能优化总结

$
0
0

转载注明出处:http://blog.csdn.net/xiaohanluo/article/details/76546209

1. 缘由

Android系统每隔16ms发出VSYNC信号,对UI进行渲染,如果每次渲染都成功,就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成,时间超出16ms越多,丢的帧就越多。

假设我们更新屏幕的背景图片,需要24ms来做这次运算。当系统在第一个16ms时刷新界面,然而我们的运算还没有结束,无法绘出图片。当系统隔16ms再发一次VSYNC信息重绘界面时,用户才会看到更新后的图片。也就是说用户是32ms后看到了这次刷新(注意,并不是24ms),这就是丢帧。

大多数多用感知到卡顿等问题最主要的根源是渲染问题,而导致渲染问题的原因是性能问题,为了保证程序正常的使用,性能方面需要着重注意,本篇针对的性能优化是从一些平时常见的细节入手,

2. 性能优化

丢帧只是用户能感知到的表面现象,严重的会引起程序卡顿甚至ANR,深层次的原因是代码中有比较耗时的操作阻塞到了主线程,也就是性能问题。

2.1 过度绘制

过度绘制(Overdraw)是指屏幕上同一个像素点在同一帧时间内被绘制多次。在层级复杂的UI结构中,如果不可见的UI也被绘制,会导致某些像素区被绘制多次,浪费大量的CPU以及GPU资源。

在手机的开发者选项中,打开显示布局边界即可查看页面的绘制信息。


图 1 - 布局渲染视图

有四中颜色,蓝色,淡绿,淡红,深红分别代表着不同的绘制信息。蓝色代表一次绘制,淡绿代表两次绘制,淡红代表三次绘制,深红代表四及以上次绘制。

我们的目的是尽量减少红色的绘制信息,方法有两种。

  • 简化页面UI结构,复杂的UI布局会导致大量View重叠,出现过度绘制的可能性比较大,要避免布局嵌套过多,例如一般情况下,优先使用LinearLayout布局。
  • 复用背景色,例如如果父布局和子View背景色是相同的,只需要父布局设置背景色即可,子View不用设置。

2.2 布局优化

上面提到简化布局可以减少过度绘制的问题,布局优化可以从下面几方面入手。

  • 布局的选择,能满足需求的情况下优先选择LinearLayout,因为RelativeLayoutmeasure会比LinearLayout多出一次,
    即使LinearLayout使用了weight后,性能依然会比RelativeLayout
  • 边距的设置,RelativeLayoutmeasure过程中,如果出现子View和布局本身高度不同时候,还会触发measure过程,解决方法很简单,使用padding代替marigin
  • 复用布局,include
  • 延迟加载,ViewStub
  • 合并布局层级,merge

关于这块的具体详情,参考下面链接:

带你从源代码详细分析View的绘制过程

Android布局优化之ViewStub、include、merge使用与源码分析

Android中RelativeLayout和LinearLayout性能分析

2.3 I/O操作(SharedPreferences)

这里I/O操作仅针对SharedPreferences,因为这个基本是在Android中使用最多的储存库了。

2.3.1 读操作

读操作一般不会阻塞到主线程,如果读取的数据比较大而且需要有大量的处理操作,直接开子线程,在子线程处理。

2.3.2 写操作

写操作比读操作复杂一些,向SharedPreferences写入数据时候,有commitapply两个方法。

  • commit方法,直接将数据同步写入磁盘
  • apply方法,先将数据写入内存,再异步写入磁盘

commit和apply方法区别在于同步写入和异步写入,以及是否需要返回值。在不需要返回值的情况下,使用apply方法可以极大的提高性能。

在使用lint静态扫描代码时候,会建议使用apply去替代commit,说明官方也是支持使用apply方法的。

但是!!!

并不是是说apply不会阻塞到主线程,它也有可能会阻塞到主线程。详情请看这篇文章SharedPreferences调用导致的ANR分析

长话短说。

使用apply方法时候,会向QueuedWork队列中添加一个等待写入操作完成的线程,只有当写入操作完成后,才会从QueuedWork将等待线程线程移除掉。

而主线程中,Service的启动和stop以及Activity的onPause()onStop()生命周期都会等待其他异步线程完成,才会继续执行。

例如停止Service的时候代码如下(ActivityThread类中):

private void handleStopService(IBinder token) {
    ···

    QueuedWork.waitToFinish(); // 等待其他异步线程完成

    ···
}

可以看到,如果使用apply方法写入大量复杂数据,确实有可能会阻塞到主线程,甚至可能导致ANR。

优化方法:

  • 复杂数据
    • 即时性较弱(距离下次使用时间较长),新建子线程使用commit方法,防止阻塞到主线程
    • 即时性较强,可以考虑直接放在内存中
  • 简单数据,使用apply方法

2.4 序列化

大部分情况下,与后台交互使用的数据是gson格式。相信很多是直接使用google官方的Gson类来序列化和反序列化的。

我在项目中就遇见了有使用Gson反序列化复杂数据时候,造成的卡顿现象。

Gson序列化和反序列化是可以优化的。

// 反序列化
public List<Message> readJsonStream(InputStream in) throws IOException {
    JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
    List<Message> messages = new ArrayList<Message>();
    reader.beginArray();
    while (reader.hasNext()) {
        Message message = gson.fromJson(reader, Message.class);
        messages.add(message);
    }
    reader.endArray();
    reader.close();
    return messages;
}

// 序列化
public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
    JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
    writer.setIndent("  ");
    writer.beginArray();
    for (Message message : messages) {
        gson.toJson(message, Message.class, writer);
    }
    writer.endArray();
    writer.close();
}

2.5 反射

大部分框架或者SDK为了解耦,或多或少的会使用到反射,反射本身会性能就会比直接调用差很多。

有时候会大批量使用反射去实例化对象,所以不要在那些会被反射实例化的类的构造函数中做太多的事情。

建议在获取实例化对象后,再使用对象直接调用初始化方法。

2.6 异常

在代码中有时候可能会发生异常,所以一般使用try/catch来做保护,避免程序崩溃。

一旦有异常发生,系统会耗费资源去处理,本身对内存和CPU就会有消耗。

所以不能因为有了try/catch而不顾代码质量,要尽量保证没有大量的异常出现,我在项目中见过大量空指针异常出现,这种异常我们可以在代码层面就直接避免。

2.7 频繁GC

频繁的GC也会对性能造成影响,严重的会导致卡顿或者ANR。

频繁GC原因有两个

  • 内存抖动,大量的对象被创建又在短时间内马上被释放
  • 瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加 Heap的压力,从而触发更多其他类型的GC。

在项目中避免短时间内突然创建大量的对象。

2.8 设备&应用基本信息

获取设备&应用基本信息,比如说包名、IMEI信息等等,是比较耗时的操作,可以进行一些优化。

  • 固定信息获取采用缓存策略,例如版本号、版本名称、手机mac地址、运营商信息等等,一次获取,缓存到内存,下次直接使用
  • 不部分不固定信息,例如网络情况(2G/3G/4G/WIFI),可以采用监听网络变更方式,来即时更新网络情况
  • 其他不固定信息,比如基站信息,只能每次获取

3. 总结

关于性能优化方面知识,官方也出过视频Android Performance Patterns,网上也有很多翻译的文章,写得都比较详细,这篇文章是自身从平时一些常见点入手做的一些总结,希望对大家有帮助。

作者:xiaohanluo 发表于2017/8/1 16:53:52 原文链接
阅读:2 评论:0 查看评论

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

$
0
0

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

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

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

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

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


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

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

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


图2 Plugin调用OpenGL接口的过程

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

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


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

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

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

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

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

  

图4 Plugin的OpenGL上文创建过程

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

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

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


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

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

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

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

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


图6 Plugin执行SwapBuffers操作的过程

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

       2. instance,表示目标Plugin Instance。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ......

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

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

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

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

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

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

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

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

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

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

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

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

class Resource {
 public:
  ......

  PP_Resource pp_resource() const { return pp_resource_; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  thunk::EnterResourceCreation enter(instance);

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

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

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

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

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

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

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

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

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

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

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

  ......

 private:
  ......

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ......

  return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

       2. instance,表示目标Plugin Instance。

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

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

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

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

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

    ......

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ......

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ResourceT* object_;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ......

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

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

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

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

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

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

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

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

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

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

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

Android WebView简要介绍和学习计划

$
0
0

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

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

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

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

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

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

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


图1 App进程间不共享GNU_RELRO Section

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


图2 App进程间共享GNU_RELRO Section

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

作者:Luoshengyang 发表于2016/11/21 0:59:03 原文链接
阅读:36948 评论: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 原文链接
阅读:32528 评论: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 原文链接
阅读:32452 评论: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 原文链接
阅读:28060 评论:5 查看评论

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

Android 和 Dagger 2 中的依赖注入

$
0
0

原文:Dependency Injection in Android with Dagger 2
作者:Joe Howard
译者:kmyhy

在现代开发团队中到处充斥着“你一定要用依赖注入”的叫嚣。依赖注入(简称 DI)变成了一个威风十足的名字,足以让每个开发者都胆战心惊。

无处不在的依赖注入和它的名字一样复杂,它是一个重要的、可维护和可测试的软件构建工具。此外,通过依赖注入你可以极大地简化你的代码并允许用一种更简单的方式编写可测试的代码。

在这篇教程中,我们会将一个现成的 app “DeezFoods” 升级为依赖注入。这个 app 显示一张食品列表(来自 USDA 食品成分数据库)。用户可以点击一种食品查看它的糖分,然后对这个糖分含量点赞或或者拍砖。

这个 app 使用常见的 Android 库和设计模式,比如 Retrofi 和 MVP。我们会用流行的 Java/Android 依赖注入框架 Dragger 2 来进行依赖注入。

下图是 DeezFoodz 的详情页:

在此之前,请到 USDA 网站(前面的链接)获得访问权限。你需要获得它的 API key,否则无法在 app 中使用它。

介绍

到底什么是依赖?任何重要的软件程序都会包含多个组件,这些组件之间会来回调用并传递数据。

例如,在使用面向对象语言(比如 Android 中的 Java)时,对象会调用其引用的其它对象的方法。简单的依赖就是一个对象依赖于其它对象的具体实现。

具体到 Java 代码中,我们可以识别出自己代码中的依赖,比如你在一个对象中使用 new 关键字创建一个新对象。这时,你唯一责任就是创建这个对象,并在创建时正确配置这个对象。例如,有一个类 A:

public class A {
  private B b;
  public A() {
    b = new B();
  }
}

A 对象在构造函数中创建了变量 b。A 对象完全依赖了 B 的具体实现,为了能够正确调用到 b 的属性,我们配置了 b 变量。

这表明了 A 类对 B 类是耦合的或者是依赖的。如果 B 对象的创建比较复杂,则 A 类中也会得到体现。任何对 B 对象的配置的改变则必然对 A 类造成影响。

如果 B 类依赖于 C,C 依赖于 D,所有的复杂性都会在代码库中传递,并导致 app 中组件的紧耦合。

依赖注入是一个术语,用于描述了这种松散耦合的描述。在这个例子中,我们只需要做一小点修改:

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}

好了——依赖注入出现了。比起在 A 的构造器中创建一个 b 对象,现在 b 对象是以注入的方式传递到 A 的构造器中。配置 b 的任务可以在任何地方进行,A 类只是简单地对 B 类进行消费。

依赖反转原则

依赖注入经常和面向对象设计中的 SOLID 五原则之一相提并论:即依赖反转原则。关于 SOLID 的介绍,尤其是针对 Android 系统,请参考“依赖反转的王国”。

简单来说,依赖反转原则是说,依赖于抽象而不要依赖于具体实现。

还是之前的例子,将 B 用 Java 的接口来替换 Java 类。这样,就可以将不同的实现了 B 接口的具体的 B 的类型传递给 A 的构造函数了。这会带来几个好处:

  • 可以用各种 B 对象来测试 A 类。
  • 在某种测试场景中,根据需要可以使用假的 B 对象。
  • 测试 A 并不会依赖于 B 的具体实现。

虽然经常二者并提,但依赖注入和依赖反转原则是不一样的。基本上,依赖注入是一种遵循了依赖反转原则的技术。

Dagger 2

在 Java 的世界,有许多用于简化依赖注入的使用的框架。这些框架减少了大量重复的代码,提供了在软件系统中使用依赖注入的机制。

早期有一个 Java 依赖注入框架,叫做 Guice,由 Google 所创建。后来 Square 团队开发了 Dagger 框架,主要针对于 Android 系统。

虽然 Dagger 总的来说很棒,但第一版的 Dagger 框架也有几个缺点。例如,由于运行时反射导致的性能问题,以及 ProGuard 的难用。

因此,Dagger 2 框架问世了,它生成的代码更小,通过在编译时进行注入解决了性能问题。

Dagger (短剑,匕首)这个名字来自于开发中依赖的本质。依赖发生在对象之间,比如 A、B、C …,导致一种所谓的“有向非循环图”的结构。Dagger 和 Dagger 2 用于降低 Java 和 Android 项目中此类图的出现。

在本文后面,Dagger 一次专指 Dagger 2.

理论课上完了!开始编写代码吧!

开始

这里下载开始项目。

用 Android Studio 打开开始项目。你需要使用 Android Studio 2.2.2 以上版本。低于此版本,或者提示你升级 gradle 版本,请照提示进行。

修改 app 包下面的 Constants.java 文件,将 API_KEY 替换成你自己的值。

在模拟器或设备上运行 app,确认你可以编译通过。

这个 app 有两个页面。第一个是一个来自 USDA 食品成分数据库的食品列表,点击任何单元格,显示详情页,它会列出该食品的糖分数据。

查看项目结构和类文件。你会看到 app 使用了 MVP 模型来搭建代码结构。还使用了 Square 的 Retrofit 2 库来作为连接 USDA API 的网络层。

main 包下面的子包分别是 app、model、network、ui。如果你查看 ui 包,你会看到这两个页面的子包。每个页面都有自己的 View 类和 present 类。

打开 app 的 build.gradle 文件,你会看到 app 用 Butterknife 进行视图绑定,和一个类似 Java 8 streams 的lightweight stream 库

MVP

如果你不熟悉 MVP 模式,你可以先阅读一些在线资源。

MVP 和其它结构模式类似,实现了关注分离,就像 MVC 和 MVVM。对于 Android 的 MVP 来说,你的 activity 和 fragment 就是典型的 view 对象,实现了 View 的界面,处理 app 于用户之间的交互。

view 将用户动作传递给 presenter,后者负责业务逻辑,和数据库存储交互,比如服务端 API 或者数据库。model 层则用于表示构成 app 内容的对象。

以 DeezFoodz 来说,FoodActivity 类是详情页,实现了 FoodView 接口。FoodActivity 有一个对 FoodPresenter 接口的引用,这个接口负责访问 Food 类型的模型对象。

对 Retrofit 2 的使用放在了 network 包下面的 UsdaApi 类中。这个类定义了 app 所要调用的 USDA API 接口。有两个 GET 类型的调用,一个获取一个 FoodzList 对象,另一个通过查询参数获取指定 Food 的详情。

注意:Retrofit 是一个强大的类型安全的 HTTP 客户端。它将 USDA REST API 重新暴露成 Java 接口。Retrofit 简化了进行同步异步 web 请求的工作,将响应数据转换成 POJO。更多内容,请看 Android 网络教程:开始

DeezFoodz 中的依赖

打开 ui.food 下面的 FoodActivity 类。在 onCreate() 方法, 这几句创建和配置了 FoodPresenter:

presenter = new FoodPresenterImpl();
presenter.setView(this);
presenter.getFood(foodId);

这里,我们创建了一个 FoodPresenter 的具体实现类。打开 FoodPresenterImpl.java 看一下 getFood()。Retrofit 对象和 UsdaApi 对象都在这个方法中以工厂方法的方式创建和配置。

这导致了对模型、视图和 presenter 层的紧耦合。针对这个 view,换一个假的 presenter 就必须修改 View 的代码。创建 Retrofit 对象和 UsdaApi 对象的代码在两个 presenter 中重复了:FoodPresenterImpl 和 FoodzPresenterImpl。

接下来,我们用 Dagger 实习对 DeezFoodz 进行依赖注入,移除不同层之间的耦合和重复代码。

最后,是写代码的时候了!

项目中 Dagger 2 的相关配置

打开 app 的 build.gradle 添加 Dagger 依赖:

depencies {
  ...
  // Dependency Injection
  apt "com.google.dagger:dagger-compiler:2.2"
  compile "com.google.dagger:dagger:2.2"
  provided 'javax.annotation:jsr250-api:1.0'
  ...
}

省略号代表项目中的已有依赖。Android Studio 会提示你的 gradle 文件已改变,需要同步,因此请根据提示进行,确保你的 Dagger 能够正确引入。注意你还导入了 javax 的注解库,因为 Dagger 的许多功能都是通过 Java 注解来实现的。

模块

你要用到的第一个注解就是@Module。在 app 的 main 包下面新建一个 dagger 包,右键点击 main 包,选择 New/Package:

然后,在 dagger 包下面新建文件。右键点击 dagger,选择 New/Java Class。类名命名为 AppModule。

在新文件中添加类声明:

@Module
public class AppModule {
}

这里,我们创建了一个名为 AppModule 的类,并用 @Module 进行注解。Android Studio 会自动创建必须的 import 语句,如果没有,请用 Alt-Enter 创建。

@Module 注解告诉 Dagger,AppModule 类为 app 提供依赖。在一个项目中使用多个 Dagger 模块是比较常见的,通常用对于 app 级别的依赖会专门用一个模块来提供。

在类体中添加下列代码:

private Application application;

public AppModule(Application application) {
  this.application = application;
}

@Provides
@Singleton
public Context provideContext() {
  return application;
}

我们添加了一个私有成员保存对 app 的引用,用一个构造函数来配置 application 成员,以及一个 provideContext() 方法返回这个 application。注意这个方法有两个 Dagger 注解: @Provides and @Singleton。

@Provides 注解告诉 Dagger 这个方法提供了某种类型的依赖,在我们的例子中,就是 Context 对象。当 app 请求 Dagger 注入一个 Context 时,@Provides 注解告诉 Dagger 去哪里找到这个对象。

注意:这个 provider 的方法名,叫做 provideContext() 或者别的什么一点也不重要。Dagger 只关心返回类型。

@Singleton 注解告诉 Dagger 这个依赖只有一个单例对象,并且为你减少了许多重复的代码。

组件

现在你有了一个 Dagger 模块,它包含了一个可注入的依赖,我们要怎么使用它呢?

这需要用到另外了一个 Dagger 注解 @Component。新建一个 Java 文件在 dagger 包下,命名为 AppComponent>

在这个文件中,编写代码:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
}

我们告诉 Dagger 这个 AppComponent 是一个单例组件。@Component 注解用一个模块数组作为参数,对于这个参数,我们在数组中添加了一个 AppModule。

这个组件用于将对象和它们的依赖进行绑定,通常需要覆盖 injecdt() 方法。在第一版的 Dagger 中,这个过程在反射对象图的时候进行。为了使用组件,应该让它在 app 中所有需要注入的地方被访问。一般,就是 app 的 Applicatoin 子类。

首先,添加下列变量声明以及 Applicationd 的 get 方法:

private AppComponent appComponent;

public AppComponent getAppComponent() {
  return appComponent;
}

然后来初始化 AppCompoent.z DeezFoodzApplication 中添加如下方法:

protected AppComponent initDagger(DeezFoodzApplication application) {
  return DaggerAppComponent.builder()
      .appModule(new AppModule(application))
      .build();
}

AndroidStudio 会在 DaggerAppComponnet 上报错。这个类还没有被生成。通过 Android Studio 的 Build 菜单,选择 Make Module ‘app’ 。这会产生编译错误,但会清除掉代码中的报错。

再次执行 Make Module ‘app’,清除编译错误。然后,你会在 appModule()方法上出现一个警告,这个很快就能搞定。

最终,在 DeezFoodzApplication 修改 onCreate() 方法:

@Override
public void onCreate() {
  super.onCreate();
  appComponent = initDagger(this);
}

当 app 第一次启动,初始化了 appComponent 变量。

用 Dagger 2 进行依赖注入

在 AppComponent 接口中添加这个方法:

void inject(FoodzActivity target);

这里,我们指定 FoodzActivity 类需要依靠 AppComponent 进行注入。接下来我们将对应的对象注入到 FoodzActivity。

在 dagger 包中新建类 PresenterModule。在这个类中声明:

@Module
public class PresenterModule {
  @Provides
  @Singleton
  FoodzPresenter provideFoodzPresenter() {
    return new FoodzPresenterImpl();
  }
}

这个类会提供一个 FoodzPresenter ,这个方法会返回一个由 FoodzPresenterImpl 来具体实现的 presenter 对象。

然后,在 AppComponent 中的 @Component 注解中,添加 PresenterModule,和 AppComponent 放在一起。

@Component(modules = {AppModule.class, PresenterModule.class})

最后,打开 FoodzActivity ,它位于 ui.foodz 包下面。首先,在 presenter 字段前添加一个 @Inject 注解:

@Inject
FoodzPresenter presenter;

然后,在 onCreate() 方法中,将下列创建 presenter 的语句删除:

presenter = new FoodzPresenterImpl();

@Inject 注解告诉 Dagger,我们对 presenter 字段进行依赖注入。

运行 app,app 崩溃,报 NPE(空指针异常)错误。我们还有一个步骤忘了做。

修改 onCreate() 方法,在其中加入对 AppComponent.inject() 的调用:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_foodz);

  ((DeezFoodzApplication)getApplication()).getAppComponent().inject(this);

  ButterKnife.bind(this);
  ...

我们从 DeezFoodzApplication 获得了 AppComponent 的引用,然后用它将所有依赖注入到 FoodzActivity。因为 presenter 被声明为 @Inject,Dagger 会将一个 FoodzPresenter 实现注入给 FoodzActivity。

Dagger 知道在 PresenterModule 类中有一个 ProvideFoodzPresenter() 方法,并用它来创建用于注入的FoodzPresenter 对象。

运行 app。结果和之前一样,同时我们也解决了 NPE 问题。现在我们已经用 Dagger 2 实现了我们的第一个依赖注入!

根据前面我们对代码的修改,我们可以总结出一些规律。以 Dagger 向 FoodzActivity 中进行的依赖注入为例:

  1. 在 AppComponent 新建一个 inject() 方法,使用一个 FoodzActivity 对象做参数。
  2. 在 PresenterModule 中新增方法 provideFoodzPresenter()。
  3. 在 FoodzActivity 的 presenter 之前添加注解 @Inject。
  4. 在 FoodzActivity 的 onCreate() 方法中添加 DeezFoodzApplication.getAppComponent().inject(this)。

假设将 FoodzActivity 称作 Target 类,FoodzPresenter 用将被注入的接口 Source,那么上述步骤可以抽象为向任何目标类注入源接口的步骤:

  1. 在 AppCompoent 中增加 inject() 方法,用 Target 类作为参数。
  2. 在 PresenterModule 中,针对每个注入 Source 对象的方法用 @Provides 进行修饰。
  3. 在 Target 类中,对每个 Source 成员变量,用 @Inject 进行修饰。
  4. 在 Target 的 OnCreate() 方法中,添加 DeezFoodzApplication.getAppComponent().inject(this)。

给你出道题,将 FoodPresenter 详情页面注入到 FoodActivity 中(在 ui.food 包下面),文件中的这行代码要删除:

presenter = new FoodPresenterImpl();

这和我们在 FoodzActivity 中所做的步骤是一样的。使用上面的模板,如果做不出来,可以参考最终项目中的源代码。

注入 Network Graph

在读到这里的时候,列表页面和详情页面的 presenter 仍然在各自创建自己的网络依赖。在一个正常的使用了 Dagger 2 和 Retrofit 的 app 中,Retrofit 应该是以依赖注入的方式使用的。

这里列出了一些使用依赖注入和 Dagger 2 的好处,包括:

  • 解决代码复用问题
  • 解决依赖配置问题
  • 自动生成依赖图谱

在 dagger 包中新建文件 NetworkModule,文件内容为:

@Module
public class NetworkModule {
}

这里我们需要注入一个 UsdaApi 对象给 app 的 presenter 实现,以便 presenter 能够使用这些 API。

例如,打开当前的 FoodPresenterImpl 文件,你会发现 UsdaApi 依赖了一个 Retrofit 对象,而创建一个 Retrofit 对象需要一个字符串形式的 API base URL 和一个 Conver.factory 对象。

首先,在 NetworkModule 中新增两个方法和一个字符串常量:

private static final String NAME_BASE_URL = "NAME_BASE_URL";

@Provides
@Named(NAME_BASE_URL)
String provideBaseUrlString() {
  return Constants.BASE_URL;
}

@Provides
@Singleton
Converter.Factory provideGsonConverter() {
  return GsonConverterFactory.create();
}

这里有一个新注解出现了。@Named。你要注入一个字符串对象,在 Android app 中字符串是一种普通类型,我们可以通过 @Named 注解指定某个字符串应当是被提供的(类似 @Provides)。如果我们有很多变量需要注入时,也可以在自定义类型上使用同样的方法。

现在,你拥有了一个 String 和 一个 GsonConverterFactory, 在 NetworkModule 底部添加:

@Provides
@Singleton
Retrofit provideRetrofit(Converter.Factory converter, @Named(NAME_BASE_URL) String baseUrl) {
  return new Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(converter)
    .build();
}

@Provides
@Singleton
UsdaApi provideUsdaApi(Retrofit retrofit) {
  return retrofit.create(UsdaApi.class);
}

我们添加了两个 provide 方法,一个返回 Retrofit 对象,一个返回 UsdaApi 对象。这就让 Dagger 能够构建出一个依赖图谱,当一个对象请求一个 UsdaApi 对象注入时,Dagger 首先会向 provideUsdaApi(Retrofit retrofit) 方法提供一个 Retrofit 对象。

然后 Dagger 在图谱中查找 converter 和 baseUrl 以便提供给 provideRetrofit(Converter.Factory converter, @Named(NAME_BASE_URL) String baseUrl) 方法。

通过 @Singleton 注解, UsdaApi 和 Retrofit 对象都只会创建一次,然后在不同的 activity 中共用。

将 NetworkModule 添加到 AppComponent 的 @Component 注解:

@Component(modules = {AppModule.class, PresenterModule.class, NetworkModule.class})

然后,修改 PresenterModule 的 provide 方法,以便 presenter 的构造函数能够增加一个 Context 参数:

@Provides
@Singleton
FoodzPresenter provideFoodzPresenter(Context context) {
  return new FoodzPresenterImpl(context);
}

@Provides
@Singleton
FoodPresenter provideFoodPresenter(Context context) {
  return new FoodPresenterImpl(context);
}

想 AppComponent 中添加两个注入方法:

void inject(FoodzPresenterImpl target);
void inject(FoodPresenterImpl target);

然后,修改 FoodzPresenterImpl 和 FoodPresenterImpl 增加两个构造函数:

public FoodzPresenterImpl(Context context) {
  ((DeezFoodzApplication)context).getAppComponent().inject(this);
}
public FoodPresenterImpl(Context context) {
  ((DeezFoodzApplication)context).getAppComponent().inject(this);
}

在两个类中增加以下注入式字段:

@Inject
UsdaApi usdaApi;

现在,我们已经向 UsdaApi 注入两个 presenter 实现了,可以将它们中的这些重复的语句删除了:

Converter.Factory converter = GsonConverterFactory.create();

Retrofit retrofit = new Retrofit.Builder()
  .baseUrl(Constants.BASE_URL)
  .addConverterFactory(converter)
  .build();

UsdaApi usdaApi = retrofit.create(UsdaApi.class);

最后一次运行 app。这回,你会发现 app 行为和之前没有区别,但我们成功地使用了依赖注入,你的 app 维护性更好,更容易测试了。

结束

这里下载最终项目。

对 DeezFoodz 的改造花费了我们大量的工作。但在现实生活中使用依赖注入和 Dagger 2 这样的框架的 app 变得非常普遍,因为这些 app 的依赖图谱是非常复杂的。

Dagger 2 和依赖注入在测试 app 时尤其有用,它允许在测试中模拟后台 API 和数据存储。

更多关于 Dagger 2 的内容用法,还有:

  • Scopes
  • Subcomponents
  • Testing with Mockito

在网上有许多优秀的资源介绍了这些主题。终于结束了,祝你在注入时开心!

有任何问题和评论,请在下面留言。

作者:kmyhy 发表于2017/7/31 16:31:12 原文链接
阅读:222 评论:0 查看评论

android ndk 02 C语言 二级指针 指针运算 指针与数组

$
0
0

c函数速查: https://pan.baidu.com/s/1sllYdul

通过几个例子 复习 二级指针 指针的运算 指针与数组 函数指针 练习

指针为什么要有类型

指针和地址的区别, 指针有类型 地址没有类型.


#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <math.h>
#include <time.h>
//这是引用的头文件

举例: 1

// 1.指针为什么要有类型?

//指针有类型,地址没有类型

//地址只是开始的位置,类型读取到什么位置结束

void main(){
    int i = 89;
    //int 类型的指针
    int *p = &i;
    double j = 78.9;
    //赋值为double类型变量的地址
    p = &j;
    printf("double size:%d\n", sizeof(double));
    printf("%#x,%lf\n",p,*p); //想通过4字节读取8字节变量的值,是不行的    

    getchar();
}

2. NULL空指针 指针初始化


//2.NULL空指针  指针一般需要初始化 置空
void main(){
    int i = 9;
    int *p = NULL;
    //p = &i;

    //空指针的默认值为0
    printf("%#x\n",p);
    //访问内存地址0x000000操作系统不允许
    //p = 100; //操作系统不允许访问
    printf("%d\n",*p);
    getchar();
}

3, 多级指针 使用

//3.多级指针(二级指针)
//指针保存的是变量的地址,保存的这个变量还可以是一个指针变量
//动态内存分配给二维数组
void main(){
    int a = 50;
    //p1上保存的a的地址
    int* p1 = &a;

    //p2上保存的p1的地址
    int** p2 = &p1;

    //int*** p3 = &p2;

    printf("p1:%#x,p2:%#x\n",p1,p2);
    **p2 = 90;

    printf("%d\n",a);

    getchar();
}

上面的例子 通过操作二级指针改变了 a 的值

个人理解 : 二级指针 可以理解成二维数组 三级指针可以理解成三位数组

一级指针是一条线 , 二级指针是一个面, 三级指针 是一个立方体 .

image

— 分割线 —

第4个例子 指针的运算

发现markdown > 操作不错

//4 指针的运算

void main(){

    int ids[] = { 78, 90, 23, 65, 19 };
    printf("%#x\n", ids);
    printf("%#x\n", &ids);
    printf("%#x\n", ids[0]);

    //指针变量
    int *p = ids;
    printf("%d\n", *p);//默认打印第0个

    //指针的加法
    p++;
    printf("p的值 %d\n", *p);
    printf("16进制 %#x\n", *p);


    getchar();
}

效果图

image

第五个例子 通过指针给数组赋值


//5 通过指针给数组赋值

void main(){

    int uids[5];
    //int i = 0;
    //高级写法 现在的写法  这里没有操作指针 实际上内部操作了
    /*for (i; i < 5; i++)
    {
    uids[i] = i;
    }*/

    //早些版本的写法   操作指针了 
    int* p = uids;
    printf("%#x\n", p);
    int i = 0; //i是数组元素的值
    for (; p < uids + 5; p++){
        *p = i;
        i++;
    }
    int j = 0;
    for (; j < 5; j++)
    {
        printf("数组 uids[%d] = %d \n", j, uids[j]);
    }
    getchar();
}

iamge

第六个例子 函数指针

函数指针1


int msg(char *msg, char *title){
    messagebox(0, msg, title, 0); 
    return 0;
}

void main(){

    //函数返回值类型,函数指针的名称,函数的参数列表
    //函数指针 这是把一个函数赋给指针
    int(*fun_p)(char *msg, char *title) = msg;
    fun_p("内容", "title");
    getchar();

}

image

函数指针2 例子


int add(int a, int b){
    return a + b;
}

int minus(int a, int b){
    return a - b;
}

void msg(int(*func_p)(int a, int b), int m, int n){
    printf("执行一段代码...\n");
    printf("执行回调函数...\n");
    int r = func_p(m, n);
    printf("执行结果:%d\n", r);
}

void main(){
    //加法
    //int(*func_p)(int a, int b) = add;//把这句 直接放到msg 作为参数穿进去了

    msg(add, 3 , 5);

    getchar();

}

运行结果

image

最后一个例子


//案例:用随机数生成一个数组,写一个函数查找最小的值,并返回最小数的地址,在主函数中打印出来

int* getMinPointer(int ids[], int len){
    int i = 0;
    int *p = &ids[0];
    for (; i < len; i++)
    {
        if (ids[i] < *p){
            p = &ids[i];
        }
    }
    return p;
}

void main(){
    int ids[10];
    int i = 0;
    //初始化随机数发生器,设置种子,种子不一样,随机数才不一样
    //当前时间作为种子 有符号 int -xx - > +xx
    //看c语言速查表
    srand((unsigned)time(NULL));
    for (i; i < 10; i++)
    {
        //100范围内
        ids[i] = rand() % 100;
        printf("%d\n", ids[i]);
    }

    //调用
    int* p = getMinPointer(ids, sizeof(ids) / sizeof(int));
    printf("指针地址: %#x, 最小值: %d", p, *p);
    getchar();

}

运行结果:

image

作者:liudao7994 发表于2017/7/31 17:27:39 原文链接
阅读:186 评论:0 查看评论

Android Studio打包,生成jks密钥,签名Apk,多渠道打包

$
0
0

生成jks签名文件

选择Build > Generate Signed APK…

这里写图片描述

选择项目app > Next

这里写图片描述

选择Create new…

这里写图片描述

选择签名文件的路径,这些信息和eclipse一样了,填完点击OK

这里写图片描述

选择Next

这里写图片描述

我们这里做测试选择qihu360和xiaomi打包release版,最后点击Finish

这里写图片描述

等待生成

这里写图片描述

生成完毕提示我们查看,选择在文件夹查看

这里写图片描述

我们可以看到两个apk生成好了,现在我们做测试 
这里写图片描述

安装小米版本 
这里写图片描述

这里写图片描述

测试qihu360版本,先把xiaomi的卸载了,因为报名一样版本号一样,所以只能存在一个

这里写图片描述

这里写图片描述

最后修改我们的上一篇中的签名配置,然后我们重新签名打包

这里写图片描述

我们选择assembleRelease 点击Run

这里写图片描述

打包完毕,查看测试

这里写图片描述

这里写图片描述

作者:system_err 发表于2017/7/31 17:44:02 原文链接
阅读:71 评论:0 查看评论

Android面试题基础集锦《一》

$
0
0

深圳华谷网络技术有限公司

10道题值得思考

第一道题

1、Android 数据存储方式分为哪几种?

  1. SharedPreferences存储数据
    2. CotentProvider内容提供者
    3. 文件存储
    4. 数据库存储(Sqlite)
    5.网络存储

2. NDK是什么? 1. 一系列工具类的集合 2. Arm指令集 3. NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。 NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

3.Android线程间的网络通信有哪些?

共享内存(变量);文件,数据库;Handler;Java 里的 wait(),notify(),notifyAll()

4、Android横竖屏生命周期切换?

横竖屏切换生命周期

1、启动程序进入Activity界面

这里写图片描述

onCreate(初始化)–>OnStart(启动)–>OnResume(恢复)

2、旋转切换屏幕

这里写图片描述

onPause(暂停)–>OnSaveInstanceState(保存当前状态)–>OnStop(停止)–>OnDestory(销毁)–>onCreate(初始化)–>OnStart(启动)–>OnRestoreInstanceState(再次保存状态)–>OnResume(恢复)
3.Android横竖屏切换在手机开发中比较常见,很多软件在开发过程中为了避免横竖屏切换时引发不必要的麻烦,通常禁止掉横竖屏的切换。
一、在AndroidManifest.xml中设置activity中的android:screenOrientation属性值来实现。
(1)竖屏:android:screenOrientation=”portrait”
(2)横屏:android:screenOrientation=”landscape”
二、在Java代码中通过类似如下代码来设置 (不推荐这种方法,在大的app不同方向启动时会慢)
(1)竖屏: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
(2)横屏:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
三、如果要彻底禁止翻转,忽略重力感应带来的切换,(模拟器上不管用,在真机上是正确的)
(1)忽略重力:android:screenOrientation=”nosensor”
横竖屏辨识
一、在onConfigurationChanged里判断,为了onConfigurationChanged在监听屏幕方向变化有效需要以下条件
(1)AndroidManifest.xml增加权限:<uses-permission android:name="android.permission.CHANGE_CONFIGURATION"></uses-permission>
(2)AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13
(3)在AndroidManifest.xml的Activity里增加:android:configChanges="keyboard|screenSize|orientation|layoutDirection"
(4)在onConfigurationChanged(Configuration newConfig)进行判断

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(newConfig.orientation == 1)//竖屏 
if(newConfig.orientation == 2)// 横屏
}

二、因为当屏幕变为横屏的时候,系统会重调用Activity的onCreate方法可以在onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。

if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
Log.i("info","landscape"); // 横屏
} else if(this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT) {
Log.i("info","portrait"); // 竖屏
}

注意:该方法是在AndroidManifest.xml中不设置onConfigurationChanged才能重走生命周期

5、页面上现有ProgressBar控件progressBar,请用书写线程以10秒的的时间完成其进度显示工作。

          //得到progeressBar的最大长度  
                int progressBarMax = progressBar.getMax();  
                try {  
                    //progressBar当前的长度没有达到他的最长度,让循环一直进行  
                    while (progressBarMax != progressBar.getProgress()) {  
                        //拿到一个每次前进的进度值,因为是要10s完成,所以分为10份  
                        int stepProgress = progressBarMax / 10;  
                        //progressBar当前的进度值  
                        int currentProgress = progressBar.getProgress();  
                        //让progressBar进度为每次前进最大值的十分之一  
                        progressBar.setProgress(currentProgress + stepProgress);  
                        //前进一次,睡眠一秒  
                        Thread.sleep(1000);  
                    }  

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

            }  

6、Android如何避免OOM?

1)使用更加轻量的数据结构

例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,下图演示了HashMap的简要工作原理,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。

2)避免在Android里面使用Enum

3)减小Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:

inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

decode format:解码格式,选择ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,存在很大差异。

4)使用更小的图片

在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。

在Android上面最常用的一个缓存算法是LRU(Least Recently Use)

5)复用系统自带的资源

Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

6)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用

7)Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。

利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率上的提升(3.0以及4.4以后存在一些使用限制上的差异)。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。

8)避免在onDraw方法里面执行对象的创建

类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

9)StringBuilder

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

避免对象的内存泄露

内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,gc就会更容易被触发,容易出现内存抖动,从而引起性能问题。

10)注意Activity的泄漏

通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:

内部类引用导致Activity的泄漏

最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。

内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。

11)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。

12)注意临时Bitmap对象的及时回收

虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。

需要特别留意的是Bitmap类里面提供的createBitmap()方法:

这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。

13)注意WebView的泄漏

Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

14)资源文件需要选择合适的文件夹进行存放

我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。

15)谨慎使用static对象

因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象。

16)特别留意单例对象中不合理的持有

虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。

17)珍惜Services资源

18)优化布局层次,减少内存消耗

越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。

19)谨慎使用“抽象”编程

很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。

20)谨慎使用多进程

使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术。

7、Android UI如何刷新View?

第一,利用子线程发消息刷新UI。
第二,利用异步任务更新UI
第三,利用配置文件+activity的生命周期方法刷新UI。

8、面试题有四棵树如何让相邻的书两个之间的距离保持相等(腾讯面试题)

正三菱锥
扔一个坑里
平面解决不了的问题换种思维空间?

9、动画的分类?

动画分类

View动画、帧动画、属性动画

View动画包括:平移、旋转、缩放、透明度,View动画是一种渐近式动画
帧动画:图片切换动画
属性动画:通过动态改变对象的属性达到动画效果
View动画

继承自Animation,四个动画效果实现类:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation

10、自定义View?

这里写图片描述

1 2 3表示随机颜色值 90%表示进度条 上面是个五边形 如何实现一直连续的改变颜色值并不断更新进度?

作者:qq_15950325 发表于2017/7/31 22:59:58 原文链接
阅读:760 评论:0 查看评论

一起Talk Android吧(第三十四回:Android中的Activity六)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,我们在前面章回中介绍了Activity的状态和各个状态的变换,在本章回中我们继续来说Activity的生命周期。Activity的生命周期看上去是一个整体,其实可以对它进行详细的划分,我们可以将其分为三个小的部分:

  • 完整生命周期
  • 可见生命周期
  • 前台生命周期

这样的划分方法是官方认可的,大家可以官方网站中查找到。接下来我们分别介绍它们。

1.完整生命周期

从Activity的初始化状态开始,经历启动、运行、暂停、停止,这些状态,直到Activity被销毁为止。当然了,其中也包括我们在前面章回中提到的三个“波折的箭头”。随着Activity的状态变化,各个回调方法也在交替执行。我们在前面章回介绍过各个状态以及与状态匹配的回调方法,如果有看官忘记了,可以回顾一个前面章回中的内容。

2.可见生命周期

从Activity的启动状态开始,经历运行和暂停状态,直到Activist的停止状态为止,当然了,其中也包括两个“波折的箭头”。在整个生命周期中,我们都可以看到Activity,因此给它取名叫可见生命周期,这是一个十分形象的名字。

3.前台生命周期

从Activity的运行状态开始,到暂停状态为止,当然了,其中也包括一个“波折的箭头”。在整个生命周期中,Activity一直可以获取焦点,我们对它操作后,它也会做出相应的响应。鉴于这个原因,我们称这个生命周期为前台生命周期。

看官们,下面展示一个我在很早之前总结出来的图形,虽然图的内容有点旧(大约是1.6版本时的图形),但是内容还是正确的,从图形可以可以更加直观地看到这三个生命周期.

这里写图片描述

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 23:13:12 原文链接
阅读:254 评论:0 查看评论

一起Talk Android吧(第三十五回:Android中的Activity七)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,我们在前面章回中介绍了Activity的状态以及生命周期,这部分内容都是偏向于理论的知识,我们一直推崇实践,今天给大家介绍一下,如何去实践。其实也不是什么新的方法,就是使用断点进行调试,我简单介绍一下,算是新瓶装旧酒吧,希望大家喝的开心。

首先建立一个新的工程,在MainActivity中重写生命周期中的方法就可以。下面是我写的代码,供大家参考:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

重写完这些方法后,在每个方法上都打上断点,然后开始调试刚才新建的项目。打断点调试是常见的调试方法,相信大家都会操作。因此,我就不啰嗦了。在调试过程中希望大家结合我们给出的生命周期图来分析程序的运行过程,这样有助于大家理解Activity的生命周期。

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 23:40:26 原文链接
阅读:318 评论:0 查看评论

58 同城 Android 端 HTTPS 实践之旅

$
0
0

自 WWDC 2016 苹果传递出从 2017 年 1 月起强制启用应用程序安全传输协议(App Transport Security)的信号,各大厂均开始了 HTTPS 化的征程。虽然目前苹果将此计划延期,但 HTTPS 协议已经在各大厂开花结果。

前言

HTTPS 协议是以 SSL 协议为基础的安全版 HTTP 协议,好处不言自明,即为安全。对于用户来说,HTTPS 协议不仅能保障自己的隐私与数据安全,同时也降低了“页面小弹窗”的困扰,极大地提升了用户体验。本文将介绍 58 同城 App 在 HTTPS 改造方面的一些经验,并对 Android 端 HTTPS 实践中遇到的问题进行总结。

项目准备

58 同城平台为了推动各业务线进行 HTTPS 改造,需要提供各端的完整改造方案。所以,我们在项目准备阶段,主要做了两部分事情:

  • 调研 HTTPS 协议与部署相关问题;
  • 输出具体改造方案。

在调研 HTTPS 协议与部署相关问题之后,各端均输出了一份具体的改造方案,如下:

  • 服务端:动态适配请求协议头,消灭硬编码,域名升级;
  • 前端:页面静态路径去掉协议头;
  • 客户端:升级 Native 网络库支持 HTTPS 及 WebView 升级(此仅 iOS 端);
  • 测试:HTTPS 测试方法与测试点、上线流程。

接下来,笔者将主要对上述改造方案中的 Android 客户端实践及其涉及原理进行详细介绍,对于 HTTPS 协议与 HTTP2 协议原理分析感兴趣的读者,可以阅览《HTTPS 与 HTTP2 协议分析》了解更多。

改造 Android 端 HTTPS 实践

改造后的项目架构如图 1 所示,相对于 58 同城 App 原有架构,添加了 OkHttp 网络库进行网络层收敛,而 API 请求、图片请求、H5 页面资源请求最终均会在 OkHttp 创建的连接上进行数据传输。

图 1 项目架构设计

需要说明的是,这里之所以引入了 OkHttp 网络库,主要是因为 HTTP2 协议的支持。

因为当考虑进行 HTTPS 改造时,我们首先想到的一个问题便是 HTTPS 性能低下。相对 HTTP 协议来说,HTTPS 协议建立数据通道更耗时,若直接部署到 App 中,势必会降低数据传递的效率,间接影响用户体验。

HTTP2 协议本是为了解决 HTTP/1.X 协议的低效率而诞生的,不过在实际应用中,只会在 HTTPS 协议握手阶段进行协议协商,所以 HTTP2 目前直接改善的其实是 HTTPS 的低效率。为此,HTTP2 主要提出了两大改进点:

  • 多路复用。同一域名下的请求,可通过同一条链路进行传输,不必单独建立链路,有效节省开销;
  • 压缩头信息。将头部字段缓存为索引,客户端与服务端维护索引表,通信过程中尽可能采用索引进行通信,收到索引后查询索引表,才能解析出真正的头部信息。

因此,我们在 Android 端的具体改造方案主要在于 OkHttp 库与调用库之间的交互与包装,其中:

  • Volley 底层连接替换 OkHttp,只需要创建 OkHttpStack 类实现 HTTPStack 接口并替换 HurlStack 即可,网上成型方案较多,这里不再赘述。
  • Fresco 底层连接替换 OkHttp 更加简单,官方已经提供了 OkHttpNetworkFetcher 类,直接通过 ImagePipelineConfig 设置 NetworkFetcher 即可完成替换。在后面的具体实践部分,还会讲到对 Fresco 官方提供的 OkHttpNetworkFetcher 在取消加载部分的优化。

部署实施

对 App 进行 HTTPS 改造需要服务端、前端、客户端一同配合开发,QA 进行质量把控。同时,由于 58 同城 App 涵盖了众多业务线与第三方,每个业务乃至接口的部署都可能会对其他业务造成影响。所以,各业务开发与部署的时序、整体进度的把控是我们面临的最大难题。

部署实施步骤

经过与各业务线的充分讨论,我们最终确立了如下实施步骤:

  • 以业务线为单位进行服务梳理,确定并理清各业务线的依赖关系。
  • 业务线基于依赖关系进行改造排期预估,并着手开发。

58 同城 App 平台方及时主动地跟进各业务线,解决改造期间的技术问题与协调业务线间联调配合等。同时,开发必要的风险规避策略(譬如降级策略),以降低后续灰度上线风险。

业务线完成改造并通过测试后,58 同城 App 平台方修改业务线入口跳转协议,提供 HTTPS 入口进行灰度测试,若效果符合预期,则逐步提高灰度测试比例直至全量。

实施注意事项

通过以上步骤,基本保证了业务线间能够高效并行开发,但在实施过程中,有几点需要特别注意:

  • 业务线间由于历史问题,有些业务存在严重的交叉依赖情况,需要及时协调业务线进行暂时的依赖解除。

    何为“暂时的依赖解除”?多个业务线由于并行进行 HTTPS 改造,服务的相互依赖导致单个业务线无法测试。此时进度较快的业务线可以将依赖的服务使用 HTTP 协议代替访问,或通过 host 配置相关服务的测试机,待其他业务线完成部署后再改回 HTTPS 协议。

  • 虽然以业务线为单位进行并行开发可以将开发、测试等流程分发到业务线内部完成,但 HTTPS 改造涉及到的服务众多,改造成本很高,可能会与业务线的业务需求开发产生冲突。因此,平台方需要及时跟进业务线的进度,及时妥善地处理阻塞因素。

HTTPS 实践问题汇总

鉴于 HTTPS 用户体验更好,以及可以解决 HTTPS 性能问题的切实方案,58 同城 App 便开展了全站 HTTPS 化的改造。当然,在改造过程中,我们也遇到了一些问题,主要有以下几类:

  • HTTPS 调试问题
  • 性能问题
  • 环境问题
  • OkHttp 接入问题

下面将对以上问题进行依次分析。

HTTPS 调试问题

进行 HTTPS 改造遇到的第一个问题就是 HTTPS 不好调试。当我们绑定了 PC 作为代理,通过 Charles 或 Fiddler 抓取请求时,它们即成为我们的代理服务器。若不安装 Charles 或 Fiddler 的证书到设备上,便无法完成对代理服务器的身份认证,后续的应用数据传输也就无从谈起,直接的表现即为 HTTPS 请求失败。

面对这种问题,最简单的方式是给设备安装证书,之后便可以调试 HTTPS 请求了。但每台 PC 的代理证书各异,若需要像 HTTP 请求一样方便地调试,须对每台手机安装每台 PC 的代理证书。这点对于仅需要验证请求数据的测试同学来说比较痛苦,只是为了看下数据,为什么要这么麻烦?

在此给出两点可行的建议:

  • 客户端将 HTTPS 请求结果作为日志输出,开发与测试同学可以针对日志分析接口问题;
  • 采用类似 Chuck 项目(https://github.com/jgilfelt/chuck)的思路,为 OkHttp 添加 interceptor 以收集请求结果,并将其以 UI 形式直观地展示出来。

通过以上两种方式,可以有效地简化请求结果的验证与查看。若是需要修改请求的结果进行调试开发,是否是 HTTPS 协议已无关紧要,此时借助 Charles 与 Fiddler 调试 HTTP 接口也非常简单。

性能问题

HTTPS 协议性能较 HTTP 协议稍差,也由此造成了弱网情况下的连接超时问题。

  • 多路复用特性提升 HTTPS 性能

HTTPS 协议通信效率较 HTTP 协议通信效率低是众所周知的事实,当 App 全面升级为 HTTPS 时,通信效率的降低会直接影响用户体验。我们经过线上数据对比发现,通过 HTTPS 协议访问,其耗时是 HTTP 协议访问耗时的 1.3-2.1 倍。

那么,HTTPS 该如何提高通信效率呢?

在建立安全通道部分,由于涉及到身份认证与算法、密钥协商,两次网络往返是很难优化的。但在建立了安全通道后,若能复用此通道,则后续请求便可避免两次网络往返。所以,基于这种思路,58 同城 App 主要借助 HTTP2(或 SPDY)协议的多路复用特性,提高通道使用率,进而提高通信效率。

由于多路复用特性是域名级复用,所以最重要的一点便是收敛域名。收敛效果越好,通道的复用率越高。因此,我们对 API 接口、图片等资源接口进行了域名收敛,尽可能地收敛多级域名至二级域名、收敛零散域名至统一域名。

综上,借助 HTTP2(或 SPDY)协议的多路复用特性,以及对现有业务的域名收敛进行优化,通过线上数据对比得出,其访问耗时是 HTTP 协议访问耗时的 1.2 倍左右。

  • 提高列表页 HTTPS 图片加载速度

58 同城 App 使用的图片库是 Fresco,在 OkHttp 接入后,我们也顺势将 Fresco 的 Fetcher 替换为 OkHttp 实现,以提高 HTTPS 图片的加载速度。但官方提供的 OkHttpNetworkFetcher 却仍有优化空间。比如,OkHttpNetworkFetcher 的加载任务取消操作是通过调用 Call.cancel()来实现的。具体代码如下:

//OkHttpNetworkFetcher 对 Call 进行取消
fetchState.getContext().addCallbacks(
    new BaseProducerContextCallbacks(){
        @Override
        public void onCancellationRequested(){
        if(Looper.myLooper()! = Looper.getMainLooper()){
            call.cancel();
        }else{                           mCancellationExecutor.execute(new Runnable(){
        @Override public void run() {
           call.cancel();
          }
        });
      }
   }
});

对 Call.cancel()执行加载取消操作后,加载仍然会被线程池调用执行,直到 RetryAndFollowInterceptor 执行时 cancel 操作才会起作用。

因此,我们对 OkHttpNetworkFetcher 进行了改写。在 Fresco 取消加载的回调中,对图片加载任务对应的 future 进行 cancel 操作,便可以减少 RetryAndFollowInterceptor 之前的逻辑处理(主要是自定义 Interceptor 部分)。

具体代码实现与 HttpUrlConnectionNetworkFetcher 的取消回调实现类似。

HTTPS 环境问题

客户端证书验证问题

HTTPS 改造过程中,常见的一个问题便是客户端证书验证出错,究其原因,往往是因为:

  • 证书管理混乱,导致下发证书的域名与请求域名不符,无法通过验证。
  • 证书过期。
  • 证书签发 CA 未被内置于客户端。
  • 证书链不完整,无法验证。

在此,我们具体剖析一下“CA 未被内置于客户端”与“证书链不完整”的问题。

证书签发 CA 未被内置于客户端——由于 CA 数量众多,质量也参差不齐,面对同样众多的手机厂商与自定义 ROM,无法保证 CA 能够内置到客户端证书列表中,所以 CA 存在不被客户端认可的可能性(Google 也会基于 CA 认可度进行证书列表的更新)。相对来说,顶级 CA 的设备兼容性较好,若遇到根证书路径找不到的异常,可以考虑更换 CA,签发证书。

证书链不完整,无法验证——在握手协议中,服务端会下发 Certificate 消息给客户端,消息中携带了由 CA 签发的证书与 CA 证书构成的证书链。客户端通过证书链信息,逐级向上寻找根证书,找到后再通过根证书的公钥逐级向下验证证书链,若证书链验证通过,则身份验证阶段完成。

倘若服务端只下发了自己的证书或下发的证书链不足以寻找到根证书,导致验证流程断裂,则无法通过身份验证。这是服务端证书配置部署问题,如果证书认证失败,很有可能是这个原因造成的。

DNS 劫持问题

虽然我们采用 HTTPS 提高了通信安全,但 DNS 劫持的情况仍然无法解决。当我们希望获取 IP 地址时,需要通过 DNS 服务器进行查询,若访问的服务器被污染,返回给我们错误的 IP 地址,此时便产生了 DNS 劫持问题。

相对于 HTTP 协议,HTTPS 协议 DNS 劫持的后果更为严重。于 HTTP 协议而言,DNS 劫持后会产生监听数据或插入数据的风险,而功能可能不受影响。但对于 HTTPS 协议来说,DNS 劫持后,服务器下发证书无法通过客户端认证,或是服务器根本没有开启 443 端口,均无法建立 HTTPS 连接。

面对 DNS 劫持情况,这里提供两种解决方案:

  • 下发(或内置)IP 列表,通过 DNS 接口由客户端进行 DNS 解析或 IP 地址比对;
  • 运维监控或第三方监控,对出现 DNS 劫持的区域向运营商投诉解决。

降级策略

考虑到 HTTPS 存在证书验证、DNS 劫持及代理 443 未开启等诸多问题,在实践过程中,我们也添加了降级策略。启动 App 时,通过服务接口下发域名降级字典,譬如:

{"key" : [{"HTTPS://app.58.com" : "http://app.58.com"}]}

在请求前,会将 URL 与降级字典匹配处理,经过匹配的 URL 再发起请求,避免 HTTPS 改造影响用户功能。

OkHttp 接入问题

OkHttp 是目前使用最广泛的支持 HTTP2 的 Android 端开源网络库,下面分享下 58 同城 App 在接入 OkHttp 过程中遇到的问题。

OkHttp 头部数据非法字符抛出异常

OkHttp 构造头部数据主要通过 Request.Builder 对象的 add(name,value)与 set(name,value)两种方法,而它们内部均会调用 checkNameAndValue(name,value),这个方法会对 name 与 value 分别进行字符检测,若字符不在\u001f 至\u007f 之间,则会抛出 IllegalArgumentException。

private void checkNameAndValue(String name, String value) {
  if(name == null) throw new NullPointerException("name == null");
  if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
  for(int i = 0, length = name.length(); i < length; i++) {
     char c = name.charAt(i);
     if (c <= '\u001f' || c >= '\u007f') {
     throw new IllegalArgumentException(Util.format(
          "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
     }
}
   if (value == null) throw new NullPointerException("value == null");
   for (int i = 0, length = value.length(); i < length; i++) {
     char c = value.charAt(i);
     if (c <= '\u001f' || c >= '\u007f') {
     throw new IllegalArgumentException(Util.format(
         "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
     }
   }
}

从 OkHttp 底层代码可以发现,OkHttp 对字符串是通过 UTF-8 编码的,这里强制进行字符检测,猜想可能是基于对编码规范的考虑。

为了避免字符检测失败导致的异常,我们可以通过其他 API 绕过这个限制:

  • Header 添加通过 Headers.of 方法生成 Headers,并通过 Builder.headers 方法配置进去。
  • 通过 Internal.instance.addLenient 方法直接设置 name 与 value,add 与 set 方法底层也是调用这个方法。不过这个方法需要保证 Internal.instance 已初始化,即 OkHttpClient 已创建,否则会抛出空指针异常。

OkHttp 中 post 请求抛出异常

当我们使用 Request.Builder 类通过 post(RequestBody)方法进行 post 请求构造时,若不对 RequestBody 做判空操作,则有可能会抛出 IllegalArgumentException。

post(RequestBody)方法最终会调用 method()方法,它会对请求类型与 body 做校验,若 post 请求对应的 body 为 null,则抛出异常。代码如下:

public Builder method(String method, RequestBody body) {
  if (method == null) throw new NullPointerException("method == null");
  if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
  if (body != null && !HttpMethod.permitsRequestBody(method)) {
  throw new IllegalArgumentException("method " + method + " must not have a request body.");
  }
  //这里便是 body 为空时,异常抛出点
  if (body == null && HttpMethod.requiresRequestBody(method)) {
  throw new IllegalArgumentException("method " + method + " must have a request body.");
  }
  this.method = method;
  this.body = body;
  return this;
}

所以在构造 post 请求时,需要对 RequestBody 进行非空判断,若 RequestBody 为空,则需要构造一个无内容的 RequestBody 对象。

OkHttp 在 HTTP2 协议下 Response 监听线程崩溃

在接入 OkHttp 并使用 HTTP2 协议进行通信后,第三方应用崩溃检测工具(如 Bugly)会收集到线上版本关于 OkHttp 的两种崩溃,分别为 EOFException、ArrayIndexOutOfBoundsException。这两种崩溃的堆栈信息显示,均是在 FramedConnection 内部类 Reader 的 execute( )方法中抛出的。

@Override protected void execute() {
 ......
 try {
 ......
} catch (IOException e) {
    ......
    } finally {
    try {
    //抛出点 1
    close(connectionErrorCode, streamErrorCode);
    } catch (IOException ignored) {
   }
    //抛出点 2
    Util.closeQuietly(frameReader);
    }
}

究其原因,Reader 的 execute 方法被独立线程调用进行 Response 的监听,在连接断开或异常中断的情况下,会进入代码中 finally 代码块,而 finally 代码块只 Catch 住了 OkHttp 可能抛出的异常,并没有关注 Okio 抛出的异常。

这个问题当前还无法稳定复现,可能出现在两端,也可能出现在国内复杂的网络环境下,排查较为复杂。而对连接断开与异常中断等场景,如果我们 Catch 住所有的异常,也不会对用户有任何影响。
目前我们的处理方式便是对 OkHttp 进行重打包,对整个 execute()方法进行捕获,以解决 Response 监听线程崩溃的问题。

在 HTTP2 协议下头信息小写问题

HTTP2 为了解决 HTTP1.X 中头信息过大导致效率低下的问题,提出了通过 HPACK 压缩算法压缩头部信息的解决方案。

正因为 HPACK 以索引代替头部字段,所以相同头部字段若因为大小写的问题导致存在多个索引,是一种很大的浪费。举个例子,“accept-encoding”、“Accept-Encoding”与“ACCEPT-ENCODING”表达的是一个意思,所以 HTTP2 规定,头部信息统一用小写。

OkHttp 的实现,也是统一采用小写:

private static final Header[] STATIC_HEADER_TABLE = new Header[] {
   ......
   new Header("accept-charset", ""),
   new Header("accept-encoding", "gzip, deflate"),
   new Header("accept-language", ""),
   new Header("accept-ranges", ""),
   new Header("accept", ""),
    ......
};

所以当我们需要针对某些头信息进行逻辑处理时,首先要对字段进行小写的格式化操作,以避免监听不到头部字段或添加的大写头部字段被小写头部字段覆盖。

总结

站在技术的角度解决用户痛点是每个开发者的愿景,HTTPS 改造虽然只是一次普通的技术改造,但对用户隐私保护与用户体验优化却有着深远的影响。通过 HTTPS 改造项目,58 同城 App 完成了接口的 HTTPS 化,数据监听与内容篡改已成往事,用户体验也得到了保障,但由于 58 同城 App 涉及到众多业务,HTTPS 性能方向上仍然有很大空间亟待我们后续优化。

作者:赵岘,58 同城 Android 高级工程师,主要负责性能优化与质量提高方向,主导了 58 同城 App 端 HTTPS 改造项目。
责编:唐门教主
声明:本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》。

作者:Byeweiyang 发表于2017/8/1 9:56:32 原文链接
阅读:850 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


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