Cronet的基本用法
我们从一段代码来开始我们对Cronet android设计与实现的探索,这段代码向我们展示要如何使用Cronet为android提供的Java接口来做HTTP请求。
Cronet中主要通过CronetEngine
来处理网络请求。这里我们专门创建一个class CronetUtils
来管理CronetEngine
对象,来处理CronetEngine
对象的创建,HTTP请求的提交等:
CronetEngine
是Cronet中资源管理的中心,这些资源包括用于异步执行各种网络请求的线程池,连接池等等。CronetEngine
本身对于对象的创建没有施加太多的限制,但为了资源的使用效率及性能考虑,我们在这里通过将CronetUtils
设计为单例,进而控制CronetEngine
对象的创建为最多一个。
CronetEngine
的对象需要通过CronetEngine.Builder
来创建,我们首先创建CronetEngine.Builder
的对象,然后为Builder设置我们希望Cronet所具有的特性,如开启HTTP2,开启QUIC,开启缓存,设置cornet的so文件的文件名等,然后调用builder.build()来创建对象。
提交网络请求则是,借助于UrlRequest.Builder创建一个UrlRequest,传入url,Executor,callback和CronetEngine等,然后执行UrlRequest.start()向Cronet提交HTTP请求。后续在HTTP请求的执行过程中遇到的事件,会通过callback传递给Cronet的客户端。CronetEngine
的实现及具体的职责,以及Executor的作用,我们会在后面做详细分析。
而在我们的应用程序中需要实现Callback,用以处理HTTP请求执行过程中遇到的事件,获取执行HTTP请求所得的响应等。一个简单的示例Callback如下:
我们的Callback通过扩展UrlRequest.Callback来实现,它会被传递给Cronet,其中的方法会在适当的时机被调用。回调方法大概有如下这些:
onRedirectReceived():当收到一个重定向的响应时,这个回调方法会被调用。这个方法会在UrlRequest.start()被调用之后,而在UrlRequest.Callback.onResponseStart()之前被调用。重定向的响应的body,如果存在的话,将被忽略。通常在这个回调方法中,我们需要调用request.followRedirect(),以follow重定向。否则Cronet不会自动follow重定向。
onResponseStarted():在所有的重定向都处理完了,且http响应的header都接收完,需要读取response的body时这个方法会被调用。一个请求的整个处理过程中,这个方法只会被调用一次。在这个方法中需要分配ByteBuffer,以用于http response body的读取过程。Response的body内容会首先被读取到这里分配的ByteBuffer中。
onReadCompleted():http response body读取完成,或者ByteBuffer读满的时候,这个回调方法会被调用。在这个回调方法中,需要将ByteBuffer中的数据copy出来,清空ByteBuffer,然后重新启动读取。这里的回调方法实现,是将ByteBuffer的内容copy出来,借助于WritableByteChannel放进ByteArrayOutputStream中。
onSucceeded()和onFailed():这两个回调方法分别在http请求执行完全成功时,和失败时调用。在这两个方法被调用之后,将不会再有其它的回调方法被调用。
onCanceled():这是UrlRequest.Callback的另一个方法,尽管在我们的回调实现中没有实现这个方法。这个方法是在我们取消http请求执行时被调用。这个回调方法被调用之后将不会再有其它方法被调用。
总结一下,Cronet的使用方法如下:
- 创建CronetEngine对象。
- 实现UrlRequest.Callback类。
- 根据Url等http请求相关信息创建UrlRequest。
- 启动UrlRequest的执行。
CronetEngine对象的创建
CronetEngine对象需要通过CronetEngine.Builder来创建。这里我们来看CronetEngine.Builder.build()方法的实现(chromium/src/components/cronet/android/api/src/org/chromium/net/CronetEngine.java):
|
|
在这个方法中,主要做了如下这些事情:
- 没有为CronetEngine显式地设置UserAgent时,构造一个UserAgent。
- legacy模式是指,使用系统的HttpUrlConnection实现作为CronetEngine的Http stack,而不是使用chromium来执行网络请求。这个选项默认是关闭的。在不处于legacy模式时,会尝试调用CronetEngine.createCronetEngine()来创建CronetEngine对象,一个以chromium net为http stack的CronetEngine。
- 因为某些原因CronetEngine.createCronetEngine()调用失败,或处于legacy模式时,创建JavaCronetEngine,也就是以系统的HttpUrlConnection实现作为Http stack的CronetEngine。
- 将创建的CronetEngine返回给调用者。
CronetEngine.Builder的getDefaultUserAgent()的实现(chromium/src/components/cronet/android/api/src/org/chromium/net/CronetEngine.java)是这样的:
在UserAgent.from()定义了一套计算UserAgent的规则(chromium/src/components/cronet/android/api/src/org/chromium/net/UserAgent.java):
这个过程构造的UserAgent类似于下面这样:
而CronetEngine.createCronetEngine() 通过反射来创建 org.chromium.net.impl.CronetUrlRequestContext对象,也就是以chromium net为http stack的CronetEngine:
这里我们来看一下CronetUrlRequestContext的创建过程:
这个过程大体如下:
- 加载并初始化cronet库。
- 根据CronetEngine.Builder的用户设置创建NativeUrlRequestContextConfig。
- 利用前面创建的NativeUrlRequestContextConfig创建RequestContextAdapter。
- 在主线程中初始化RequestContextAdapter。
顺带简单地提一下CronetEngine的其它职责。除了创建CronetEngine实现对象外,CronetEngine还可以做这样的一些事情:
- 创建UrlRequest。
- 创建BidirectionalStream。
- 获取一些Metrics,network quality信息及trace信息,如rtt,吞吐量等。
- 创建URLConnection。
- 获取Request的一些trace信息。
加载并初始化cronet库
CronetLibraryLoader.ensureInitialized() (chromium/src/components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java)被用于加载并初始化cronet库:
加载主要是指动态链库so文件的加载,这个过程通常会在将so文件读入内存,解析引用的其它动态链接库或系统动态连接库的符号外,执行一些诸如native方法的注册等动作。而初始化则主要是在native方法nativeCronetInitOnMainThread()中初始化cronet native层的一些设施。
我们先来看so文件的加载。
Cronet的JNI_OnLoad
我们知道,通过System.loadLibrary()加载动态链接库so文件时,so库中的JNI_OnLoad方法会被执行,以完成Java的native方法的注册,及其它的一些初始化动作。Cronet的动态链接库so文件的JNI_OnLoad(chromium/src/components/cronet/android/cronet_jni.cc)定义如下:
这里是将职责完全委托给了cronet::CronetOnLoad,而后者的定义(chromium/src/components/cronet/android/cronet_library_loader.cc)为:
这里定义了一个静态的表,表中的每一项都描述了一个包含native方法的类的类名及该类注册native方法的函数。在RegisterJNI()函数中,通过base::android::RegisterNativeMethods函数执行表中每一个类的native方法注册函数来注册native方法。
而RegisterJNI()函数则借助于base::android::OnJNIOnLoadRegisterJNI()来执行。
此外,这里还会执行一些初始化函数。
每个类都通过编译期间自动为它们产生的定义于头文件中的RegisterNativesImpl()函数来注册native方法。如ChromiumUrlRequestContext的ChromiumUrlRequestContextRegisterJni():
而在RegisterNativesImpl()函数中,则通过JNIEnv的函数注册native方法,如CronetLibraryLoader的RegisterNativesImpl()函数:
总结一下,cronet动态连接库加载的过程为:
- 定义native方法注册函数表,其中每一个表项定义了一个类的类名,及该类的native方法注册函数。
- 通过base模块提供的util函数执行每一个类的native方法注册函数。
这个过程并没有什么特别的地方。比较特殊的是,native方法注册函数的定义方式,在cronet中,所有类的native方法注册函数,java的native方法的native层实现函数,及java native方法与native层的实现函数之间的对应关系表等,都是在构建期间动态产生的。这会给我们的代码阅读带来一点小小的障碍。
cronet库的初始化
nativeCronetInitOnMainThread()用于初始化cronet库,它是在构建过程中动态产生的CronetLibraryLoader_jni.h文件 (位于chromium_android/src/out/Default/gen/components/cronet/android/cronet_jni_headers/cronet/jni/CronetLibraryLoader_jni.h)中定义的。nativeCronetInitOnMainThread()会将所有的事务委托给CronetInitOnMainThread()(chromium_android/src/components/cronet/android/cronet_library_loader.cc),这个函数的定义为:
在这里主要是创建了MessageLoop。不过这里的MessageLoop又是用来做什么的呢?
创建并初始化RequestContextAdapter
回到CronetEngine的创建过程。在加载及初始化cronet动态链接库之后,会创建RequestContextAdapter。
RequestContextAdapter通过native方法nativeCreateRequestContextAdapter()创建,这个方法也是在构建期自动定义的,其实现位于chromium_android/src/out/Default/gen/components/cronet/android/cronet_jni_headers/cronet/jni/CronetUrlRequestContext_jni.h。这个函数将职责委托给CreateRequestContextAdapter()函数(chromium_android/src/components/cronet/android/cronet_url_request_context_adapter.cc),后者创建CronetURLRequestContextAdapter对象:
创建CronetURLRequestContextAdapter的过程主要是起了一个线程。这个线程又是用来做什么的呢?
接着继续来看org.chromium.net.impl.CronetUrlRequestContext中初始化的过程,也就是nativeInitRequestContextOnMainThread()方法:
这个方法调用CronetURLRequestContextAdapter的InitRequestContextOnMainThread()函数来执行初始化:
这个函数会创建proxy config service,初始化net log,然后抛一个task给URLRequestContextAdapter的network thread,task是CronetURLRequestContextAdapter::InitializeOnNetworkThread():
这里主要是通net::URLRequestContextBuilder,过创建了一个URLRequestContext对象,这个结构chromium net库的资源管理中心。
这里所做的具体的设置,我们目前先不管。