云音乐Android Cronet接入实践_onet弱网

public abstract void onRedirectReceived(UrlRequest var1, UrlResponseInfo var2, String var3) 
public abstract void onResponseStarted(UrlRequest var1, UrlResponseInfo var2)
public abstract void onReadCompleted(UrlRequest var1, UrlResponseInfo var2, ByteBuffer var3) 
public abstract void onSucceeded(UrlRequest var1, UrlResponseInfo var2);
public abstract void onFailed(UrlRequest var1, UrlResponseInfo var2, CronetException var3);

这两个 interceptor 主要负责的是核心的网络请求的全部后续细节,Cronet 有自己来接管自然无需适配。

2、eventlistener适配

由于 okhttp eventlistener 依赖的一些回调例如 connectEnd、dnsEnd 等是在这两个拦截器中调用的,虽然Cronet 有自己的是 Callback:


public abstract void onRedirectReceived(UrlRequest var1, UrlResponseInfo var2, String var3) 
public abstract void onResponseStarted(UrlRequest var1, UrlResponseInfo var2)
public abstract void onReadCompleted(UrlRequest var1, UrlResponseInfo var2, ByteBuffer var3) 
public abstract void onSucceeded(UrlRequest var1, UrlResponseInfo var2);
public abstract void onFailed(UrlRequest var1, UrlResponseInfo var2, CronetException var3);

但是没有 okhttp eventlistener 提供的全面,如果需要完整的实现 okhttp eventlistener,需要对 Cronet 的核心关键请求点做改造来透出给 java 层,考虑到成本和使用场景,我们没有对这部分做改造,而是直接采用 Cronet 的 callback 做桥接来实现了部分的核心 eventlistener 的 callback。

3、超时逻辑适配

业务侧指定请求的超时时间来做一些策略也是常见的操作,而 Cronet 并未提供超时相关的 api,于是我们基于Cronet 源码开发了建链超时和读流超时等能力

void CronetURLRequest::SetOriginRequestID(uint32_t origin_request_id)
void CronetURLRequest::SetConnectTimeoutDuration(uint32_t connect_timeout_ms)

并通过 jni 暴露给 java 层,java 层通过适配层桥接到 Okhttp 接口:

CronetUrlRequest.java类

mRequestContext.onRequestStarted();
if (mInitialMethod != null) {
if (!nativeSetHttpMethod(mUrlRequestAdapter, mInitialMethod)) {
throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
}
}
if (mRequestId > 0) {
nativeSetOriginRequestID(mUrlRequestAdapter, mRequestId);
}
// 将业务侧设置的超时时间传递到Cronet
if (connectTime > 0) {
nativeSetConnectTimeoutDuration(mUrlRequestAdapter, (int) connectTime);
}
// 将业务侧设置的超时时间传递到Cronet
if (readTime > 0) {
nativeSetReadTimeoutDuration(mUrlRequestAdapter, (int) readTime);
}

这样上层业务侧无需任何改动既可继续使用 Okhttp 原有能力。

网络请求适配

1、请求维度适配

发起请求时,由原先的通过 Okhttp 内置 interceptor 发起请求切换到使用 Cronet 发起请求后,需要在 Okhttp 接口到 Cronet 接口间做一下请求和响应的适配转换。

网络请求切换示意

同时由于将之前的一些 java 层网络策略下沉到 C++ 实现,之前的一些 java 层的直接调用和传参我们通过基于CronetUrlRequest 进行扩展打通了向 Cronet 的 jni 调用

2、全局调用适配

下沉到 C++ 的网络策略,为尽可能做到和 Cronet 原有代码的解耦,在 C++ 以一个个独立插件形式存在。java 侧通过 CronetRequestContext 设置到 C++ 侧,然后向对应注册的组件进行分发,这个链路上涉及到 java、jni 和C++ 的代码改动,为了降低后续网络策略的开发维护成本,采用了类 JsBridge 的方法,开发了’CppBridge’,将java 和 C++ 之间的方法调用协议化,通过 json 传递数据,这样避免了后续对插件做更新带来的 java 到 C++ 请求链路上繁琐的开发工作,且 C++ 策略可以通过java层的配置中心能力进行动态配置。

解决问题

1、线程优化

众所周知,网络请求需要在子线程中发起,在 Cronet 的官方文档介绍中,推荐通过传入 Executor 来负责执行网络请求:

然后在 okhttp interceptor 中已经是子线程的执行环境,如果仍然传入独立对 executor,会造成不必要的线程切换和时间消耗。通过查看 Cronet 源码,发现其 CronetHttpURLConnection 使用的 MessageLoop 类实现是在当前线程,使用 MessageLoop 即可减少不必要的多余线程引入。

通过 MessageLoop 请求生命周期

2、兼容性解决

不同网络库之间切换,兼容性问题在所难免。虽然同样遵循 http 协议,但是对于一些边界条件的处理不一致或处理严格程度不同也会引起兼容性偏差。篇幅所限,这里仅介绍几个兼容点:

  1. Cronet 库对于http链接数设置为了6个,如果有对于 http 请求的不当使用,例如不正常持有未释放,一旦达到了6个,后续的请求将会 block 直到前序连接资源释放,这在 http1.1 下更容易触发;
  2. Cronet 对请求做了检测,如请求 body 未设置 Content-Type,将会直接抛出异常,
if (!hasContentType) {
    throw new IllegalArgumentException("Requests with upload data must have a Content-Type.");
}

在某些特殊设置情况下,存在有 request body 未设置 Content-Type 的情况将会直接导致请求抛异常;

  1. Cronet 请求返回4xx时,会直接抛出异常,而 okhttp 是通过将结果连带 code 返回到上层,交由使用者自己去处理。

兼容性优化没有统一的解决办法,只能见招拆招,通常是向前保证兼容性或推动优化不合理代码来解决。

3、重定向问题解决

Http 请求发生重定向时,请求 header 中的 Host 字段需要更新为新的目标主机地址,否则服务端校验Host字段和实际请求的 host 不一致时会拒绝请求。首先看一下 Okhttp 是如何实现的这个功能:

okhttp 在 RetryAndFollowUpInterceptor 类中,302会触发重新构建请求对象:

之后在 BridgeInterceptor 中,重新设置 Host:

而 Cronet 在 android 侧的默认实现中,并未对此进行更新,查看cronet代码:

类:cronet_url_request.cc

可以看到,cronet 下层接口是支持对重定向时传入修改的 header 的,但是默认传入了空,也没有提供暴露给 java 侧的接口来进行设置。

解决方案:对 cronet 重定向时更新 header 的能力进行打通,新增设置接口:

void CronetURLRequest::NetworkTasks::SetRedirectHeader(
    const std::string& key,
    const std::string& value) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  DCHECK(url_request_.get());
  if (redirect_request_headers_ == base::nullopt) {
    redirect_request_headers_ = base::make_optional<net::HttpRequestHeaders>();
  }
  redirect_request_headers_->SetHeader(key, value);
}

在重定向时将从 java 侧设置下来的 header 传入:

  @Override
    protected void handleRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
        try {
            Uri newUri = Uri.parse(newLocationUrl);
            String host = newUri.getHost();
            // 更新Host
            request.setRedirectHeader("Host", host);
            request.followRedirect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

cronet 执行 FollowDeferredRedirect (真正重定向的方法)时,将原有方法替换为传入重定向 header 的方法:

void CronetURLRequest::NetworkTasks::FollowDeferredRedirect() {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
#if defined(WOW_BUILD)
  url_request_->FollowDeferredRedirect(
      this->redirect_request_headers_ /* modified_request_headers */);
#else
  url_request_->FollowDeferredRedirect(
      base::nullopt /* modified_request_headers */);
#endif
}

灰度&上线

网络库切换牵扯业务的方方面面,影响面较大,上线需要格外谨慎:

  1. 在上线前的开发阶段,在开发环境提前切换到 Cronet,如果有问题可以尽早暴露;

  2. 灰度阶段反复分流验证,结合稳定性平台和舆情信息反馈观察,确保 Cronet sdk 的稳定性;

  3. 技术上,为了防止有其他异常情况引起的网络不可用,对非网络抖动引起的网络请求异常自动降级到 Okhttp,达到一定次数后开始彻底降低回 Okhttp,并上报日志进行分析;对网络组件以最小粒度进行动态配置,保证根据任意的组件都可以按需更新/开闭以进行线上ab效果观测;对网络请求各阶段的进行全面端到端数据埋点。

  4. 上线后,拉长观测周期,分阶段放量。反复从各个维度比对网络性能数据,发现异常数据及时分析定位解决,确保数据是完全正向的。分析维度包括:

    • 首包时长/请求时长
    • 错误率
    • 长尾数据分析
    • 业务体感数据这个阶段相对较为漫长,通常是从数据侧发现问题后,结合对应的业务场景去进一步定位问题,在针对不同具体错误类型的数据分析过程中,我们也发现了一些上层非正常使用带来的错误率问题,并一起促进优化降低了部分场景的错误率。

目前 android cronet 已经线上全量稳定运行了一年多时间,从统计数据来看,主站api请求时长有16%的优化,错误率有4%的优化,cdn请求不同域名也有不同程度的优化。

后续规划

弱网场景的特殊优化是业务开发中经常遇到的,云音乐基于 Cronet 的 nqe 模块做二次开发,对外提供弱网检测通知能力(正在进行中);

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

[外链图片转存中…(img-l6nqCYaC-1720119035901)]

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-aMocaVWV-1720119035901)]

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值