從Cronet 看Http3和QUIC(一)(上)

前言

前一段時(shí)間恕汇,在公司內(nèi)部進(jìn)行了一次QUIC協(xié)議的演講。當(dāng)時(shí)因?yàn)闀r(shí)間有限徙赢,沒有仔細(xì)的討論Cronet 的源碼細(xì)節(jié)赂弓,僅僅只是介紹了QUIC的協(xié)議細(xì)節(jié)。本文就從Cronet源碼出發(fā)孽鸡,聊聊QUIC的一些實(shí)現(xiàn)蹂午,進(jìn)而看看QUIC對比Http2的優(yōu)勢,解決了什么問題彬碱?

網(wǎng)上搜了不少Q(mào)UIC解析文章豆胸,不是太老就是粗略聊聊原理,沒有幾個(gè)真的深入源碼層面來證明說法是否正確堡妒,本文將主要根據(jù)QUIC最新的源碼來聊聊整個(gè)協(xié)議的設(shè)計(jì)配乱,以及這樣做的優(yōu)勢

正文

Http 發(fā)展史

在聊QUIC之前,我們需要對QUIC有一個(gè)初步的了解皮迟。QUIC本質(zhì)上是在Http之上進(jìn)一步的發(fā)展搬泥,而不是憑空出現(xiàn)的,而是為了解決Http之前的痛點(diǎn)誕生的伏尼。為此我們需要先了解http的發(fā)展忿檩,以及每一代比起上一代都解決了什么問題。

下圖是一個(gè)Http的發(fā)展進(jìn)程:

Http發(fā)展.png

在這個(gè)發(fā)展史中可以看到在Http2.0正式推出之前爆阶,Google就開始實(shí)驗(yàn)QUIC協(xié)議燥透。并在2018年在QUIC基礎(chǔ)上進(jìn)一步的發(fā)展出Http3協(xié)議。在2021年正式發(fā)布出QUIC協(xié)議辨图。

能看到Http 1到SPDY協(xié)議中間間隔10年時(shí)間班套,究竟是為什么在Http 2.0正式發(fā)布之前,就開始了Http3前身QUIC進(jìn)行實(shí)驗(yàn)了故河?那必然是很早就被Google發(fā)現(xiàn)了Http 2.0協(xié)議的有根本性的缺陷吱韭,無法被彌補(bǔ),需要立即實(shí)驗(yàn)下一代協(xié)議鱼的。

再來看看如今Http 2.0的全網(wǎng)使用情況:

Http2 全網(wǎng)占比.png

能看到目前Http 2.0全網(wǎng)使用率從發(fā)展到2022年2月份還是50%的使用率理盆,而在5月份就是驟降了5%。實(shí)際上都轉(zhuǎn)去了QUIC協(xié)議凑阶,如今已盡占比接近25%了猿规。那究竟有什么魅力,導(dǎo)致這么多開發(fā)者青睞QUIC協(xié)議呢宙橱?

帶著疑問姨俩,我們來簡單回顧一下每一代Http協(xié)議實(shí)現(xiàn)的功能蘸拔,以及缺點(diǎn)。

Http 1

在Http 1中奠定了Http協(xié)議的基本語義:

  • 由請求行/狀態(tài)行环葵,body和header 構(gòu)成 Http請求
Http請求協(xié)議結(jié)構(gòu).png
Http響應(yīng)協(xié)議結(jié)構(gòu).png

Http的缺點(diǎn)分為如下幾點(diǎn):

  • 1.header 編碼效率低:特別是Rest 架構(gòu)都伪,往往無狀態(tài)容易,沒有對Header進(jìn)行編碼壓縮

  • 2.多路復(fù)用成本過高

    • 慢啟動(dòng)
    • 一旦網(wǎng)絡(luò)發(fā)生異動(dòng)就需要重建連接积担,無論如何都需要3次握手陨晶,緩慢
  • 3.一旦長連接建立了就無法中斷

  • 4.Http 應(yīng)用層不支持流控

為了解決這些問題,就誕生出了Http 2.0協(xié)議帝璧。

Http 2.0

Http 2.0在Http 1.0基礎(chǔ)上實(shí)現(xiàn)了如下的功能:

  • 1.多路復(fù)用

    • 連接先誉,Stream級(jí)別流控
    • 帶權(quán)重,依賴優(yōu)先級(jí)
    • Stream Reset
    • 應(yīng)用層數(shù)據(jù)交換單位細(xì)化到Frame(幀)
  • 2.HPack 頭部編碼

  • 3.服務(wù)器消息推送

關(guān)于Http 2.0詳細(xì)的設(shè)計(jì)的烁,可以閱讀我之前寫的Okhttp源碼解析中的Http2Connection相關(guān)的源碼解析褐耳。里面有詳細(xì)剖析Okhttp是如何進(jìn)行Frame江湖,以及HPack是如何壓縮的渴庆,還有流是如何控制的铃芦。

Http 2.0的缺點(diǎn)如下:

  • 1.隊(duì)頭阻塞
網(wǎng)絡(luò)協(xié)議-Http2隊(duì)頭阻塞.png

因?yàn)镠ttp 2.0中使用的是多路復(fù)用的流模型,一個(gè)tcp鏈接的發(fā)送數(shù)據(jù)過程中可能會(huì)把一個(gè)個(gè)請求分割成多個(gè)流發(fā)送到服務(wù)器襟雷。刃滓,因?yàn)門cp的tls加密是一個(gè)Record的加密,也就是接近10stream大小進(jìn)行加密耸弄。如果其中在某一個(gè)流丟失了咧虎,整一串都會(huì)解密失敗。

這就是Http 2.0最為嚴(yán)重的隊(duì)頭阻塞問題计呈。

  • 2.建立連接速度緩慢砰诵,能看到整個(gè)過程都需要一個(gè)十分冗長的過程,三次握手捌显,tls密鑰交換等等茁彭。可以簡單看看https的建立鏈接過程:
Https通信模型.png
  • 3.基于TCP四元組確定一個(gè)鏈接扶歪,在移動(dòng)互聯(lián)網(wǎng)中表現(xiàn)不佳理肺。因?yàn)橐苿?dòng)設(shè)備經(jīng)常移動(dòng),可能在公交地鐵等地方击罪,出現(xiàn)了基站變換哲嘲,Wi-Fi變化等狀態(tài)贪薪。導(dǎo)致四元組發(fā)聲變化媳禁,而需要重新建立鏈接。

QUIC

Http 2.0的問題很大情況是因?yàn)門CP本身在傳輸層本身就需要保證包的有序性導(dǎo)致的画切,因此QUIC干脆拋棄TCP協(xié)議竣稽,使用UDP協(xié)議,可以看看下面QUIC的協(xié)議構(gòu)成:

網(wǎng)絡(luò)協(xié)議-QUIC_Http3.png

Http2是基于TCP協(xié)議,并在可以獨(dú)立出tls加密協(xié)議出來毫别⊥薰可以選擇是否使用tls加密。

能看到QUIC協(xié)議本質(zhì)上是基于UDP傳輸層協(xié)議岛宦。在這個(gè)之上的應(yīng)用層是QUIC協(xié)議台丛,其中包含了tls加密協(xié)議。而未來的Http3則是在QUIC協(xié)議上進(jìn)一步發(fā)展的砾肺。

QUIC的源碼十分之多和復(fù)雜挽霉?為什么如此呢?能看到QUIC實(shí)際上是在UDP上發(fā)展变汪,那么需要保證網(wǎng)絡(luò)數(shù)據(jù)包的有序性以及正確性侠坎,就需要把類似TCP可靠協(xié)議邏輯放在QUIC中實(shí)現(xiàn)。

也正因如此裙盾,QUIC是在應(yīng)用層實(shí)現(xiàn)的協(xié)議实胸,可以很靈活的切換各種協(xié)議狀態(tài),而不需要在內(nèi)核中增加socket中netFamily的族群番官,在傳輸層增加邏輯庐完。

為什么QUIC協(xié)議中內(nèi)置tls協(xié)議呢?往后看就知道優(yōu)勢在哪里了徘熔。

QUIC 使用

在聊QUIC之前假褪,我們需要熟悉這個(gè)協(xié)議cronet是如何使用的QUIC協(xié)議的。

估計(jì)熟悉的人不多近顷,因?yàn)?code>cronet網(wǎng)絡(luò)庫官方告訴你的依賴方式生音,需要的引擎需要通過GooglePlay獲取到cronet的引擎才能完整的使用所有的功能。國內(nèi)環(huán)境一般是沒有GooglePlay因此如果想要使用cronet窒升,最好把源碼弄下來缀遍,自己生成so庫或者使用生成好的so庫。

這里就不多說依賴饱须,來看看如何使用的:

  • 1.先生成一個(gè)CronetEngine引擎
    CronetEngine.Builder myBuilder = new CronetEngine.Builder(context);
    CronetEngine cronetEngine = myBuilder.build();
  • 2.構(gòu)造一個(gè)網(wǎng)絡(luò)請求過程中域醇,不同狀態(tài)的回調(diào)函數(shù):
 class MyUrlRequestCallback extends UrlRequest.Callback {
      private static final String TAG = "MyUrlRequestCallback";

      @Override
      public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
        request.followRedirect();
      }

      @Override
      public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
        request.read(ByteBuffer.allocateDirect(102400));
      }

      @Override
      public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
        request.read(ByteBuffer.allocateDirect(102400));
      }

      @Override
      public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
      }
    }
    1. 生成UrlRequest對象,并啟動(dòng)請求:
    Executor executor = Executors.newSingleThreadExecutor();
    UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(
            "https://www.example.com", new MyUrlRequestCallback(), executor);

    UrlRequest request = requestBuilder.build();
    request.start();

其實(shí)就是這么簡單蓉媳。

下面是對Cronet中UrlRequest請求的生命周期:

cronet-lifecycle.png

QUIC 源碼架構(gòu)

在聊QUIC源碼之前譬挚,我們需要初步的對Cronet的源碼架構(gòu)有一個(gè)了解。這部分的源碼實(shí)在太多酪呻,接下來的源碼使用的分支是最新的chromium 瀏覽器內(nèi)核中Cronet模塊减宣。

下面是一個(gè)Cronet的核心類在整個(gè)Cronet的組成:

cronet.png

根據(jù)上面的示意圖,可以看到Cronet玩荠,將整個(gè)模塊分為如下幾個(gè)部分:

  • 面向應(yīng)用的api層漆腌,如Android贼邓,iOS。

    • iOS 則是由一個(gè)Cronet的中類方法通過cronet_environment控制cronet引擎闷尿。
    • Android 則復(fù)雜很多塑径。首先面向開發(fā)者的java-api接口,在這個(gè)api接口中有4種不同的實(shí)現(xiàn)填具,分別是GmsCoreCronetProvider,PlayServicesCronetProvider统舀,NativeCronetProvider,JavaCronetProvider.為什么會(huì)這樣呢?其實(shí)前兩者是在Google環(huán)境和存在Google商店下內(nèi)置了Cronet的組件庫劳景,那么就可以直接復(fù)用Google為你提供的Cronet 網(wǎng)絡(luò)請求服務(wù)绑咱,從而減小包大小。當(dāng)然如果上述Cronet引擎都找不到枢泰,就會(huì)裝載默認(rèn)的JavaCronetProvider對象描融,通過JavaUrlRequest使用URLConnection進(jìn)行網(wǎng)絡(luò)請求。當(dāng)然我們可以把GmsCoreCronetProvider,PlayServicesCronetProvider看成NativeCronetProvider也未嘗不可衡蚂,之后我們也只看這個(gè)引擎加載器的源碼窿克。最終NativeCronetProvider 最終會(huì)生成CronetUrlRequest`對象交給開發(fā)者進(jìn)行請求
  • 對于Android jni層來說,幾乎java每一個(gè)步驟下生成的Java對象都會(huì)在jni的中有一個(gè)native對象毛甲。這里只說核心的幾個(gè)年叮。而在jni中,名字帶有Adapter的對象一般都是適配器玻募,連接java層對應(yīng)在native的對象只损。

    • CronetURLRequest對應(yīng)在jni中也有一個(gè)CronetURLRequest負(fù)責(zé)請求的總啟動(dòng).
    • CronetURLRequestAdapter 負(fù)責(zé)監(jiān)聽CronetURLRequest回調(diào)到j(luò)ava層對應(yīng)的生命周期回調(diào)。
    • CronetUploadDataStream 控制post時(shí)候需要發(fā)送的消息體數(shù)據(jù)流
  • Cronet Core 也就是cronet的核心引擎層七咧。在這個(gè)層級(jí)里面跃惫,無論是iOS還是Android最終都會(huì)調(diào)用到他們的api。其中在這個(gè)引擎層中包含了3部分艾栋,UrlRequest 控制請求事務(wù)流轉(zhuǎn)曾層爆存,緩存與請求流控制層QUIC實(shí)現(xiàn)層蝗砾。當(dāng)然這cronet并不只是包含了qui協(xié)議c先较,同級(jí)別的具體協(xié)議實(shí)現(xiàn)還包含了如http1.0,http2.0悼粮,webscoket等闲勺,可以在UrlRequest組建的時(shí)候決定當(dāng)前請求的協(xié)議應(yīng)該是什么。

    • URLRequest 所有的請求都會(huì)存在一個(gè)URLRequestJobFactory 請求工作工廠扣猫,當(dāng) URLRequest 需要執(zhí)行的請求的時(shí)候菜循,就會(huì)通過這個(gè)工廠生成一個(gè)Job對象進(jìn)行生命周期的流轉(zhuǎn),當(dāng)真正執(zhí)行的時(shí)候就會(huì)把事情委托給HttpCache,進(jìn)行流級(jí)別的控制管理

    • HttpCache 本質(zhì)上是一個(gè)面向流的緩存苞笨≌洌可以緩存多個(gè)請求事務(wù)(Transaction),同時(shí)每個(gè)事務(wù)會(huì)控制不同的流瀑凝。而每一個(gè)新的流都會(huì)生成一個(gè)全新的HttpStreamRequest序芦,通過JobController 創(chuàng)建一個(gè)HttpStreamFactory::Job進(jìn)行生命周期的流轉(zhuǎn)。而在這個(gè)全新的Job粤咪,就會(huì)根據(jù)之前在 URLRequest 配置好的協(xié)議谚中,進(jìn)行不同的協(xié)議請求。

    • QUIC協(xié)議層部分則是對應(yīng)quic的具體實(shí)現(xiàn)寥枝。其中會(huì)生成一個(gè)QuicStreamRequest控制每一個(gè)quic請求流宪塔,而這個(gè)請求流會(huì)把事務(wù)委托給QuicStreamFactory生成QuicStreamFactory::Job工作對象。在Job中流轉(zhuǎn)整個(gè)請求的狀態(tài)囊拜。并在這個(gè)Job中控制UDP傳輸quic協(xié)議格式的數(shù)據(jù)某筐。

有一個(gè)大致的認(rèn)識(shí)后,讓我們進(jìn)一步的了解整個(gè)QUIC的運(yùn)行機(jī)制冠跷,再來看看QUIC協(xié)議中原理以及其優(yōu)越性南誊。

QUIC 源碼解析

先根據(jù)使用來看看最初幾個(gè)api的設(shè)計(jì),來看看CronetEngine.Builder的構(gòu)造函數(shù):

        public Builder(Context context) {
            this(createBuilderDelegate(context));
        }
        private static ICronetEngineBuilder createBuilderDelegate(Context context) {
            List<CronetProvider> providers =
                    new ArrayList<>(CronetProvider.getAllProviders(context));
            CronetProvider provider = getEnabledCronetProviders(context, providers).get(0);
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG,
                        String.format("Using '%s' provider for creating CronetEngine.Builder.",
                                provider));
            }
            return provider.createBuilder().mBuilderDelegate;
        }

能看到實(shí)際上是通過CronetProvider.getAllProviders獲取所有的Cronet引擎提供容器,通過getEnabledCronetProviders篩選出第一個(gè)可用的Cronet引擎蜜托。

CronetProvider getAllProviders


    private static final String JAVA_CRONET_PROVIDER_CLASS =
            "org.chromium.net.impl.JavaCronetProvider";

    private static final String NATIVE_CRONET_PROVIDER_CLASS =
            "org.chromium.net.impl.NativeCronetProvider";

    private static final String PLAY_SERVICES_CRONET_PROVIDER_CLASS =
            "com.google.android.gms.net.PlayServicesCronetProvider";

    private static final String GMS_CORE_CRONET_PROVIDER_CLASS =
            "com.google.android.gms.net.GmsCoreCronetProvider";
...
    private static final String RES_KEY_CRONET_IMPL_CLASS = "CronetProviderClassName";

    public static List<CronetProvider> getAllProviders(Context context) {
        // Use LinkedHashSet to preserve the order and eliminate duplicate providers.
        Set<CronetProvider> providers = new LinkedHashSet<>();
        addCronetProviderFromResourceFile(context, providers);
        addCronetProviderImplByClassName(
                context, PLAY_SERVICES_CRONET_PROVIDER_CLASS, providers, false);
        addCronetProviderImplByClassName(context, GMS_CORE_CRONET_PROVIDER_CLASS, providers, false);
        addCronetProviderImplByClassName(context, NATIVE_CRONET_PROVIDER_CLASS, providers, false);
        addCronetProviderImplByClassName(context, JAVA_CRONET_PROVIDER_CLASS, providers, false);
        return Collections.unmodifiableList(new ArrayList<>(providers));
    }

    private static boolean addCronetProviderImplByClassName(
            Context context, String className, Set<CronetProvider> providers, boolean logError) {
        ClassLoader loader = context.getClassLoader();
        try {
            Class<? extends CronetProvider> providerClass =
                    loader.loadClass(className).asSubclass(CronetProvider.class);
            Constructor<? extends CronetProvider> ctor =
                    providerClass.getConstructor(Context.class);
            providers.add(ctor.newInstance(context));
            return true;
        } catch (InstantiationException e) {
            logReflectiveOperationException(className, logError, e);
        } catch (InvocationTargetException e) {
            logReflectiveOperationException(className, logError, e);
        } catch (NoSuchMethodException e) {
            logReflectiveOperationException(className, logError, e);
        } catch (IllegalAccessException e) {
            logReflectiveOperationException(className, logError, e);
        } catch (ClassNotFoundException e) {
            logReflectiveOperationException(className, logError, e);
        }
        return false;
    }

    private static boolean addCronetProviderFromResourceFile(
            Context context, Set<CronetProvider> providers) {
        int resId = context.getResources().getIdentifier(
                RES_KEY_CRONET_IMPL_CLASS, "string", context.getPackageName());
        // Resource not found
        if (resId == 0) {
            // The resource wasn't included in the app; therefore, there is nothing to add.
            return false;
        }
        String className = context.getResources().getString(resId);

        if (className == null || className.equals(PLAY_SERVICES_CRONET_PROVIDER_CLASS)
                || className.equals(GMS_CORE_CRONET_PROVIDER_CLASS)
                || className.equals(JAVA_CRONET_PROVIDER_CLASS)
                || className.equals(NATIVE_CRONET_PROVIDER_CLASS)) {
            return false;
        }

        if (!addCronetProviderImplByClassName(context, className, providers, true)) {
...
        }
        return true;
    }

能看到整個(gè)核心就是

  • 1.獲取資源ID為CronetProviderClassName 所對應(yīng)的Cronet引擎類名抄囚。
  • 2.反射內(nèi)置好的GmsCoreCronetProvider,PlayServicesCronetProviderNativeCronetProvider,JavaCronetProvider四種類名為默認(rèn)引擎提供器

在這里我們只需要閱讀NativeCronetProvider#createBuilder().mBuilderDelegate相關(guān)的源碼即可橄务。

NativeCronetProvider createBuilder

public class NativeCronetProvider extends CronetProvider {

    @UsedByReflection("CronetProvider.java")
    public NativeCronetProvider(Context context) {
        super(context);
    }

    @Override
    public CronetEngine.Builder createBuilder() {
        ICronetEngineBuilder impl = new NativeCronetEngineBuilderWithLibraryLoaderImpl(mContext);
        return new ExperimentalCronetEngine.Builder(impl);
    }

從名字能很侵襲的看到整個(gè)過程是一個(gè)委托者設(shè)計(jì)模式幔托,構(gòu)建一個(gè)ExperimentalCronetEngine對象,而這個(gè)對象將真正的執(zhí)行者委托給NativeCronetEngineBuilderWithLibraryLoaderImpl.而之前的mBuilderDelegate就是指NativeCronetEngineBuilderWithLibraryLoaderImpl對象蜂挪。

NativeCronetEngineBuilderWithLibraryLoaderImpl build

public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl {

    public NativeCronetEngineBuilderImpl(Context context) {
        super(context);
    }

    @Override
    public ExperimentalCronetEngine build() {
        if (getUserAgent() == null) {
            setUserAgent(getDefaultUserAgent());
        }

        ExperimentalCronetEngine builder = new CronetUrlRequestContext(this);

        mMockCertVerifier = 0;

        return builder;
    }
}

能看到直接返回CronetUrlRequestContext對象作為CronetEngine返回給應(yīng)用層重挑。之后會(huì)通過CronetUrlRequestContext調(diào)用newUrlRequestBuilder獲取UrlRequestBuilder。

    public CronetEngineBuilderImpl(Context context) {
        mApplicationContext = context.getApplicationContext();
        enableQuic(true);
        enableHttp2(true);
        enableBrotli(false);
        enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISABLED, 0);
        enableNetworkQualityEstimator(false);
        enablePublicKeyPinningBypassForLocalTrustAnchors(true);
    }

CronetEngineBuilderImpl將默認(rèn)支持quic的選項(xiàng)棠涮,http2選項(xiàng)攒驰,關(guān)閉httpCache。

CronetUrlRequestContext 構(gòu)造函數(shù)

    public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) {
        mRttListenerList.disableThreadAsserts();
        mThroughputListenerList.disableThreadAsserts();
        mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
        CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
        if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) {
            CronetUrlRequestContextJni.get().setMinLogLevel(getLoggingLevel());
        }
        if (builder.httpCacheMode() == HttpCacheType.DISK) {
            mInUseStoragePath = builder.storagePath();
            synchronized (sInUseStoragePaths) {
                if (!sInUseStoragePaths.add(mInUseStoragePath)) {
                    throw new IllegalStateException("Disk cache storage path already in use");
                }
            }
        } else {
            mInUseStoragePath = null;
        }
        synchronized (mLock) {
            mUrlRequestContextAdapter =
                    CronetUrlRequestContextJni.get().createRequestContextAdapter(
                            createNativeUrlRequestContextConfig(builder));
            if (mUrlRequestContextAdapter == 0) {
               ...
            }
        }

        // Init native Chromium URLRequestContext on init thread.
        CronetLibraryLoader.postToInitThread(new Runnable() {
            @Override
            public void run() {
                CronetLibraryLoader.ensureInitializedOnInitThread();
                synchronized (mLock) {
CronetUrlRequestContextJni.get().initRequestContextOnInitThread(
                            mUrlRequestContextAdapter, CronetUrlRequestContext.this);
                }
            }
        });
    }

這里核心就是圍繞3個(gè)native方法:

  • 1.CronetUrlRequestContextJni.get().setMinLogLevel(getLoggingLevel()) 設(shè)置日志等級(jí)
  • 2.CronetUrlRequestContextJni.get().createRequestContextAdapter 通過createNativeUrlRequestContextConfig獲取當(dāng)前Cronet的配置在native下層創(chuàng)建一個(gè)UrlRequestContextAdapter
  • 3.CronetUrlRequestContextJni.get().initRequestContextOnInitThread在異步線程中初始化故爵。

核心是CronetUrlRequestContextJni.get().createRequestContextAdapter 以及CronetUrlRequestContextJni.get().initRequestContextOnInitThread玻粪。要弄懂Cronet在jni層調(diào)用之前需要了解Cronet的jni初始化的JNI_OnLoad 做了什么。

不過在這之前先來簡單看看createNativeUrlRequestContextConfig看看UrlRequestContextConfig(UrlRequest上下文的配置)都有些什么選項(xiàng)诬垂?

createNativeUrlRequestContextConfig 創(chuàng)建Context配置對象

    public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) {
        final long urlRequestContextConfig =
                CronetUrlRequestContextJni.get().createRequestContextConfig(builder.getUserAgent(),
                        builder.storagePath(), builder.quicEnabled(),
                        builder.getDefaultQuicUserAgentId(), builder.http2Enabled(),
                        builder.brotliEnabled(), builder.cacheDisabled(), builder.httpCacheMode(),
                        builder.httpCacheMaxSize(), builder.experimentalOptions(),
                        builder.mockCertVerifier(), builder.networkQualityEstimatorEnabled(),
                        builder.publicKeyPinningBypassForLocalTrustAnchorsEnabled(),
                        builder.threadPriority(Process.THREAD_PRIORITY_BACKGROUND));
...
        for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) {
            CronetUrlRequestContextJni.get().addQuicHint(urlRequestContextConfig, quicHint.mHost,
                    quicHint.mPort, quicHint.mAlternatePort);
        }
        for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) {
            CronetUrlRequestContextJni.get().addPkp(urlRequestContextConfig, pkp.mHost, pkp.mHashes,
                    pkp.mIncludeSubdomains, pkp.mExpirationDate.getTime());
        }
        return urlRequestContextConfig;
    }

能看到配置除了上面說過的quic模式和劲室,http2模式。還有httpCache的開關(guān)以及Cache的大小结窘。

注意如果想要使用QUIC需要設(shè)置QuicHint很洋,告訴QUIC協(xié)議哪些urlhost支持quic協(xié)議。

另外隧枫,還能通過設(shè)置CronetEngineBuilderImpl.Pkp 設(shè)置默認(rèn)的加密公鑰喉磁。

cronet_jni JNI_OnLoad

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  return cronet::CronetOnLoad(vm, reserved);
}

extern "C" void JNI_OnUnLoad(JavaVM* vm, void* reserved) {
  cronet::CronetOnUnLoad(vm, reserved);
}
jint CronetOnLoad(JavaVM* vm, void* reserved) {
  base::android::InitVM(vm);
  JNIEnv* env = base::android::AttachCurrentThread();
  if (!RegisterMainDexNatives(env) || !RegisterNonMainDexNatives(env)) {
    return -1;
  }
  if (!base::android::OnJNIOnLoadInit())
    return -1;
  NativeInit();
  return JNI_VERSION_1_6;
}

void CronetOnUnLoad(JavaVM* jvm, void* reserved) {
  if (base::ThreadPoolInstance::Get())
    base::ThreadPoolInstance::Get()->Shutdown();

  base::android::LibraryLoaderExitHook();
}
  • 1.RegisterMainDexNativesRegisterNonMainDexNatives 實(shí)際上是加載通過jni_registration_generator.py生成的cpp文件谓苟。這種文件生成出來就是為了減少dlsym()耗時(shí)。

了解jni的小伙伴都會(huì)清楚jni有兩種注冊方式一種是簡單的直接聲明native方法协怒,然后通過AS可以自動(dòng)生成包名_類名_方法名的cpp方法涝焙。而后虛擬機(jī)加載native的方法時(shí)候,就會(huì)通過dlsym()調(diào)用查找so動(dòng)態(tài)庫中的對應(yīng)的方法孕暇。另一種則是通過JNIEnv->RegisterNatives手動(dòng)在JNI_OnLoad注冊當(dāng)前的native方法關(guān)聯(lián)的java方法(注意要有指向包和類名)仑撞。

jni_registration_generator.py就是會(huì)遍歷所有java文件中的native方法并JNIEnv->RegisterNatives手動(dòng)注冊的代碼cpp代碼。同時(shí)會(huì)遍歷Java文件中帶上了@CalledByNative方法妖滔,說明這是native想要調(diào)用java方法隧哮,也會(huì)生成相關(guān)的反射jmethod的方法的文件。

在這里RegisterMainDexNativesRegisterNonMainDexNatives 本質(zhì)上就是裝在生成好的動(dòng)態(tài)注冊的jni方法座舍。

這個(gè)不是重點(diǎn)之后有機(jī)會(huì)再仔細(xì)聊聊沮翔。

    1. OnJNIOnLoadInit 這個(gè)方法就是獲取JNIUtils類中的ClassLoader,并獲取ClassLoader#loadClass的jmethodID保存到全局變量g_class_loader_load_class_method_id
  • 3.NativeInit 在全局生成一個(gè)名為Cronet的線程池。

CreateRequestContextAdapter 初始化

static jlong JNI_CronetUrlRequestContext_CreateRequestContextAdapter(
    JNIEnv* env,
    jlong jconfig) {
  std::unique_ptr<URLRequestContextConfig> context_config(
      reinterpret_cast<URLRequestContextConfig*>(jconfig));

  CronetURLRequestContextAdapter* context_adapter =
      new CronetURLRequestContextAdapter(std::move(context_config));
  return reinterpret_cast<jlong>(context_adapter);
}

很簡答就是初始化了CronetURLRequestContextAdapter一個(gè)cpp對象對應(yīng)java對象曲秉,并返回當(dāng)前對象的地址到j(luò)ava中鉴竭。

CreateRequestContextAdapter 頭文件

要了解一個(gè)c++的類,首先看看頭文件岸浑,然后再看看構(gòu)造函數(shù)搏存。

...
class CronetURLRequestContextAdapter
    : public CronetURLRequestContext::Callback {
 public:
  explicit CronetURLRequestContextAdapter(
      std::unique_ptr<URLRequestContextConfig> context_config);

  CronetURLRequestContextAdapter(const CronetURLRequestContextAdapter&) =
      delete;
  CronetURLRequestContextAdapter& operator=(
      const CronetURLRequestContextAdapter&) = delete;

  ~CronetURLRequestContextAdapter() override;

...
 private:
  friend class TestUtil;

  // Native Cronet URL Request Context.
  raw_ptr<CronetURLRequestContext> context_;

  // Java object that owns this CronetURLRequestContextAdapter.
  base::android::ScopedJavaGlobalRef<jobject> jcronet_url_request_context_;
};

}  // namespace cronet

...

在這里其實(shí)持有了一個(gè)CronetURLRequestContextnative對象,這個(gè)對象顧名思義就是Cronet請求時(shí)候的上下文矢洲。同時(shí)持有將會(huì)持有一個(gè)UrlRequestContext的java對象璧眠。

之后當(dāng)Cronet的狀態(tài)發(fā)生變化都會(huì)通過這里的回調(diào)java層的CronetUrlRequestContext中的監(jiān)聽。

CronetURLRequestContextAdapter 構(gòu)造函數(shù)

CronetURLRequestContextAdapter::CronetURLRequestContextAdapter(
    std::unique_ptr<URLRequestContextConfig> context_config) {
  // Create context and pass ownership of |this| (self) to the context.
  std::unique_ptr<CronetURLRequestContextAdapter> self(this);
#if BUILDFLAG(INTEGRATED_MODE)
  // Create CronetURLRequestContext running in integrated network task runner.
  ...
#else
  context_ =
      new CronetURLRequestContext(std::move(context_config), std::move(self));
#endif
}

CronetURLRequestContextAdapter 則會(huì)創(chuàng)建一個(gè)CronetURLRequestContext對象

CronetURLRequestContext 頭文件

class CronetURLRequestContext {
 public:
  // Callback implemented by CronetURLRequestContext() caller and owned by
  // CronetURLRequestContext::NetworkTasks.
  class Callback {
   public:
    virtual ~Callback() = default;

    // Invoked on network thread when initialized.
    virtual void OnInitNetworkThread() = 0;

    // Invoked on network thread immediately prior to destruction.
    virtual void OnDestroyNetworkThread() = 0;

...
  };


  CronetURLRequestContext(
      std::unique_ptr<URLRequestContextConfig> context_config,
      std::unique_ptr<Callback> callback,
      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner =
          nullptr);

  CronetURLRequestContext(const CronetURLRequestContext&) = delete;
  CronetURLRequestContext& operator=(const CronetURLRequestContext&) = delete;

  // Releases all resources for the request context and deletes the object.
  // Blocks until network thread is destroyed after running all pending tasks.
  virtual ~CronetURLRequestContext();

  // Called on init thread to initialize URLRequestContext.
  void InitRequestContextOnInitThread();
...

 private:
  friend class TestUtil;
  class ContextGetter;

  class NetworkTasks : public net::EffectiveConnectionTypeObserver,
                       public net::RTTAndThroughputEstimatesObserver,
                       public net::NetworkQualityEstimator::RTTObserver,
                       public net::NetworkQualityEstimator::ThroughputObserver {
   public:
    // Invoked off the network thread.
    NetworkTasks(std::unique_ptr<URLRequestContextConfig> config,
                 std::unique_ptr<CronetURLRequestContext::Callback> callback);

    NetworkTasks(const NetworkTasks&) = delete;
    NetworkTasks& operator=(const NetworkTasks&) = delete;

    // Invoked on the network thread.
    ~NetworkTasks() override;
...

   private:
...
  };

...
};

}  // namespace cronet

#endif  // COMPONENTS_CRONET_CRONET_URL_REQUEST_CONTEXT_H_

能看到這個(gè)頭文件分為3部分:

  • CronetURLRequestContext 承載所有URLRequest請求的上下文读虏,主要用于構(gòu)建網(wǎng)絡(luò)任務(wù)的環(huán)境责静,回調(diào)網(wǎng)絡(luò)質(zhì)量相關(guān)的回調(diào)(如rtt耗時(shí)等)
  • Callback 用于回調(diào)來自NetworkQualityEstimator對象對網(wǎng)絡(luò)質(zhì)量的監(jiān)控
  • NetworkTasks 實(shí)現(xiàn)了Callback的回調(diào),承載網(wǎng)絡(luò)請求的起始

CronetURLRequestContext 構(gòu)造函數(shù)


CronetURLRequestContext::CronetURLRequestContext(
    std::unique_ptr<URLRequestContextConfig> context_config,
    std::unique_ptr<Callback> callback,
    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
    : bidi_stream_detect_broken_connection_(
          context_config->bidi_stream_detect_broken_connection),
      heartbeat_interval_(context_config->heartbeat_interval),
      default_load_flags_(
          net::LOAD_NORMAL |
          (context_config->load_disable_cache ? net::LOAD_DISABLE_CACHE : 0)),
      network_tasks_(
          new NetworkTasks(std::move(context_config), std::move(callback))),
      network_task_runner_(network_task_runner) {
  if (!network_task_runner_) {
    network_thread_ = std::make_unique<base::Thread>("network");
    base::Thread::Options options;
    options.message_pump_type = base::MessagePumpType::IO;
    network_thread_->StartWithOptions(std::move(options));
    network_task_runner_ = network_thread_->task_runner();
  }
}

初始化一個(gè)NetworkTasks作為一個(gè)網(wǎng)絡(luò)請求任務(wù)承載者盖桥。并初始化一個(gè)名為network的線程灾螃,并以這個(gè)線程創(chuàng)建一個(gè)looper賦值給network_task_runner_,之后所有的請求任務(wù)都會(huì)在這個(gè)loop中開始揩徊。

initRequestContextOnInitThread

void CronetURLRequestContext::InitRequestContextOnInitThread() {
  DCHECK(OnInitThread());
  auto proxy_config_service =
      cronet::CreateProxyConfigService(GetNetworkTaskRunner());
  g_net_log.Get().EnsureInitializedOnInitThread();
  GetNetworkTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CronetURLRequestContext::NetworkTasks::Initialize,
                     base::Unretained(network_tasks_), GetNetworkTaskRunner(),
                     GetFileThread()->task_runner(),
                     std::move(proxy_config_service)));
}

GetNetworkTaskRunner獲取network_task_runner_調(diào)用PostTask進(jìn)入切換到network線程的loop腰鬼,執(zhí)行CronetURLRequestContext::NetworkTasks::Initialize方法

void CronetURLRequestContext::NetworkTasks::Initialize(
    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
    scoped_refptr<base::SequencedTaskRunner> file_task_runner,
    std::unique_ptr<net::ProxyConfigService> proxy_config_service) {

  std::unique_ptr<URLRequestContextConfig> config(std::move(context_config_));
  network_task_runner_ = network_task_runner;
  if (config->network_thread_priority)
    SetNetworkThreadPriorityOnNetworkThread(
        config->network_thread_priority.value());
  base::DisallowBlocking();
  net::URLRequestContextBuilder context_builder;
  context_builder.set_network_delegate(
      std::make_unique<BasicNetworkDelegate>());
  context_builder.set_net_log(g_net_log.Get().net_log());

  context_builder.set_proxy_resolution_service(
      cronet::CreateProxyResolutionService(std::move(proxy_config_service),
                                           g_net_log.Get().net_log()));

  config->ConfigureURLRequestContextBuilder(&context_builder);
  effective_experimental_options_ =
      base::Value(config->effective_experimental_options);

  if (config->enable_network_quality_estimator) {
    std::unique_ptr<net::NetworkQualityEstimatorParams> nqe_params =
        std::make_unique<net::NetworkQualityEstimatorParams>(
            std::map<std::string, std::string>());
    if (config->nqe_forced_effective_connection_type) {
      nqe_params->SetForcedEffectiveConnectionType(
          config->nqe_forced_effective_connection_type.value());
    }

    network_quality_estimator_ = std::make_unique<net::NetworkQualityEstimator>(
        std::move(nqe_params), g_net_log.Get().net_log());
    network_quality_estimator_->AddEffectiveConnectionTypeObserver(this);
    network_quality_estimator_->AddRTTAndThroughputEstimatesObserver(this);

    context_builder.set_network_quality_estimator(
        network_quality_estimator_.get());
  }

...

  // Disable net::CookieStore.
  context_builder.SetCookieStore(nullptr);

  context_ = context_builder.Build();

..

  if (config->enable_quic) {
    for (const auto& quic_hint : config->quic_hints) {
      if (quic_hint->host.empty()) {
      ...
        continue;
      }

      url::CanonHostInfo host_info;
      std::string canon_host(
          net::CanonicalizeHost(quic_hint->host, &host_info));
      if (!host_info.IsIPAddress() &&
          !net::IsCanonicalizedHostCompliant(canon_host)) {
...
        continue;
      }

      if (quic_hint->port <= std::numeric_limits<uint16_t>::min() ||
          quic_hint->port > std::numeric_limits<uint16_t>::max()) {
...
        continue;
      }

      if (quic_hint->alternate_port <= std::numeric_limits<uint16_t>::min() ||
          quic_hint->alternate_port > std::numeric_limits<uint16_t>::max()) {
...
        continue;
      }

      url::SchemeHostPort quic_server("https", canon_host, quic_hint->port);
      net::AlternativeService alternative_service(
          net::kProtoQUIC, "",
          static_cast<uint16_t>(quic_hint->alternate_port));
      context_->http_server_properties()->SetQuicAlternativeService(
          quic_server, net::NetworkIsolationKey(), alternative_service,
          base::Time::Max(), quic::ParsedQuicVersionVector());
    }
  }

  for (const auto& pkp : config->pkp_list) {
    // Add the host pinning.
    context_->transport_security_state()->AddHPKP(
        pkp->host, pkp->expiration_date, pkp->include_subdomains,
        pkp->pin_hashes, GURL::EmptyGURL());
  }

  context_->transport_security_state()
      ->SetEnablePublicKeyPinningBypassForLocalTrustAnchors(
          config->bypass_public_key_pinning_for_local_trust_anchors);

  callback_->OnInitNetworkThread();
  is_context_initialized_ = true;

  if (config->enable_network_quality_estimator && cronet_prefs_manager_) {
    network_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(
            &CronetURLRequestContext::NetworkTasks::InitializeNQEPrefs,
            base::Unretained(this)));
  }

#if BUILDFLAG(ENABLE_REPORTING)
  if (context_->reporting_service()) {
    for (const auto& preloaded_header : config->preloaded_report_to_headers) {
      context_->reporting_service()->ProcessReportToHeader(
          preloaded_header.origin, net::NetworkIsolationKey(),
          preloaded_header.value);
    }
  }

  if (context_->network_error_logging_service()) {
    for (const auto& preloaded_header : config->preloaded_nel_headers) {
      context_->network_error_logging_service()->OnHeader(
          net::NetworkIsolationKey(), preloaded_header.origin, net::IPAddress(),
          preloaded_header.value);
    }
  }
#endif  // BUILDFLAG(ENABLE_REPORTING)

  while (!tasks_waiting_for_context_.empty()) {
    std::move(tasks_waiting_for_context_.front()).Run();
    tasks_waiting_for_context_.pop();
  }
}

別看這段代碼很長實(shí)際上做的事情也就如下幾件:

  • 1.根據(jù)URLRequestContextConfig裝載出NetworkQualityEstimator網(wǎng)絡(luò)質(zhì)量監(jiān)控器
  • 2.創(chuàng)建URLRequestContext 對象,為之后的UrlRequest做準(zhǔn)備
  • 3.如果URLRequestContextConfig允許了quic協(xié)議那么會(huì)加載所有的QuicHint中的資源路徑塑荒,端口號(hào)作為識(shí)別熄赡。之后遇到這些請求就會(huì)使用quic協(xié)議,最后生成的SchemeHostPort通過SetQuicAlternativeService保存到URLRequestContext

到目前為止java層的CronetUrlRequestContext通過native層的CronetUrlRequestContextAdapter創(chuàng)建了一個(gè)對應(yīng)在native層的CronetUrlRequestContext對象進(jìn)行一一對應(yīng)齿税。

當(dāng)準(zhǔn)備好了CronetUrlRequestContext,就可以使用CronetUrlRequestContext創(chuàng)建newUrlRequestBuilder請求

CronetEngineBase newUrlRequestBuilder 創(chuàng)建請求對象

    @Override
    public ExperimentalUrlRequest.Builder newUrlRequestBuilder(
            String url, UrlRequest.Callback callback, Executor executor) {
        return new UrlRequestBuilderImpl(url, callback, executor, this);
    }
UrlRequestBuilderImpl createRequest 創(chuàng)建請求對象
    @Override
    public UrlRequestBase createRequest(String url, UrlRequest.Callback callback, Executor executor,
            int priority, Collection<Object> requestAnnotations, boolean disableCache,
            boolean disableConnectionMigration, boolean allowDirectExecutor,
            boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
            int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener,
            int idempotency) {
        synchronized (mLock) {
            checkHaveAdapter();
            return new CronetUrlRequest(this, url, priority, callback, executor, requestAnnotations,
                    disableCache, disableConnectionMigration, allowDirectExecutor,
                    trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid,
                    requestFinishedListener, idempotency);
        }
    }

很簡單彼硫,這里把之前在UrlRequestBuilderImpl組合的參數(shù)都保存到CronetUrlRequest返回給應(yīng)用層。

CronetUrlRequest.start 啟動(dòng)請求
    @Override
    public void start() {
        synchronized (mUrlRequestAdapterLock) {
            checkNotStarted();

            try {
                mUrlRequestAdapter = CronetUrlRequestJni.get().createRequestAdapter(
                        CronetUrlRequest.this, mRequestContext.getUrlRequestContextAdapter(),
                        mInitialUrl, mPriority, mDisableCache, mDisableConnectionMigration,
                        mRequestContext.hasRequestFinishedListener()
                                || mRequestFinishedListener != null,
                        mTrafficStatsTagSet, mTrafficStatsTag, mTrafficStatsUidSet,
                        mTrafficStatsUid, mIdempotency);
                mRequestContext.onRequestStarted();
                if (mInitialMethod != null) {
                    if (!CronetUrlRequestJni.get().setHttpMethod(
                                mUrlRequestAdapter, CronetUrlRequest.this, mInitialMethod)) {
...
                    }
                }

                boolean hasContentType = false;
                for (Map.Entry<String, String> header : mRequestHeaders) {
                    if (header.getKey().equalsIgnoreCase("Content-Type")
                            && !header.getValue().isEmpty()) {
                        hasContentType = true;
                    }
                    if (!CronetUrlRequestJni.get().addRequestHeader(mUrlRequestAdapter,
                                CronetUrlRequest.this, header.getKey(), header.getValue())) {
...
                    }
                }
                if (mUploadDataStream != null) {
                    if (!hasContentType) {
                ...
                    }
                    mStarted = true;
                    mUploadDataStream.postTaskToExecutor(new Runnable() {
                        @Override
                        public void run() {
                            mUploadDataStream.initializeWithRequest();
                            synchronized (mUrlRequestAdapterLock) {
                                if (isDoneLocked()) {
                                    return;
                                }
                                mUploadDataStream.attachNativeAdapterToRequest(mUrlRequestAdapter);
                                startInternalLocked();
                            }
                        }
                    });
                    return;
                }
            } catch (RuntimeException e) {
...
            }
            mStarted = true;
            startInternalLocked();
        }
    }

    @GuardedBy("mUrlRequestAdapterLock")
    private void startInternalLocked() {
        CronetUrlRequestJni.get().start(mUrlRequestAdapter, CronetUrlRequest.this);
    }

這里圍繞著4個(gè)核心的jni方法:

  • 1.createRequestAdapter 生成一個(gè)jni的UrlRequestAdapter對象
  • 2.回調(diào)onRequestStarted生命周期
  • 3.setHttpMethod 為jni的UrlRequestAdapter 設(shè)置 http請求類型
  • 4.addRequestHeader為請求裝載header
  • 5.mUploadDataStream讀取并把body的數(shù)據(jù)緩存到j(luò)ni的UploadDataStream中.
  • 6.調(diào)用startInternalLocked也就是CronetUrlRequest的start

CronetURLRequestAdapter 創(chuàng)建

static jlong JNI_CronetUrlRequest_CreateRequestAdapter(
    JNIEnv* env,
    const JavaParamRef<jobject>& jurl_request,
    jlong jurl_request_context_adapter,
    const JavaParamRef<jstring>& jurl_string,
    jint jpriority,
    jboolean jdisable_cache,
    jboolean jdisable_connection_migration,
    jboolean jenable_metrics,
    jboolean jtraffic_stats_tag_set,
    jint jtraffic_stats_tag,
    jboolean jtraffic_stats_uid_set,
    jint jtraffic_stats_uid,
    jint jidempotency) {
  CronetURLRequestContextAdapter* context_adapter =
      reinterpret_cast<CronetURLRequestContextAdapter*>(
          jurl_request_context_adapter);


  CronetURLRequestAdapter* adapter = new CronetURLRequestAdapter(
      context_adapter, env, jurl_request, url,
      static_cast<net::RequestPriority>(jpriority), jdisable_cache,
      jdisable_connection_migration, jenable_metrics, jtraffic_stats_tag_set,
      jtraffic_stats_tag, jtraffic_stats_uid_set, jtraffic_stats_uid,
      static_cast<net::Idempotency>(jidempotency));

  return reinterpret_cast<jlong>(adapter);
}
CronetURLRequestAdapter::CronetURLRequestAdapter(
    CronetURLRequestContextAdapter* context,
    JNIEnv* env,
    jobject jurl_request,
    const GURL& url,
    net::RequestPriority priority,
    jboolean jdisable_cache,
    jboolean jdisable_connection_migration,
    jboolean jenable_metrics,
    jboolean jtraffic_stats_tag_set,
    jint jtraffic_stats_tag,
    jboolean jtraffic_stats_uid_set,
    jint jtraffic_stats_uid,
    net::Idempotency idempotency)
    : request_(
          new CronetURLRequest(context->cronet_url_request_context(),
                               std::unique_ptr<CronetURLRequestAdapter>(this),
                               url,
                               priority,
                               jdisable_cache == JNI_TRUE,
                               jdisable_connection_migration == JNI_TRUE,
                               jenable_metrics == JNI_TRUE,
                               jtraffic_stats_tag_set == JNI_TRUE,
                               jtraffic_stats_tag,
                               jtraffic_stats_uid_set == JNI_TRUE,
                               jtraffic_stats_uid,
                               idempotency)) {
  owner_.Reset(env, jurl_request);
}

CronetURLRequestContextAdapter 持有一個(gè) CronetURLRequest對象。剛剛好對應(yīng)java層中的CronetURLRequest拧篮。整個(gè)請求的發(fā)起就是從CronetURLRequest開始词渤。

CronetURLRequest 構(gòu)造函數(shù)
CronetURLRequest::CronetURLRequest(CronetURLRequestContext* context,
                                   std::unique_ptr<Callback> callback,
                                   const GURL& url,
                                   net::RequestPriority priority,
                                   bool disable_cache,
                                   bool disable_connection_migration,
                                   bool enable_metrics,
                                   bool traffic_stats_tag_set,
                                   int32_t traffic_stats_tag,
                                   bool traffic_stats_uid_set,
                                   int32_t traffic_stats_uid,
                                   net::Idempotency idempotency)
    : context_(context),
      network_tasks_(std::move(callback),
                     url,
                     priority,
                     CalculateLoadFlags(context->default_load_flags(),
                                        disable_cache,
                                        disable_connection_migration),
                     enable_metrics,
                     traffic_stats_tag_set,
                     traffic_stats_tag,
                     traffic_stats_uid_set,
                     traffic_stats_uid,
                     idempotency),
      initial_method_("GET"),
      initial_request_headers_(std::make_unique<net::HttpRequestHeaders>()) {
}

能看到CronetURLRequest默認(rèn)設(shè)置GET http的方法,同時(shí)創(chuàng)建HttpRequestHeaders接受Http協(xié)議的頭部信息串绩。

CronetURLRequest start

java方法CronetUrlRequestJni.get().start所對應(yīng)的jni方法如下德澈,也就是CronetURLRequest的start方法滴须。

void CronetURLRequestAdapter::Start(JNIEnv* env,
                                    const JavaParamRef<jobject>& jcaller) {
  request_->Start();
}
void CronetURLRequest::Start() {
  DCHECK(!context_->IsOnNetworkThread());
  context_->PostTaskToNetworkThread(
      FROM_HERE,
      base::BindOnce(&CronetURLRequest::NetworkTasks::Start,
                     base::Unretained(&network_tasks_),
                     base::Unretained(context_), initial_method_,
                     std::move(initial_request_headers_), std::move(upload_)));
}

start方法其實(shí)就是切換到network線程棚赔。調(diào)用NetworkTasks名為start類方法脉幢。

NetworkTasks Start
void CronetURLRequest::NetworkTasks::Start(
    CronetURLRequestContext* context,
    const std::string& method,
    std::unique_ptr<net::HttpRequestHeaders> request_headers,
    std::unique_ptr<net::UploadDataStream> upload) {

  url_request_ = context->GetURLRequestContext()->CreateRequest(
      initial_url_, net::DEFAULT_PRIORITY, this, MISSING_TRAFFIC_ANNOTATION);
  url_request_->SetLoadFlags(initial_load_flags_);
  url_request_->set_method(method);
  url_request_->SetExtraRequestHeaders(*request_headers);
  url_request_->SetPriority(initial_priority_);
  url_request_->SetIdempotency(idempotency_);
  std::string referer;
  if (request_headers->GetHeader(net::HttpRequestHeaders::kReferer, &referer)) {
    url_request_->SetReferrer(referer);
  }
  if (upload)
    url_request_->set_upload(std::move(upload));
  if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
#if BUILDFLAG(IS_ANDROID)
    url_request_->set_socket_tag(net::SocketTag(
        traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
        traffic_stats_tag_set_ ? traffic_stats_tag_
                               : net::SocketTag::UNSET_TAG));
#else
...
#endif
  }
  url_request_->Start();
}

GetURLRequestContext()->CreateRequest創(chuàng)造一個(gè)URLRequest對象益咬。將保存在CronetURLRequest填充到URLRequest中佳窑,并調(diào)用這個(gè)對象的start方法单旁。

而這個(gè)URLRequest 你可以看成Cronet的內(nèi)核對外的最重要的接口墨林。因?yàn)閕OS的模塊最終也是對接到URLRequest對象中腰涧。

URLRequest Start

void URLRequest::Start() {
  if (status_ != OK)
    return;
...

...

  StartJob(context_->job_factory()->CreateJob(this));
}

很見到在這里獲取job_factory通過CreateJob 創(chuàng)建一個(gè)URLRequestJob工作項(xiàng)韧掩,并調(diào)用UrlRequestStartJob啟動(dòng)URLRequestJob。
簡單看看CreateJob返回的是什么類型的URLRequestJob.

std::unique_ptr<URLRequestJob> URLRequestJobFactory::CreateJob(
    URLRequest* request) const {

  if (!request->url().is_valid())
    return std::make_unique<URLRequestErrorJob>(request, ERR_INVALID_URL);

  if (g_interceptor_for_testing) {
    std::unique_ptr<URLRequestJob> job(
        g_interceptor_for_testing->MaybeInterceptRequest(request));
    if (job)
      return job;
  }

  auto it = protocol_handler_map_.find(request->url().scheme());
  if (it == protocol_handler_map_.end()) {
    return std::make_unique<URLRequestErrorJob>(request,
                                                ERR_UNKNOWN_URL_SCHEME);
  }

  return it->second->CreateJob(request);
}

實(shí)際上在不同的協(xié)議都會(huì)對應(yīng)上不同的URLRequestJob工廠窖铡,而這些網(wǎng)絡(luò)協(xié)議創(chuàng)建工廠為ProtocolHandler.這些ProtocolHandler都可以通過設(shè)置到protocol_handler_map_中疗锐,根據(jù)協(xié)議頭scheme進(jìn)行自定義協(xié)議實(shí)現(xiàn)。

而在這個(gè)內(nèi)核層中费彼,默認(rèn)自帶了HttpProtocolHandler實(shí)現(xiàn),如下:

class HttpProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
 public:

  explicit HttpProtocolHandler(bool is_for_websockets)
      : is_for_websockets_(is_for_websockets) {}

  HttpProtocolHandler(const HttpProtocolHandler&) = delete;
  HttpProtocolHandler& operator=(const HttpProtocolHandler&) = delete;
  ~HttpProtocolHandler() override = default;

  std::unique_ptr<URLRequestJob> CreateJob(URLRequest* request) const override {
    if (request->is_for_websockets() != is_for_websockets_) {
      return std::make_unique<URLRequestErrorJob>(request,
                                                  ERR_UNKNOWN_URL_SCHEME);
    }
    return URLRequestHttpJob::Create(request);
  }

  const bool is_for_websockets_;
};

能看到默認(rèn)的 Http對應(yīng)的協(xié)議處理器HttpProtocolHandler,并通過CreateJob創(chuàng)建請求任務(wù)對應(yīng)URLRequestHttpJob滑臊。而這個(gè)的設(shè)置時(shí)機(jī):

URLRequestJobFactory::URLRequestJobFactory() {
  SetProtocolHandler(url::kHttpScheme, std::make_unique<HttpProtocolHandler>(
                                           /*is_for_websockets=*/false));
  SetProtocolHandler(url::kHttpsScheme, std::make_unique<HttpProtocolHandler>(
                                            /*is_for_websockets=*/false));
#if BUILDFLAG(ENABLE_WEBSOCKETS)
  SetProtocolHandler(url::kWsScheme, std::make_unique<HttpProtocolHandler>(
                                         /*is_for_websockets=*/true));
  SetProtocolHandler(url::kWssScheme, std::make_unique<HttpProtocolHandler>(
                                          /*is_for_websockets=*/true));
#endif  // BUILDFLAG(ENABLE_WEBSOCKETS)
}

job_factory也就是URLRequestJobFactory類型,能看到構(gòu)造函數(shù)中默認(rèn)的設(shè)置了http和https箍铲,ws,wss的協(xié)議處理器雇卷。

void URLRequest::StartJob(std::unique_ptr<URLRequestJob> job) {
...
  job_ = std::move(job);
  job_->SetExtraRequestHeaders(extra_request_headers_);
  job_->SetPriority(priority_);
  job_->SetRequestHeadersCallback(request_headers_callback_);
  job_->SetEarlyResponseHeadersCallback(early_response_headers_callback_);
  job_->SetResponseHeadersCallback(response_headers_callback_);

  if (upload_data_stream_.get())
    job_->SetUpload(upload_data_stream_.get());

...
  job_->Start();
}

很簡單就是把URLRequest 中的頭部,優(yōu)先級(jí)颠猴,回調(diào)关划,消息體的數(shù)據(jù)流引用數(shù)據(jù)保存到URLRequestJob,并調(diào)用URLRequestJob的Start翘瓮。此時(shí)URLRequestJob一般是指URLRequestHttpJob.

URLRequestHttpJob Start

void URLRequestHttpJob::Start() {

  request_info_.url = request_->url();
  request_info_.method = request_->method();

  request_info_.network_isolation_key =
      request_->isolation_info().network_isolation_key();
  request_info_.possibly_top_frame_origin =
      request_->isolation_info().top_frame_origin();
  request_info_.is_subframe_document_resource =
      request_->isolation_info().request_type() ==
      net::IsolationInfo::RequestType::kSubFrame;
  request_info_.load_flags = request_->load_flags();
  request_info_.secure_dns_policy = request_->secure_dns_policy();
  request_info_.traffic_annotation =
      net::MutableNetworkTrafficAnnotationTag(request_->traffic_annotation());
  request_info_.socket_tag = request_->socket_tag();
  request_info_.idempotency = request_->GetIdempotency();
#if BUILDFLAG(ENABLE_REPORTING)
  request_info_.reporting_upload_depth = request_->reporting_upload_depth();
#endif

  bool should_add_cookie_header = ShouldAddCookieHeader();


  if (!should_add_cookie_header) {
    OnGotFirstPartySetMetadata(FirstPartySetMetadata());
    return;
  }
  absl::optional<FirstPartySetMetadata> metadata =
      cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
          SchemefulSite(request()->url()), request()->isolation_info(),
          request()->context()->cookie_store()->cookie_access_delegate(),
          request()->force_ignore_top_frame_party_for_cookies(),
          base::BindOnce(&URLRequestHttpJob::OnGotFirstPartySetMetadata,
                         weak_factory_.GetWeakPtr()));

  if (metadata.has_value())
    OnGotFirstPartySetMetadata(std::move(metadata.value()));
}

UrlRequest的請求參數(shù)保存到request_info_贮折。如果沒有任何的cookie則直接調(diào)用OnGotFirstPartySetMetadata,如果存在全局通用cookie,則把數(shù)據(jù)保存到FirstPartySetMetadata。并調(diào)用OnGotFirstPartySetMetadata.

OnGotFirstPartySetMetadata

void URLRequestHttpJob::OnGotFirstPartySetMetadata(
    FirstPartySetMetadata first_party_set_metadata) {
  first_party_set_metadata_ = std::move(first_party_set_metadata);
 
  request_info_.privacy_mode = DeterminePrivacyMode();
...

  GURL referrer(request_->referrer());

  if (referrer.is_valid()) {
    std::string referer_value = referrer.spec();
    request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer,
                                          referer_value);
  }

  request_info_.extra_headers.SetHeaderIfMissing(
      HttpRequestHeaders::kUserAgent,
      http_user_agent_settings_ ?
          http_user_agent_settings_->GetUserAgent() : std::string());

  AddExtraHeaders();

  if (ShouldAddCookieHeader()) {

    cookie_partition_key_ =
        absl::make_optional(CookiePartitionKey::FromNetworkIsolationKey(
            request_->isolation_info().network_isolation_key(),
            base::OptionalOrNullptr(
                first_party_set_metadata_.top_frame_owner())));
    AddCookieHeaderAndStart();
  } else {
    StartTransaction();
  }
}
  • 1.先通過SetHeader以及AddExtraHeaders設(shè)置Referer,GZIP等常用的Header
  • 2.如果存在cookie則通過AddCookieHeaderAndStart添加到Header中Cookie為key的數(shù)據(jù)集合中资盅。不過
  • 3.StartTransaction啟動(dòng)事務(wù)调榄。

URLRequestHttpJob StartTransaction

void URLRequestHttpJob::StartTransaction() {
...
  StartTransactionInternal();
}

void URLRequestHttpJob::StartTransactionInternal() {
 

  int rv;

  ...

  if (transaction_.get()) {
    rv = transaction_->RestartWithAuth(
        auth_credentials_, base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
                                          base::Unretained(this)));
    auth_credentials_ = AuthCredentials();
  } else {

    rv = request_->context()->http_transaction_factory()->CreateTransaction(
        priority_, &transaction_);
...

    if (rv == OK) {
      transaction_->SetConnectedCallback(base::BindRepeating(
          &URLRequestHttpJob::NotifyConnectedCallback, base::Unretained(this)));
      transaction_->SetRequestHeadersCallback(request_headers_callback_);
      transaction_->SetEarlyResponseHeadersCallback(
          early_response_headers_callback_);
      transaction_->SetResponseHeadersCallback(response_headers_callback_);

      if (!throttling_entry_.get() ||
          !throttling_entry_->ShouldRejectRequest(*request_)) {
        rv = transaction_->Start(
            &request_info_,
            base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
                           base::Unretained(this)),
            request_->net_log());
        start_time_ = base::TimeTicks::Now();
      } else {
        // Special error code for the exponential back-off module.
        rv = ERR_TEMPORARILY_THROTTLED;
      }
    }
  }
  if (rv == ERR_IO_PENDING)
    return;
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
                                weak_factory_.GetWeakPtr(), rv));
}

能看到這個(gè)過程中存在一個(gè)核心的對象transaction_也就是HttpTransaction。之后請求Job工作項(xiàng)呵扛,就將請求委托給HttpTransaction.

如果發(fā)現(xiàn)URLRequestHttpJob已經(jīng)存在了HttpTransaction振峻,那么就會(huì)調(diào)用HttpTransactionRestartWithAuth重新啟動(dòng)并且校驗(yàn)權(quán)限。

如果發(fā)現(xiàn)沒有創(chuàng)建择份,則調(diào)用事務(wù)工廠CreateTransaction創(chuàng)建HttpTransaction扣孟,然后調(diào)用Start方法正式啟動(dòng)事務(wù),開始請求荣赶。

總結(jié)

首先進(jìn)行一個(gè)初步的總結(jié)凤价,到了HttpTransaction之后鸽斟,就會(huì)開始流轉(zhuǎn)請求的生命周期,然后進(jìn)行quic協(xié)議的初始化利诺,執(zhí)行quic的請求富蓄。

不過限于篇幅,以及Cronet設(shè)計(jì)上的確實(shí)比較冗長慢逾,這里先做一個(gè)簡單的總結(jié)先:

可以將Cronet的設(shè)計(jì)組合看成3層:

  • 1.java的api層
  • 2.用于連通java和native的jni的adapter層
  • 3.通用于所有平臺(tái)的內(nèi)核層

java層會(huì)通過反射嘗試獲取不同環(huán)境依賴下的cronetProvider立倍,也就是Cornet的內(nèi)核提供器。有的是依賴Google環(huán)境侣滩,有的可以自己自己直接依賴native的包口注,都沒有則使用默認(rèn)的 android自帶的網(wǎng)絡(luò)請求。

jni層君珠,實(shí)際上就是末尾帶上了Adapter的類以及和java層中相同類名的類寝志,這些類一般不做任何事情,一般會(huì)包裹一個(gè)對應(yīng)相同名字的cpp對象在native中策添,并且把相同的行為賦予給Adpater以及對應(yīng)的native對象材部。

  • java層CronetUrlRequestContext 會(huì)對應(yīng)上 jni中的CronetURLRequestContextAdapter作為樞紐,間接控制native中的CronetURLRequestContext唯竹。而CronetURLRequestContext則是控制了整個(gè)請求的上下文

  • java層CronetUrlRequest 會(huì)對應(yīng)上jni中的CronetURLRequestAdapter,并間接控制native層的CronetUrlRequest對象乐导。

而這個(gè)對象最終會(huì)控制native層的UrlRequest,而這個(gè)對象最終會(huì)通向Cronet的內(nèi)核層浸颓。并且會(huì)從thridParty文件夾中找到quic協(xié)議相關(guān)的處理物臂。

后續(xù)的文章將會(huì)繼續(xù)揭曉HttpTransaction如何進(jìn)行事務(wù)流轉(zhuǎn),并且quic是如何執(zhí)行猾愿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹦聪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒂秘,更是在濱河造成了極大的恐慌泽本,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姻僧,死亡現(xiàn)場離奇詭異规丽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撇贺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門赌莺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人松嘶,你說我怎么就攤上這事艘狭。” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵巢音,是天一觀的道長遵倦。 經(jīng)常有香客問我,道長官撼,這世上最難降的妖魔是什么梧躺? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮傲绣,結(jié)果婚禮上掠哥,老公的妹妹穿的比我還像新娘。我一直安慰自己秃诵,他們只是感情好续搀,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著顷链,像睡著了一般目代。 火紅的嫁衣襯著肌膚如雪屈梁。 梳的紋絲不亂的頭發(fā)上嗤练,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音在讶,去河邊找鬼煞抬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛构哺,可吹牛的內(nèi)容都是我干的革答。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼曙强,長吁一口氣:“原來是場噩夢啊……” “哼残拐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碟嘴,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤溪食,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后娜扇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體错沃,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年雀瓢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枢析。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刃麸,死狀恐怖醒叁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤把沼,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布断傲,位于F島的核電站,受9級(jí)特大地震影響智政,放射性物質(zhì)發(fā)生泄漏认罩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一续捂、第九天 我趴在偏房一處隱蔽的房頂上張望垦垂。 院中可真熱鬧,春花似錦牙瓢、人聲如沸劫拗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽页慷。三九已至,卻和暖如春胁附,著一層夾襖步出監(jiān)牢的瞬間酒繁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國打工控妻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留州袒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓弓候,卻偏偏與公主長得像郎哭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子菇存,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 在平時(shí)工作的過程中夸研,不斷總結(jié)與完善自己的技術(shù)體系時(shí),現(xiàn)在初步把a(bǔ)ndroid的開發(fā)技術(shù)體系分為9大技術(shù)模塊...
    Android開發(fā)_Hua閱讀 2,126評(píng)論 0 3
  • 機(jī)遇 一直在工作上忙著做不完的需求依鸥,很久沒有學(xué)習(xí)到新的知識(shí)點(diǎn)了亥至。在某次機(jī)緣巧合下,讓我有機(jī)會(huì)學(xué)習(xí)毕籽、接觸網(wǎng)絡(luò)相關(guān)的工...
    Magic旭閱讀 3,428評(píng)論 0 2
  • 用兩張圖告訴你抬闯,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,745評(píng)論 2 59
  • 三关筒、高級(jí)開發(fā)技術(shù)面試題 這里講的是大公司需要用到的一些高端Android技術(shù)溶握,這里專門整理了一個(gè)文檔,希望大家都可...
    上善若水0819閱讀 26,322評(píng)論 0 47
  • 很難找到一款完全不需要網(wǎng)絡(luò)的應(yīng)用蒸播,即使是單機(jī)應(yīng)用睡榆,也會(huì)存在數(shù)據(jù)上報(bào)萍肆、廣告等各種各樣的網(wǎng)絡(luò)請求 網(wǎng)絡(luò)基礎(chǔ) Http ...
    今陽說閱讀 2,362評(píng)論 0 12