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

Android系统服务(一)解析ActivityManagerService(AMS)

$
0
0

相关文章
Android系统启动流程系列
Android应用进程系列
Android深入四大组件系列

前言

此前在Android系统启动流程、应用进程以及深入四大组件这三个系列文章中,都提及到了AMS,但都没有系统的来讲解它,本文就以AMS为主来进行讲解,其中会有一些知识点与这些系列文章有所重合,这里会尽量做到详尽讲解。阅读此文章前,最好阅读相关文章中列出的系列文章,否则我不敢保证这篇文章你能看的懂。

1.概述

AMS是系统的引导服务,应用进程的启动、切换和调度、四大组件的启动和管理都需要AMS的支持。从这里可以看出AMS的功能会十分的繁多,当然它并不是一个类承担这个重责,它有一些关联类,这在文章后面会讲到。AMS的涉及的知识点非常多,这篇文章主要会讲解AMS的以下几个知识点:

  • AMS的启动流程。
  • AMS与进程启动。
  • AMS家族。

2.AMS的启动流程

AMS的启动是在SyetemServer进程中启动的,在Android系统启动流程(三)解析SyetemServer进程启动过程这篇文章中提及过,这里从SyetemServer的main方法开始讲起:
frameworks/base/services/java/com/android/server/SystemServer.java

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

main方法中只调用了SystemServer的run方法,如下所示。
frameworks/base/services/java/com/android/server/SystemServer.java

private void run() {
       ...
           System.loadLibrary("android_servers");//1
       ...
           mSystemServiceManager = new SystemServiceManager(mSystemContext);//2
           LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
       ...    
        try {
           Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartServices");
           startBootstrapServices();//3
           startCoreServices();//4
           startOtherServices();//5
       } catch (Throwable ex) {
           Slog.e("System", "******************************************");
           Slog.e("System", "************ Failure starting system services", ex);
           throw ex;
       } finally {
           Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
       }
       ...
   }

在注释1处加载了动态库libandroid_servers.so。接下来在注释2处创建SystemServiceManager,它会对系统的服务进行创建、启动和生命周期管理。在注释3中的startBootstrapServices方法中用SystemServiceManager启动了ActivityManagerService、PowerManagerService、PackageManagerService等服务。在注释4处的startCoreServices方法中则启动了BatteryService、UsageStatsService和WebViewUpdateService。注释5处的startOtherServices方法中启动了CameraService、AlarmManagerService、VrManagerService等服务。这些服务的父类均为SystemService。从注释3、4、5的方法可以看出,官方把系统服务分为了三种类型,分别是引导服务、核心服务和其他服务,其中其他服务是一些非紧要和一些不需要立即启动的服务。系统服务总共大约有80多个,我们主要来查看引导服务AMS是如何启动的,注释3处的startBootstrapServices方法如下所示。

frameworks/base/services/java/com/android/server/SystemServer.java

   private void startBootstrapServices() {
        Installer installer = mSystemServiceManager.startService(Installer.class);
        // Activity manager runs the show.
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();//1
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);
      ...
    }

在注释1处调用了SystemServiceManager的startService方法,方法的参数是ActivityManagerService.Lifecycle.class:
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

  @SuppressWarnings("unchecked")
    public <T extends SystemService> T startService(Class<T> serviceClass) {
        try {
           ...
            final T service;
            try {
                Constructor<T> constructor = serviceClass.getConstructor(Context.class);//1
                service = constructor.newInstance(mContext);//2
            } catch (InstantiationException ex) {
              ...
            }
            // Register it.
            mServices.add(service);//3
            // Start it.
            try {
                service.onStart();//4
            } catch (RuntimeException ex) {
                throw new RuntimeException("Failed to start service " + name
                        + ": onStart threw an exception", ex);
            }
            return service;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

startService方法传入的参数是Lifecycle.class,Lifecycle继承自SystemService。首先,通过反射来创建Lifecycle实例,注释1处得到传进来的Lifecycle的构造器constructor,在注释2处调用constructor的newInstance方法来创建Lifecycle类型的service对象。接着在注释3处将刚创建的service添加到ArrayList类型的mServices对象中来完成注册。最后在注释4处调用service的onStart方法来启动service,并返回该service。Lifecycle是AMS的内部类,代码如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   public static final class Lifecycle extends SystemService {
        private final ActivityManagerService mService;
        public Lifecycle(Context context) {
            super(context);
            mService = new ActivityManagerService(context);//1
        }
        @Override
        public void onStart() {
            mService.start();//2
        }
        public ActivityManagerService getService() {
            return mService;//3
        }
    }

上面的代码结合SystemServiceManager的startService方法来分析,当通过反射来创建Lifecycle实例时,会调用注释1处的方法创建AMS实例,当调用Lifecycle类型的service的onStart方法时,实际上是调用了注释2处AMS的start方法。在SystemServer的startBootstrapServices方法的注释1处,调用了如下代码:

 mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();

我们知道SystemServiceManager的startService方法最终会返回Lifecycle类型的对象,紧接着又调用了Lifecycle的getService方法,这个方法会返回AMS类型的mService对象,见注释3处,这样AMS实例就会被创建并且返回。

3.AMS与进程启动

Android系统启动流程(二)解析Zygote进程启动过程这篇文章中,我提到了Zygote的Java框架层中,会创建一个Server端的Socket,这个Socket用来等待AMS来请求Zygote来创建新的应用程序进程。要启动一个应用程序,首先要保证这个应用程序所需要的应用程序进程已经被启动。AMS在启动应用程序时会检查这个应用程序需要的应用程序进程是否存在,不存在就会请求Zygote进程将需要的应用程序进程启动。Service的启动过程中会调用ActiveServices的bringUpServiceLocked方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

  private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
  ...
  final String procName = r.processName;//1
  ProcessRecord app;
  if (!isolated) {
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);//2
            if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                        + " app=" + app);
            if (app != null && app.thread != null) {//3
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode,
                    mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg);//4
                    return null;
                } catch (TransactionTooLargeException e) {
              ...
            }
        } else {
            app = r.isolatedProc;
        }
 if (app == null && !permissionsReviewRequired) {//5
            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                    "service", r.name, false, isolated, false)) == null) {//6
              ...
            }
            if (isolated) {
                r.isolatedProc = app;
            }
        }
 ...     
}

在注释1处得到ServiceRecord的processName的值赋值给procName ,其中ServiceRecord用来描述Service的android:process属性。注释2处将procName和Service的uid传入到AMS的getProcessRecordLocked方法中,来查询是否存在一个与Service对应的ProcessRecord类型的对象app,ProcessRecord主要用来记录运行的应用程序进程的信息。注释5处判断Service对应的app为null则说明用来运行Service的应用程序进程不存在,则调用注释6处的AMS的startProcessLocked方法来创建对应的应用程序进程,
具体的过程请查看Android应用程序进程启动过程(前篇)

4.AMS家族

ActivityManager是一个和AMS相关联的类,它主要对运行中的Activity进行管理,这些管理工作并不是由ActivityManager来处理的,而是交由AMS来处理,ActivityManager中的方法会通过ActivityManagerNative(以后简称AMN)的getDefault方法来得到ActivityManagerProxy(以后简称AMP),通过AMP就可以和AMN进行通信,而AMN是一个抽象类,它会将功能交由它的子类AMS来处理,因此,AMP就是AMS的代理类。AMS作为系统核心服务,很多API是不会暴露给ActivityManager的,因此ActivityManager并不算是AMS家族一份子。
为了讲解AMS家族,这里拿Activity的启动过程举例,Activity的启动过程中会调用Instrumentation的execStartActivity方法,如下所示。
frameworks/base/core/java/android/app/Instrumentation.java

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
      ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

execStartActivity方法中会调用AMN的getDefault来获取AMS的代理类AMP。接着调用了AMP的startActivity方法,先来查看AMN的getDefault方法做了什么,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

 static public IActivityManager getDefault() {
        return gDefault.get();
    }
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");//1
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);//2
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }+
    };
}

getDefault方法调用了gDefault的get方法,我们接着往下看,gDefault 是一个Singleton类。注释1处得到名为”activity”的Service引用,也就是IBinder类型的AMS的引用。接着在注释2处将它封装成AMP类型对象,并将它保存到gDefault中,此后调用AMN的getDefault方法就会直接获得AMS的代理对象AMP。注释2处的asInterface方法如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }
    return new ActivityManagerProxy(obj);
}

asInterface方法的主要作用就是将IBinder类型的AMS引用封装成AMP,AMP的构造方法如下所示。

frameworks/base/core/java/android/app/ActivityManagerNative.java

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }
...
 }

AMP的构造方法中将AMS的引用赋值给变量mRemote ,这样在AMP中就可以使用AMS了。
其中IActivityManager是一个接口,AMN和AMP都实现了这个接口,用于实现代理模式和Binder通信。
再回到Instrumentation的execStartActivity方法,来查看AMP的startActivity方法,AMP是AMN的内部类,代码如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
           String resolvedType, IBinder resultTo, String resultWho, int requestCode,
           int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
     ...
       data.writeInt(requestCode);
       data.writeInt(startFlags);
     ...
       mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);//1
       reply.readException();+
       int result = reply.readInt();
       reply.recycle();
       data.recycle();
       return result;
   }

首先会将传入的参数写入到Parcel类型的data中。在注释1处,通过IBinder类型对象mRemote(AMS的引用)向服务端的AMS发送一个START_ACTIVITY_TRANSACTION类型的进程间通信请求。那么服务端AMS就会从Binder线程池中读取我们客户端发来的数据,最终会调用AMN的onTransact方法,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

   @Override
   public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
           throws RemoteException {
       switch (code) {
       case START_ACTIVITY_TRANSACTION:
       {
       ...
           int result = startActivity(app, callingPackage, intent, resolvedType,
                   resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
           reply.writeNoException();
           reply.writeInt(result);
           return true;
       }
   }

onTransact中会调用AMS的startActivity方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Override
 public final int startActivity(IApplicationThread caller, String callingPackage,
         Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
         int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
     return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
             resultWho, requestCode, startFlags, profilerInfo, bOptions,
             UserHandle.getCallingUserId());
 }

startActivity方法会最后return startActivityAsUser方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Override
 public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
         Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
         int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
     enforceNotIsolatedCaller("startActivity");
     userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
             userId, false, ALLOW_FULL_ONLY, "startActivity", null);
     return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
             resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
             profilerInfo, null, null, bOptions, false, userId, null, null);
  }           

startActivityAsUser方法最后会return ActivityStarter的startActivityMayWait方法,这一调用过程已经脱离了本节要讲的AMS家族,因此这里不做介绍了,具体的调用过程可以查看Android深入四大组件(一)应用程序启动过程(后篇)这篇文章。

在Activity的启动过程中提到了AMP、AMN和AMS,它们共同组成了AMS家族的主要部分,如下图所示。

AMS家族(7).png

AMP是AMN的内部类,它们都实现了IActivityManager接口,这样它们就可以实现代理模式,具体来讲是远程代理:AMP和AMN是运行在两个进程的,AMP是Client端,AMN则是Server端,而Server端中具体的功能都是由AMN的子类AMS来实现的,因此,AMP就是AMS在Client端的代理类。AMN又实现了Binder类,这样AMP可以和AMS就可以通过Binder来进行进程间通信。

ActivityManager通过AMN的getDefault方法得到AMP,通过AMP就可以和AMN进行通信,也就是间接的与AMS进行通信。除了ActivityManager,其他想要与AMS进行通信的类都需要通过AMP,如下图所示。

参考资料
[深入理解Android卷二 全文-第六章]深入理解ActivityManagerService
Framework源码分析(一):ActivityManagerService
ActivityManager与Proxy模式的运用

作者:itachi85 发表于2017/7/31 0:09:44 原文链接
阅读:25 评论:0 查看评论

Android逆向之旅---Native层的Hook神器Cydia Substrate使用详解

$
0
0

一、前言

在之前已经介绍过了Android中一款hook神器Xposed,那个框架使用非常简单,方法也就那几个,其实最主要的是我们如何找到一个想要hook的应用的那个突破点。需要逆向分析app即可。不了解Xposed框架的同学可以查看:Android中hook神器Xposed使用详解;关于hook使用以及原理不多解释了。今天我们再来看另外一个hook神器Cydia Substrate,关于这个神器网上也已经介绍了,应该有的同学已经使用过了,因为最近在破解一个游戏,奈何想hook他的一个so中的一个方法,Xposed几乎没法用,所以得操刀这个框架了,所以就单独抽出来介绍这个框架。他的一个优点就在于Hook底层方法非常方便,对so中的方法hook操作非常便捷。


二、环境搭建

下面就来介绍这个框架如何安装使用,本文会介绍这个框架如何hook Java层和Native层功能,首先我们来看一下如何安装这个框架,本人操作的环境:

设备系统:小米三+原生CM 4.4系统

框架版本:0.9.4010

是否root:必须root

关于这个环境,可能有的同学操作最大的问题就在于设备和系统,不同设备不同系统,这个框架或许安装会失败,具体问题可能需要你们自己去解决了。关于框架apk和功能jar包下载地址可以去官网:http://www.cydiasubstrate.com


三、Hook Java层功能

搭建好了环境,下面就直接操作了,首先来看看如何Hook Java层功能

第一步:导入jar包



第二步:编写hook入口类


具体api这里不多介绍了,就那么几个,没必要详细介绍,这里对系统的imei进行hook操作了。网上很多人都对系统颜色值进行了hook,这里也顺带操作一下:



第三步:配置xml信息


在AndroidManifest.xml中需要配置两个地方,一个是使用权限,一个是声明hook的入口类即可。


第四步:安装运行

代码编写完成之后,直接运行安装即可,前提是你需要正确安装Cydia框架apk,安装成功界面如下:


然后我们安装hook工程apk,会出现这个提示:


点击,进入框架界面,点击重启即可。然后我们查看系统界面颜色以及返回的imei值:

  

查看颜色的确变成骚气的粉色了,再看看imei值的修改:


imei值也成功的hook成功了。到这里我们就用Substrate框架hook了Java层功能。当然这些功能Xposed也是可以做到的哦。


四、Hook Native层功能

那么下面继续来看如何hook native层的功能,也是本文的重点哦。

第一步:创建一个Native工程


这里用Eclipse操作,简单便捷,有很多人问我为什么不用AS,我想说在我心中Eclipse最好用,AS真心好丑,不想用而已。


第二步:导入Substrate的native功能包

上图可以看到,需要导入一个substrate.h头文件,和两个so功能包。native层应用都是这么干的,提供一个头文件告诉你api,具体实现在so包中。


第三步:寻找hook的函数名

这里网上没有好的hook代码,这里我们为了更好的查看这个工具的牛逼之处,弄一个比较实际的案例就是hook系统加载dex的函数,这样我们就可以获取到每个应用的dex文件了,这种方式对于早期加固是一个比较好的脱壳方案。在之前介绍脱壳我们会使用IDA在指定函数处下个断点,那么我们这里如果要hook的话,就需要找到这个加载dex的函数名称,这里一定要记的是导出的函数名,首先我们导出设备的libdvm.so文件:system/lib/libdvm.so


然后使用IDA打开,寻找加载dex函数:


切换到Exports视图页面,然后搜索dexFileParse函数,点进去:


看到了,我们需要得到的是EXPORT的函数名,需要hook的是他,这个一定要注意,不然hook没效果的。找到函数之后还得获悉函数的参数类型和返回类型,这个也好办,因为我们有Android源码,所以直接在源码中找这个函数参数说明已经返回值说明即可。因为Native层hook的其实是函数指针的替换,所以如果想hook原来的函数,必须新建一个和原来一样的函数功能,然后传递函数指针即可。这个函数的参数和返回值定义如下:

DexFile* dexFileParse(const unsigned __int8 *, unsigned int, int);

参数含义非常简单,第一个参数表示dex文件的起始地址,第二个参数是dex文件的长度,有这两个参数我们就可以写入文件了。这里我们需要获取DexFile类型,这个直接在Android源码目录下找到这个头文件DexFile.h即可。然后导入工程中。这样我们就找到了需要hook的函数已经说明了,下面就开始编写hook代码了。


第四步:编写hook代码

在编写hook代码之前,我们需要考虑这几件事:

第一件事:我们hook之后的dex存在哪?怎么存?我们这里直接通过当前的pid值获取进程名,然后将其凭借作为dex的文件名,这样每个进程的dex文件就不会冲突了。这里要理解一点:一个进程对应一个DVM,加载一个dex文件。所以这里hook其实就是注入每个进程,在每个进程中在hook每个函数功能。

第二件事:需要过滤系统进程,并不是所有的进程都是我们想要hook的,而且这些进程未必有dex文件,比如鼻祖进程zygote,而这些进程过滤规则,需要我们自己打印看结果。然后构造。

下面开始写代码了,首先定义我们想要hook的so文件:

MSConfig(MSFilterLibrary, "/system/lib/libdvm.so");

主要是第二个参数,是需要hook的so路径。然后在入口处开始hook代码:


这里首先找到so中需要hook函数符号,然后直接调用MSHookFunction传入符号,新函数地址,旧函数地址即可。这里可以看到在C中指针是多么强大,实现了函数的回调机制,而且非常方便。然后继续来看新定义的hook函数功能:


这里先获取当前进程名称,然后构造dex文件名,保存dex文件,最后一定要记得返回原始的函数,不能影响正常的流程。这里还要记得过滤规则,不要对每个进程都进行操作,并不是每个进程都是有效的。而这些过滤规则是根据自己打印进程名来自行添加即可。


第五步:编写MK文件

上面代码已经编写完成了,下面来编写编译脚本吧,主要注意编译之后的文件名一定要有cy结尾,不然是hook失败的,然后就是需要导入substrate的so库文件:



第六步:安装并运行

和之前一样,运行之后,需要重启设备,然后先看看native层的log信息:


然后再去目录中查看保存的dex文件信息:


dex文件都保存成功了,这样会发现如果对于早期的加壳,可以采用这种方式进行脱壳操作的。也不需要用IDA进行调试dump出dex文件了。


五、说明

关于native层hook就介绍完了,这里还是需要说明几点:

第一点:hook之前需要分析so获取需要hook的函数名称,参数返回值定义,这个和hook Java层一样,必须先找到突破点才能进行下一步。

第二点:hook可能会有一些错误,因为是native层比java层错误信息难发现,所以最好是在某些地方加一些日志观察结果。

如果在使用过程中发现hook失败,注意检查这几个条件:

第一个:xml中是否配置了权限和入口

第二个:编译脚本MK中的后缀名是否为cy


项目下载地址:https://github.com/fourbrother/CydiaSubstrateHook


六、总结

关于CydiaSubstrate框架就介绍到这里了,后面会分析如何hook游戏的so文件来进行破解工作,有了这个框架再也不怕hook难了,native层代码也可以一览无余了。


更多内容:点击这里

关注微信公众号,最新技术干货实时推送

编码美丽技术圈
微信扫一扫进入我的"技术圈"世界

扫一扫加小编微信
添加时请注明:“编码美丽”非常感谢!

作者:jiangwei0910410003 发表于2017/7/31 8:36:41 原文链接
阅读:118 评论:0 查看评论

Android6.0之后的权限机制对App开发的影响

$
0
0
随着Android系统的更新换代,每次重大更新的方面也逐步扩展,从4.*主要是增强功能,到5.*主要是美化界面,到6.*主要提高系统安全性,再到7.*和8.*主要支撑各种大屏设备,因此开发者需要对每个大版本的Android重新进行适配。其中6.*主要影响开发工作的升级包括权限管理和休眠模式。

对于权限管理,原本开发者只要在AndroidManifest.xml中声明相关权限,App安装完成之后即可默认获得这些权限。但是6.0引入了新的运行时权限管理机制,即使开发者实现已经声明App的权限,Android在App初次启动之时,仍会提示用户是否允许该App开启相关功能。倘若用户不同意App获得某些权限,毫无疑问App在运行过程中就可能无法正常工作。

对于休眠模式,即当手机屏幕关闭的时候,系统会自动进入休眠模式,这样原本正在运行的App将进入挂起模式,不能再进行访问网络等常用操作。当然为了保证App不被完全挂死,系统也会定时退出休眠模式,好比青蛙从冬眠之中苏醒过来,在苏醒期间,系统允许挂起的App重新恢复运行,继续先前设定好的任务。可是这个苏醒期是短暂的(通常只有几秒),一旦苏醒期结束,系统又重新进入休眠模式,于是那些App再次挂起,等待下次苏醒期的到来,如此往复。当然,只要手机恢复亮屏,比如用户按下电源键、用户给手机插上电源、手机接到来电等等,系统便自动退出休眠模式,所有挂起的App都会恢复正常运转。

下面逐个说明一下Android6.0的权限管理和休眠模式给App开发带来的影响,注意这些影响可对照《Android Studio开发实战:从零基础到App上线》一书的相应章节:
1、App的SD卡访问权限可能会被用户关闭,导致App无法正常读写SD卡。这点影响《Android Studio开发实战:从零基础到App上线》一书第4章的“4.3 SD卡文件操作”和“4.5 实战项目:购物车”。手机上查看App是否开启存储卡访问功能的界面如下图所示:


2、手机在休眠期间,原本在系统闹钟服务AlarmManager中设定好的定时任务,即使定时的时刻到达,也要等到苏醒期间才会得到执行。如果一定要在休眠期唤醒闹钟,就得调用setAndAllowWhileIdle代替set方法,或者调用setExactAndAllowWhileIdle代替setExact方法。
这点影响《Android Studio开发实战:从零基础到App上线》一书第5章的“5.5.2 定时器AlarmManager”和“5.6 实战项目:日历/日程表”。

3、Android6.0之后,App如果通过jni接口调用了so库,则这个so库必须把相关库编译进去,因为系统将不允许App访问系统库。具体到编码工作,只能在build.gradle中采用externalNativeBuild方式编译jni工程,即在jni目录下提供专门的Android.mk编译文件,由该mk文件配置jni文件的编译规则;倘若在build.gradle的defaultConfig节点下补充ndk的编译说明,则编译出来的so文件无法在Android6.0上正常调用。这点影响《Android Studio开发实战:从零基础到App上线》一书第14章的“14.2 JNI开发”和“14.4 实战项目:WIFI共享器”。

4、Android6.0对蓝牙功能的校验也变严格了,即使已经在AndroidManifest.xml声明蓝牙的相关权限如BLUETOOTH_ADMIN和BLUETOOTH,App也不能正常检测到附近的蓝牙设备。解决办法是在AndroidManifest.xml中增加下面两个权限声明:
    <!-- Android6.0 蓝牙搜索不到设备,需要补充下面两个权限 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
这点影响《Android Studio开发实战:从零基础到App上线》一书第14章的“14.3.2 蓝牙BlueTooth”。



点此查看Android开发笔记的完整目录



作者:aqi00 发表于2017/7/31 9:53:34 原文链接
阅读:0 评论:0 查看评论

android应用开发-从设计到实现 4-5资源的导入

$
0
0

资源的导入

在前面讲解产品原型设计的章节中,我们已经创建出了各种开发时需要使用的各种图标了,包括应用图标、系统图标等等。

图标导入

现在,我们先把开发过程中要使用到的图片资源添加到Android Studio工程目录当中。

  1. Sketch创建的静态高保真原型中的图标,根据导出的尺寸,将它们分类整理到对应的文件夹当中,

     4_5_drawable_resource_scale
  2. 打开天气预报所在的Android Studio的工程文件夹-app -> src -> main -> res,将图片替换到对应的mipmap目录当中;

     import_icons
  3. 重新部署应用到设备上,桌面上的天气预报应用图标,已经从以前的安卓机器人,变成了新的图标了,

     app_icon_changed

版本管理

我们新增了约90个图片资源,需要将它们加入到Git的版本控制之中。

  1. 查看Version Control: -> Local Changes -> browse;
  2. 在弹出的对话框中,选中需要加入版本控制的90个图片文件,点击左边的+

     icon_resources_add_to_git
  3. 需要管理的文件被添加到了stage当中;

     icon_resources_add_to_git_2
  4. 之后再把这些改动,按照上一张讲到提交到本地库和远程GitHub当中的方式进行操作就可以了;

     icon_resources_commit

图片的使用

对于那些导入的图片资源,Android Studio会做进一步处理,自动生成一个R.java文件,文件中给每一张图片生成一个对应的id值。

public final class R {

    public static final class mipmap {
        public static final int ic_air_quality=0x7f030000;
        public static final int ic_cloudy_l=0x7f030001;
        public static final int ic_cloudy_s=0x7f030002;
        public static final int ic_fog_l=0x7f030003;
        public static final int ic_fog_s=0x7f030004;
        public static final int ic_humidity_level=0x7f030005;
        public static final int ic_launcher=0x7f030006;
        public static final int ic_rainy_l=0x7f030007;
        public static final int ic_rainy_s=0x7f030008;
        public static final int ic_snow_l=0x7f030009;
        public static final int ic_snow_s=0x7f03000a;
        public static final int ic_sport_level=0x7f03000b;
        public static final int ic_sunny_cloudy_l=0x7f03000c;
        public static final int ic_sunny_cloudy_s=0x7f03000d;
        public static final int ic_sunny_l=0x7f03000e;
        public static final int ic_sunny_s=0x7f03000f;
        public static final int ic_ultraviolet_level=0x7f030010;
        public static final int ic_wind_direction=0x7f030011;
        public static final int ic_wind_level=0x7f030012;
    }

}

当我们要使用对应资源的时候,直接使用java代码

int id = R.mipmap.ic_launcher;

就可以了。

通过这种方式,完成了资源代码的转换。

资源的条件命名法

图片资源文件夹的命名方式很有特点,在主名字之后,使用-间隔了一些扩展名字。例如,

mipmap-hdpi mipmap-xhdpi

目录名用“-”增加了限制条件。也就是给目录里资源的使用增加了约束。

这里用mipmap-hdpi文件夹举例说明:如果当前的设备属于hdpi类型的屏幕,那么在使用图片的时候,系统优先使用其中的图片。

除了上面的目录结构,有时你还会看到如下的目录形式:

  1. mipmap-hdpi:给属于hdpi的设备使用;
  2. values-zh:给当前语言是中文的系统使用;
  3. layout-land:给处于横屏状态的系统使用;

图片的优化

屏幕密度选择性匹配

并不是说一定要为所有的屏幕密度做对应图片尺寸的匹配,因为如果做全部匹配会让程序的体积快速增大。

大多数时候,我们会选择先做高清晰度的尺寸,例如xxhdpi。其他屏幕密度的设备需要使用资源时,就会把xxhdpi中的资源,做适度的缩放,以达到匹配的效果。

对于个别系统缩放也达不到显示要求的图片,才做针对不同屏幕密度的配套图片。

我们的天气预报把所有尺寸的屏幕密度都做了适配。

矢量图标

另外,对于很多系统图标,我们也可以不使用png图片,而采用矢量图,让安卓系统绘制图标。这样一来,程序的体积可以跟小,显示效果也更为出众。

不过大家初次接触安卓开发,为了减少不必要的枝节,我就隐去了这部分内容,将来再和大家详细的讲述。


本文是《从设计到实现-手把手教你做android应用开发》系列文档中的一篇。感谢您的阅读和反馈,对本文有任何的意见和建议请留言,我都会尽量一一回复。

如果您觉得本文对你有帮助,请推荐给更多的朋友;或者加入我们的QQ群348702074和更多的小伙伴一起讨论;也希望大家能给我出出主意,让这些文档能讲的更好,能最大化的帮助到希望学习开发的伙伴们。

除了CSDN发布的文章,本系列最新的文章将会首先发布到我的专属博客book.anddle.com。大家可以去那里先睹为快。


同时也欢迎您光顾我们在淘宝的网店安豆的杂货铺。店中的积木可以搭配成智能LED灯,相关的配套文档也可以在这里看到。

这些相关硬件都由我们为您把关购买,为大家节省选择的精力与时间。同时也感谢大家对我们这些码农的支持。

最后再次感谢各位读者对安豆的支持,谢谢:)

作者:anddlecn 发表于2017/7/31 10:46:53 原文链接
阅读:0 评论:0 查看评论

解决ListView显示不全、滑动冲突问题

$
0
0

    在开发中,ListView是使用得最多的控件,有时候会遇到ListView因为各种原因引起的数据显示不全,例如,嵌套了ScrollView等。这个解决的办法很多,这里记录一下我自己用过的方法,本人亲测有效。
第一种:
/**
* 设置listview高度的方法
* @param listView
*/
public void setListViewHeight(ListView listView) {
//获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {//listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView); //获得每个子item的视图
listItem.measure(0, 0); //先判断写入的widthMeasureSpec和heightMeasureSpec是否和当前的值相等,如果不等,重新调用onMeasure(),计算子项View 的宽高
totalHeight += listItem.getMeasuredHeight(); //累加不解释,统计所有子项的总高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); //加上每个item之间的距离,listView.getDividerHeight()获取子项间分隔符占用的高度
listView.setLayoutParams(params);//params.height最后得到整个ListView完整显示需要的高度
}
这个方法需要在ListView.setAdapter(adapter)之后调用,这样就可以把数据显示完全了,这个方法可以写到工具类里面,然后直接调用。

第二种方法:
/*
* 設置listview的高度結局滑動衝突
*
* @param statisticsTO
*/
public void setListvHeight(StatisticsTO statisticsTO) {
int size = statisticsTO.getData().size();
int totalHeight = 0;
for (int i = 0; i < size; i++) {
View listItem = myAdapter.getView(i, null,
liststatistics);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = liststatistics.getLayoutParams();
params.height = totalHeight
+ (liststatistics.getDividerHeight() * (size - 1));
liststatistics.setLayoutParams(params);
}
这种方法其实和第一种方法是类似的,只不过这里传入的是数据的实体。

第三种方法:
第三种方法就是自定义ListView,然后重写onMeasure方法,这个可以解决ListView的滑动冲突,百度上说的最多的就是这种方法。
public class CustomListView extends ListView{
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, spec);
}
}

转载自 :http://blog.csdn.net/u014727709/article/details/76423318
欢迎start,欢迎评论,欢迎指正

作者:u014727709 发表于2017/7/31 11:40:43 原文链接
阅读:2 评论:0 查看评论

每日一文简单版

$
0
0

1. 简介

前段时间学习Kotlin,准备找个东西下手,复杂的内容暂时也处理不了,所以选了“每日一文”的接口作为测试,本以为内容比较少,很容易实现,最后发现,还是有一些功能不是很好处理,所以到目前为止还只是一个半成品,后面有空了再来改善吧。现在只是为了练习Kotlin。


2. 内容

  • Kotlin基本语法
  • anko-common使用
  • anko-sqlite使用
  • Navigation+DrawerLayout实现左右抽屉菜单
  • Retrofit + Okhttp + Rxjava2 + Gson 处理网络请求
  • Material Design

3. 思路

启用开发者模式,打开“显示边界布局”,你可以看到应用唯一的一个主界面采用是一体的,没有控件的分离,猜测采用的是混合式开发,所以呢,对我们没有什么帮助。只能按照自己的方式,实现它的功能。毕竟是为了熟悉语言,不深究。


4. 依赖库

    ext.kotlin_version = '1.1.2-5'
    ext.anko_version = '0.10.0'
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.anko:anko-common:$anko_version"
    compile "org.jetbrains.anko:anko-sqlite:$anko_version"
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.android.support:support-v13:25.3.1'
    compile 'com.google.code.gson:gson:2.7'
    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.x.y'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.android.support:support-vector-drawable:25.3.1'
    compile 'info.hoang8f:android-segmented:1.0.6'
    compile 'com.kyleduo.switchbutton:library:1.4.6'

5. 效果

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述


6. Github

OneArticle


7. 备注

每日一文接口采用非正式方式获取,仅供学习,若有侵权请告知,即刻删除。

作者:poorkick 发表于2017/7/31 13:48:23 原文链接
阅读:3 评论:0 查看评论

APP界面框架初窥---标签导航

$
0
0

这里写图片描述


标签导航是十大界面框架设计里最常用的界面框架设计,也是被业界之内公认的一种普遍使用的页面框架设计。那么这种页面框架设计在作业方面对一个用户来说也是最常见的一种页面框架设计,比如说微博、微信、手机百度、支付宝、淘宝,这些我们所谓的超级APP都是运用的标签导航,无一例外。

这里写图片描述

从这个角度也可以看出来,优秀的产品用标签导航这种页面框架设计是非常普及的。标签导航位于页面底部,标签的分类最好可以控制在5个之内。


使用这种框架的优点在于:

1.标签导航能够让用户清楚当前所在的入口位置。比如对于微信来说,无论用户在“发现”还是“对话框”里面,用户都能通过底部的高亮区域来划分当前所处的这个产品结构的区域。无论是当前位置的判断,还是要找这个入口,都比较方便,比如对于微信来说,很容易都过标签导航找到“朋友圈”。

这里写图片描述

2.轻松在各入口间频繁跳转且不会迷失方向。比如对于微信来说,微信团队不仅希望我们拿微信来聊天,还希望我们拿微信来逛朋友圈、购物、支付、滴滴打车等等,那么如果能够让用户在不同的入口间实现频繁的跳转,那这时用标签导航是最合适不过的。

这里写图片描述

3.直接展现最重要入口的内容信息。这有两层意思,第一层就是它能展示出来最重要的入口,比如拿微信来说有那么多的重要入口,显然“微信对话框”最重要,那么他们默认的把微信对话框作为主入口。同样微博最重要的是首页,所以默认把微博首页作为最主要的入口。其次,入口不仅可以展示,入口里面的信息也可以展示。

但是这种模式的缺点也是存在的:

功能入口过多时,该模式显得笨重不实用。怎么理解“功能模块过多”,比如说现在标签导航,一般情况下功能入口控制在5个以内,我们也会遇到6个的情况,但那种产品一般来说比较复杂,最少会是3个,最多5到6个。如果过多的话,标签导航会弹不开,那这种模式就失效了。过多不行,过少也不太方便,如果说就一个Tab、两个Tab,那么标签导航下面就会显得特别的空,也不太好看,所以这个时候我们就会知道标签导航的适用范围最好在3至5 个之间,并且这几个功能希望用户能够频繁的去操作。

好了,理论知识就普及到这边,下一篇我就来说说如何用代码来具体实现”标签导航”,效果如下:

这里写图片描述


好了。祝大家生活愉快。多多收获友谊和爱情。如果想获取更多的讯息,请扫描下方二维码关注我的微信公众号:

这里写图片描述

作者:shenjie12345678 发表于2017/7/30 21:26:17 原文链接
阅读:5 评论:0 查看评论

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 原文链接
阅读:179 评论:0 查看评论

历时2年《爱上Android》出版了

$
0
0

之前一直在百度阅读维护的书 《爱上Android》,已经由人民邮电出版社出版。起这个书名的主要原因是我非常喜爱从事开发Android程序的事业,也非常热爱分享技术。

心理专家研究过人心理周期的变化,结论证实大部分人连续、专注的干一件事的时间很难超过18个月。当你能坚持一件事做18个月的时候证明你至少战胜了百分之九十的人。

很庆幸的是,我成为了剩余百分之十的人,坚持了两年去完成这一本书。

在此期间,我的角色也升级成了一位父亲,我真的非常感谢我的妻子。写书过程中占用了很多本应是陪伴她的时间,但她毫无怨言,一直都在默默地支持我,才使得我能专心的投入我的工作以及写作中。可以毫不夸张的说,没有我妻子就没有这本书的面世。

本书主要特色

1.坚持更新知识点

互联网技术发展一日千里。写作的同时Android版本也在更新,两年期间,Android 从6.0 预览版到 8.0 预览版。

为了保证知识不过期,写过的知识点我反复更新了好几个版本,甚至开始印刷前都在更新——今年5月底,Google官方认可 Kotlin 开发 Android,当时书也马上开始印刷,我第一时间联系出版社,专门加了一节使用 Kotlin 开发 Android。

不仅限于知识点更新,包括一些细节,比如封皮设计都仔细推敲:

Android Studio 3.0 图标颜色换了,不再是传统的安卓绿,而变成了醒目的黄色。为此我专门和出版社编辑沟通,把本书的封皮换成相近的颜色。

2.录制视频

我一直认为看书学习的效率要比看视频快,但是刚入门的时候,看视频更加直观一些。

为了满足新手希望能快捷、方便入门的心理,我专门针对刚开始的几章录制了几十段视频。为了保证视频的录制质量,专门高价购买了外置声卡和话筒。

视频绝不是照着书里的文字念,而是把写代码的技巧、过程、思路都展现出来。

每录制一段十几分钟的视频,我都需要花费将近一天的时间去准备,录制效果差强人意的直接删掉,重新进行录制。

本书主要内容

书里的基本内容主要包括,开发环境搭建,界面搭建,Activity知识,数据存储,网络编程,图片处理,复杂控件,Fragment,广播接受者,服务,动画。除了这些还介绍了开发必不可少的技能,包括屏幕适配,性能优化,自定义控件和NDK编程。 最后一章通过一个完整的新闻客户端项目整合了前面的知识点。

这绝不是一本中庸的书,里面每个知识点都强调实用二字

撰写这本书的过程中,我倾尽了我的热情与脑力,对每个知识点都深入剖析,既没有盲目罗列一大段代码留给读者自己慢慢看的情况,也不会出现把所有的知识点都堆到一起让读者自行领会的消极态度,而是循序渐进的讲述开发过程中必备的知识点,使读者能在将来的实际开发过程中运用到很多。

预售

现在《爱上Android》已经在京东预售,欢迎购买。地址:

京东-爱上Android

说实在话,写书真不是为了赚钱,尤其是技术性书籍,写这本书还是希望自己能够帮助到一些人。

大家看了这本书之后,要是觉得写得好,可以帮忙宣传宣传;要是觉得写的不好,在博客或者公众号里留言,请指出不足之处,我会努力改正。

谢谢大家!

作者:yulianlin 发表于2017/7/31 17:21:19 原文链接
阅读:813 评论:5 查看评论

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 原文链接
阅读:143 评论: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 原文链接
阅读:177 评论: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 原文链接
阅读:234 评论: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 原文链接
阅读:29434 评论: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 原文链接
阅读:36905 评论: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 原文链接
阅读:32477 评论: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 原文链接
阅读:32404 评论: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 原文链接
阅读:28019 评论: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 原文链接
阅读:30279 评论: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 原文链接
阅读:34961 评论:19 查看评论

6、volley 源码解析之工作流程综述

$
0
0

文章摘要
1、volley 中的工作线程
2、volley 工作步骤
3、RequestQueue初始化以及初始化逻辑


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

感悟:

Volley的源代码真的值得去读,其中包含了很多非常好的处理逻辑,例如:Volley对其工作流程的架构、线程缓存的处理、网络请求数据的解析以及处理、其中用到的设计模式等。

当我们明确了volley要解决的开发痛点后,volley提供了松耦合的架构实现,我们可以很方便的在其框架实现内,为其进行功能扩展。引用设计模式的一句话:一切为了松耦合的设计而努力。

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

1、volley 中,存在三类线程:

  • 1、主线程。将获取的数据刷新到UI,发送网络请求等。
  • 2、缓存进程(Cache Thread)。管理缓存数据,主线程的请求,优先从主线程中获取数据,其次将请求分发到网络中。
  • 3、网络线程(Network Thread)。用户可以制定网络线程的数量,默认是4个。定义了从网络获取数据的流程,但不包括网络同步逻辑。这样子的设计,可以更好的释放线程和网络解析逻辑之间的耦合。
for (int i = 0; i < DEFAULT_NETWORK_THREAD_POOL_SIZE; i++) {
    NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
      mCache, mDelivery);
    mDispatchers[i] = networkDispatcher;
    networkDispatcher.start();
}

2、volley 工作步骤

之前的Demos中发送一个简单的请求,无论是通过RequestQueue还是通过ImageLoader,大致的使用步骤是一样的,大体包括以下步骤:

  • 2.1、初始化RequestQueue或者ImageLoader。
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
  • 2.2、初始化Request
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
  • 2.3、 将Request 加入到RequestQueue。
// Add the request to the RequestQueue.
queue.add(stringRequest);

后两个步骤,是对RequestQueue的使用,所以,我们将重点放在第一部分,即:RequestQueue的创建流程。

3、RequestQueue初始化

RequestQueue会对Request进行管理,它的作用,更多的是作为一个工具类,通过add方法,将Request分发给Cache线程以及网络线程。

  • 3.1、首先通过newRequestQueue方法获得RequestQueue对象。
    public static RequestQueue newRequestQueue(Context context) {
        //1、初始化Network对象。Network的意义是通过performRequest方法解析Request,生成Response对象。
        HttpStack stack;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        Network network = new BasicNetwork(stack);
        //2、初始化RequestQueue,共计两个参数:第一个参数是Cache缓冲区,可选,即:可不缓冲。
        //第二个参数是network对象,也是关系解析网络数据的主要劳工。
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

Cache类图
- 3.2、RequestQueue的实例初始化,调用start后,线程会跑起来。

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *创建一个工作池,非start方法调用前,不会开始执行
     * @param cache A Cache to use for persisting responses to disk
     * 1、缓存者。将相应数据持久化到硬盘
     * @param network A Network interface for performing HTTP requests
     * 2、网络处理者。处理HTTP请求的Network 接口
     * @param threadPoolSize Number of network dispatcher threads to create
     * 3、网络请求分发者。默认4个分发线程池
     * @param delivery A ResponseDelivery interface for posting responses and errors
     * 4、响应传递者。运行在主线程,传递响应数据,以及错误日志信息,实现来自:Handler的封装
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

4、RequestQueue将Request抛给工作线程

RequestQueue通过add方法,将Request抛给工作线程,工作线程包含我们上面说的Cache线程、Network线程。

//1、将Request加入到mCurrentRequests,因为本工具类还需要管理Request,例如:cancel等。
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
    mCurrentRequests.add(request);
}

// Process requests in the order they are added.
//2、包装Request。为其赋予SequenceNumber
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");

// If the request is uncacheable, skip the cache queue and go straight to the network.
//3、如果Request不需要缓存,则直接加入到Network线程
if (!request.shouldCache()) {
    mNetworkQueue.add(request);
    return request;
}

// Insert request into stage if there's already a request with the same cache key in flight.
//4、如果请求(Request)不是在等待中(即:mWaitingRequests中),那么就先抛给缓冲线程(mCacheQueue)。
synchronized (mWaitingRequests) {
    String cacheKey = request.getCacheKey();
    if (mWaitingRequests.containsKey(cacheKey)) {
        // There is already a request in flight. Queue up.
        Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
        if (stagedRequests == null) {
            stagedRequests = new LinkedList<>();
        }
        stagedRequests.add(request);
        mWaitingRequests.put(cacheKey, stagedRequests);
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
        }
    } else {
        // Insert 'null' queue for this cacheKey, indicating there is now a request in
        // flight.
        mWaitingRequests.put(cacheKey, null);
        mCacheQueue.add(request);
    }

缓冲区(RequestQueue)在获得Request后,接着如何处理,请关注:volley 源码解析之缓存线程工作流程


关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:26:12 原文链接
阅读:64 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


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