CnOps 社区
首页开源项目实践文章视频课程常见问答开发者工具
导航菜单
首页开源项目实践文章视频课程常见问答开发者工具

CnOps 社区

愿景

CnOps 社区是一个以"智能运维与可观测"为核心的开放、包容、分享的技术社区,旨在聚集运维专家、开发者和爱好者,共同探讨、学习和分享可观测最佳实践与最新技术,与众多技术社区合作互动,共同探讨交叉领域的技术挑战,推动可观测领域的创新与进步。

内容社区

  • 实践文章
  • 视频课程
  • 开源项目
  • 常见问答
  • 开发者工具

友情链接

  • Prometheus
  • Grafana Lab
  • OpenTelemetry
  • LoongCollector

关注我们

阿里云云原生公众号阿里云云原生
阿里云可观测公众号阿里云可观测

Copyright © 2026 CnOps 社区. All rights reserved.

首页实践文章RUM 实战:用数据说话的 Android 网络性能优化

RUM 实战:用数据说话的 Android 网络性能优化

#RUM实战#监控#日志#指标#追踪#性能优化

agenticOps | 2026-05-23

概述

在移动互联网时代,网络请求性能已成为影响用户体验的关键因素。据统计,转化率会随着页面加载时间增加大幅下降,而移动应用中最常遇到的用户投诉都与"加载慢"、"卡顿"等网络性能问题相关。然而,移动端网络环境的复杂性远超Web端:

网络环境多样化

  • WiFi、4G/5G、3G、2G等多种网络制式共存
  • 信号强弱变化、网络切换频繁
  • 不同地域、运营商的网络质量差异巨大

设备碎片化严重

  • Android设备品牌、型号众多
  • 系统版本从Android 5.0到最新版本跨度大
  • 设备性能参差不齐,影响网络处理能力

问题排查困难

  • 缺乏可见性:传统监控只能看到请求成功/失败和总耗时,无法了解具体耗在哪个环节
  • 难以复现:用户反馈"很慢",但开发环境下往往无法复现
  • 缺少量化依据:凭感觉优化,无法评估优化效果
  • 端到端追踪缺失:客户端日志缺失,与服务端监控割裂,无法形成完整链路

为了解决上述痛点,我们需要将网络请求的"黑盒"变成"透明盒",清晰地看到每个环节的耗时。阿里云RUM Android SDK提供了移动端网络性能监控能力。接下来,我们将详细介绍RUM SDK采集的资源指标数据模型,帮助你理解每个指标的含义和计算方式。

资源指标数据说明

要让每个网络请求的各个阶段都清晰可见、可量化,首先需要建立一套标准化的数据模型。阿里云RUM采用Resource事件作为网络请求监控的核心数据模型。

Resource事件是专门针对网络请求设计的标准化事件类型,它基于HTTP协议和W3C Performance Timing API标准制定,确保了数据采集的准确性和可对比性。由于Performance API在不同平台环境(Web、iOS、Android、HarmonyOS)下存在实现差异,RUM针对这些差异进行了修正和对齐,使得无论是Web端还是移动端,开发者都能看到口径一致的性能数据,方便跨平台性能对比和问题排查。

接下来,我们将详细介绍Resource事件包含的属性字段和指标字段。

2.1 属性字段说明

Resource事件包含丰富的属性字段,用于描述请求的上下文信息:

属性类型描述
session.idstring关联的session
view.idstring关联的view
view.namestring关联的view.name
resource.typestring采集的资源类型 (eg: css, javascript, media, XHR, image,navigation).
resource.methodstringHTTP 请求方法 (eg: POST, GET).
resource.status_codestring资源状态码
resource.messagestring一般错误时补充的返回结果内容
resource.urlstring资源 URL
resource.namestring默认为url的path部分,可以基于规则进行匹配或用户主动配置
resource.provider_typestring资源提供者类型 (eg: first-party, cdn, ad, analytics).
resource.trace_idstring资源请求 traceId
resource.snapshotsstring资源的快照JSON String

2.2 指标字段说明

除了属性字段,Resource事件还包含了核心的性能指标,这部分数据是我们排查网络请求慢的核心数据。

指标类型描述
resource.successnumber资源加载是否成功,1 表示成功,0表示失败,-1 表示未知
resource.durationlong (ms)加载资源所花费的全部时间(responseEnd - redirectStart)
resource.sizelong (bytes)资源大小,对应decodedBodySize
resource.connect_durationlong (ms)与服务器建立连接所花费的时间(connectEnd - connectStart)
resource.ssl_durationlong (ms)TLS 握手所花费的时间。如果最后一个请求不是通过 HTTPS,则不会出现此指标 (connectEnd - secureConnectionStart)。这里需要特别判断一下,如果secureConnectionStart的值为0, 说明没有发起SSL连接,此时不计算ssl_duration,ssl_duration赋值为0
resource.dns_durationlong (ms)解析最后一个请求的 DNS 名称所花费的时间 (domainLookupEnd - domainLookupStart)
resource.redirect_durationlong (ms)重定向 HTTP 请求上的时间(redirectEnd - redirectStart)
resource.first_byte_durationlong (ms)等待接收响应的第一个字节所花费的时间(responseStart - requestStart)
resource.download_durationlong (ms)下载响应所用的时间 (responseEnd - responseStart)

2.3 请求耗时阶段说明

一个完整的HTTPS请求通常包含以下关键阶段:

2.4 计算口径

了解了指标的定义,接下来我们深入了解Android端基于OkHttp3的具体计算实现。

2.4.1 OkHttp3计算口径

下表展示了Android 网络资源请求各阶段耗时计算口径,明确定义各个阶段的起止时间点和计算方法。

详细时间起始节点可在原始数据的resource.timing_data字段查看。

字段名计算公式(OkHttp回调阶段)含义(控制台展示)说明
resource.redirect_durationcallStart - (first)callStart重定向耗时

HTTP重定向的总耗时,从第一次请求到最后一次重定向完成的时间

  • 如果没有重定向,该值为 0
resource.dns_durationdnsEnd - dnsStartDNS查询耗时

域名解析耗时,将域名解析为IP地址所需的时间

  • 如果使用连接池复用连接,该值为 0(因为无需DNS解析)
resource.connect_durationconnectEnd - connectStartTCP连接耗时

与服务器建立连接的总耗时,包含TCP三次握手和SSL/TLS握手时间

  • 如果使用连接池复用连接,该值为 0
resource.ssl_durationsecureConnectEnd - secureConnectStartSSL安全连接耗时

SSL/TLS安全连接握手耗时

  • 仅在HTTPS请求时有值,HTTP请求该值为 0
  • 如果使用连接池复用连接,该值为 0
resource.first_byte_durationresponseHeadersStart - requestHeadersStart请求响应耗时从请求开始发送到收到响应首字节的时间
resource.download_durationresponseBodyEnd - responseHeadersStart内容传输耗时响应体下载耗时,从开始接收响应到完全接收的时间
resource.durationresponseBodyEnd - callStart资源加载总耗时资源加载的全部耗时,从请求开始到响应完全接收的总时长

说明:控制台展示的"TCP连接耗时"实际上包含了SSL握手时间

2.4.2 连接复用识别

根据RUM SDK采集到的指标数据,我们可以识别连接是否复用,判断依据如下:

判断依据:

  • connectionAcquiredTime > 0:连接已获取
  • dnsStartTime <= 0:没有DNS解析回调
  • tcpStartTime <= 0:没有TCP连接回调

连接复用时的特征:

  • resource.dns_duration = 0
  • resource.connect_duration = 0
  • resource.ssl_duration = 0
  • 存在 callStart → connectionAcquired 的等待时间(连接池查找时间)

这个等待时间是一个重要的性能指标,如果过长可能说明连接池配置不当。

2.4.3 TCP与SSL连接关系

对于HTTPS请求,连接建立分为两个阶段:

connectStart (TCP开始)
↓
[TCP三次握手]
↓
secureConnectStart (SSL握手开始)
↓
[SSL/TLS握手]
↓
secureConnectEnd (SSL握手结束)
↓
connectEnd (连接建立完成)

时间关系:

总连接时间 = connectEnd - connectStart
纯TCP时间 = secureConnectStart - connectStart (近似)
SSL时间 = secureConnectEnd - secureConnectStart

2.5 控制台指标查看

进入RUM控制台-选择您的应用-点击“API请求”模块-点击具体的一条明细详情,可以查看请求各阶段耗时与耗时分布

理解了数据模型和计算口径后,让我们通过一个真实的线上案例,看看如何利用这些指标数据快速定位性能问题。

真实用户案例分析

3.1 案例背景

某APP收到线上用户投诉,反馈"页面加载特别慢"、"经常转圈超过1秒"。开发团队第一时间排查后端服务,却发现了一个令人困惑的现象:客户端反馈某核心接口响应时间经常超过1秒(部分用户甚至达到2-3秒),无论WiFi还是4G网络环境都存在此问题,且具有随机性,在开发环境中难以稳定复现;而后端监控却显示接口服务端处理时间稳定在400ms左右,数据库查询性能正常无慢查询,服务器CPU、内存负载也都健康。两边数据对不上!客户端说1.2秒,服务端只用了400ms,那剩下的800ms去哪了?在没有细粒度监控的情况下,团队陷入了"盲人摸象"的困境:客户端和服务端相互甩锅,问题迟迟无法解决。通过接入阿里云RUM Android SDK,我们采集到了详细耗时数据,看看问题是如何被精准定位的。

3.2 Timing Data原始数据

在resource.timing_data字段中,我们获取到了请求各阶段的原始时间点(单位:纳秒):

{
"requestHeadersEnd": 1560814315115219,
"responseBodyStart": 1560814719308917,
"requestType": "OkHttp3",
"connectionAcquired": 1560814312934751,
"connectionReleased": 1560814721700948,
"requestBodyEnd": 1560814315850323,
"responseHeadersEnd": 1560814718722250,
"requestHeadersStart": 1560814312975011,
"responseBodyEnd": 1560814719441625,
"requestBodyStart": 1560814315146573,
"callEnd": 1560814721840948,
"duration": 1232825780,
"callStart": 1560813486615845,
"responseHeadersStart": 1560814718314125
}

关键观察:

  • 没有 DNS/TCP/SSL 相关的回调时间点 → 说明使用了连接池复用
  • callStart 到 connectionAcquired 间隔 826ms → 连接池等待时间异常长
  • 总耗时 duration = 1232.8ms

这里已经有了明确的线索:问题不是出在DNS、TCP或SSL握手上,而是等待连接池分配连接的时间过长。

3.3 详细阶段分析

基于原始数据,结合2.4的数据计算口径,我们进行逐阶段耗时计算,精准定位性能瓶颈:

阶段1:等待连接池分配

callStart → connectionAcquired
耗时: (1560814312934751 - 1560813486615845) / 1,000,000 = 826.32 ms ⚠️

说明:

  • 从连接池获取可用连接的等待时间
  • 没有 DNS/TCP 回调 = 复用现有连接
  • 这是最大的性能瓶颈!占总耗时的67%

阶段2:发送请求头

requestHeadersStart → requestHeadersEnd耗时: (1560814315115219 - 1560814312975011) / 1,000,000 = 2.14 ms ✅
requestBodyStart → requestBodyEnd耗时: (1560814315850323 - 1560814315146573) / 1,000,000 = 0.70 ms ✅
requestBodyEnd → responseHeadersStart耗时: (1560814718314125 - 1560814315850323) / 1,000,000 = 402.46 ms

说明: 服务器处理请求的时间,与后端日志吻合,正常范围内。

阶段5:接收响应头

responseHeadersStart → responseHeadersEnd耗时: (1560814718722250 - 1560814718314125) / 1,000,000 = 0.41 ms ✅
responseBodyStart → responseBodyEnd耗时: (1560814719441625 - 1560814719308917) / 1,000,000 = 0.13 ms ✅
responseBodyEnd → connectionReleased耗时: (1560814721700948 - 1560814719441625) / 1,000,000 = 2.26 ms ✅

通过这个分析,我们清晰地看到连接池等待时间是性能瓶颈。

3.4 问题诊断

异常点诊断

核心问题:连接池等待时间过长(826ms)

可能的原因:

  1. 连接池已满 - 所有连接都在使用中,需要等待其他请求释放连接
  2. 串行请求排队 - 对同一 Host 的请求过多,受限于 maxRequestsPerHost 配置
  3. 连接泄漏 - 之前的请求没有正确释放连接
  4. 连接池配置不当 - maxIdleConnections 设置过小

诊断步骤

Step 1: 检查连接池配置

// 查看当前OkHttpClient的连接池配置
ConnectionPool connectionPool = okHttpClient.connectionPool();
// 默认配置:最多5个空闲连接,保活5分钟

检查后发现:应用使用的是OkHttp默认配置,只有5个空闲连接。

Step 2: 监控同时进行的请求数量

通过RUM控制台查看该时间段内对同一Host的并发请求数量。

Step 3: 检查是否有连接泄漏

查看应用日志,确认所有请求都正确关闭了Response Body:

Response response = client.newCall(request).execute();
try {
String body = response.body().string();
// 处理响应
} finally {
response.close(); // 必须关闭!
}

诊断结论:

问题是由连接池配置过小导致的。大量请求在等待连接释放,造成严重的性能瓶颈。

明确了问题原因后,接下来我们将介绍常见的网络性能问题排查方法和优化思路。

常见问题最佳排查实践

通过上述案例,我们看到了如何利用RUM数据定位问题。本章节将系统性地介绍4类最常见的网络性能问题及其排查方法。

4.1 连接池等待时间过长

症状:在resource.timing_data中观察到连接获取耗时异常。

callStart → connectionAcquired 耗时 > 500ms

诊断步骤:

Step 1: 查看连接池配置

// 检查当前配置
ConnectionPool pool = okHttpClient.connectionPool();
// 默认:5个空闲连接

Step 2: 查看并发请求数

通过RUM控制台查看该时间段的并发请求数:

-- 在RUM控制台执行查询
SELECT 
COUNT(*) as concurrent_requests
FROM rum_resource
WHERE 
timestamp BETWEEN start_time AND end_time
AND resource.url LIKE 'https://api.example.com%'
GROUP BY timestamp
ORDER BY concurrent_requests DESC

Step 3: 检查连接泄漏

// 添加日志监控连接池状态
interceptor.addInterceptor(chain -> {
ConnectionPool pool = chain.connection().connectionPool();
Log.d("Pool", "Active: " + pool.connectionCount() + 
", Idle: " + pool.idleConnectionCount());
return chain.proceed(chain.request());
});

优化思路:

// 方案1:增加连接池大小
.connectionPool(new ConnectionPool(30, 5, TimeUnit.MINUTES))

// 方案2:增加每个Host的最大并发数
.dispatcher(new Dispatcher() {{
setMaxRequestsPerHost(10); // 默认5
setMaxRequests(64); // 默认64
}})

// 方案3:请求合并

4.2 DNS解析缓慢

症状:在控制台观察到DNS解析耗时持续偏高。

resource.dns_duration > 500ms

诊断步骤:

Step 1: 确认是DNS问题

检查 resource.dns_duration 是否持续偏高
检查不同网络环境(WiFi vs 4G)的差异

Step 2: 分析特定域名

// 在RUM控制台按域名分组
SELECT 
resource.url_host,
AVG(resource.dns_duration) as avg_dns_time,
MAX(resource.dns_duration) as max_dns_time
FROM rum_resource
WHERE resource.dns_duration > 0
GROUP BY resource.url_host
ORDER BY avg_dns_time DESC

解决思路:

// 方案1:使用自定义DNS
.dns(new CustomDns())

// 方案2:使用HttpDNS
.dns(new AliHttpDns())

// 方案3:DNS预解析
DnsPreloader.preload(client);

4.3 SSL握手耗时高

症状:在控制台观察到SSL握手耗时异常。

resource.ssl_duration > 1000ms

诊断步骤:

Step 1: 确认SSL版本

// 添加拦截器查看SSL信息interceptor.addInterceptor(chain -&gt; {    Connection connection = chain.connection();    if (connection != null) {        Handshake handshake = connection.handshake();        if (handshake != null) {            Log.d(&quot;SSL&quot;, &quot;Protocol: &quot; + handshake.tlsVersion());            Log.d(&quot;SSL&quot;, &quot;Cipher: &quot; + handshake.cipherSuite());        }    }    return chain.proceed(chain.request());});
// 在RUM控制台查询SELECT     COUNT(CASE WHEN resource.ssl_duration = 0 THEN 1 END) * 100.0 / COUNT(*) as reuse_rateFROM rum_resourceWHERE resource.url LIKE 'https://%'
// 方案1:启用SSL Session复用.sslSocketFactory(SslConfig.createSSLSocketFactory())// 方案2:增加连接保活时间.connectionPool(new ConnectionPool(30, 10, TimeUnit.MINUTES))  // 延长到10分钟// 方案3:使用证书固定.certificatePinner(certificatePinner)

4.4 TTFB过长

症状:从请求发送到收到首字节的时间过长,在控制台上观察“请求响应耗时”较长。

resource.first_byte_duration > 2000ms

诊断步骤:

Step 1: 排除客户端问题

确认以下指标正常:

  • DNS解析时间 < 300ms
  • 连接建立时间 < 500ms
  • 请求发送时间 < 100ms

Step 2: 分析服务器响应时间

TTFB主要由服务器处理时间决定,如果客户端指标正常,需要:

1. 检查服务器负载
2. 检查数据库查询性能
3. 检查接口业务逻辑复杂度
4. 使用APM工具追踪服务端性能

Step 3: 网络路径分析

// 通过RUM控制台查看不同地域/运营商的TTFB差异
SELECT 
user.region,
user.isp,
AVG(resource.first_byte_duration) as avg_ttfb
FROM rum_resource
GROUP BY user.region, user.isp
ORDER BY avg_ttfb DESC

优化思路:

// 方案1:使用CDN加速
// 将静态资源和API部署到CDN节点

// 方案2:启用服务器缓存
// 在服务端实现合理的缓存策略

// 方案3:数据预取
// 在用户可能访问前提前请求数据
PreloadManager.preload("https://api.example.com/user/profile");

// 方案4:请求优先级管理
.dispatcher(new Dispatcher() {{
// 高优先级请求使用单独的线程池
}})

案例小结

通过前面4类常见问题的排查方法,我们掌握了系统化的诊断思路。现在,让我们回到第3章那个困扰团队多日的真实案例——连接池等待时间826ms的性能瓶颈。通过RUM数据的精准定位,我们发现问题的根源在于连接池配置不当导致请求排队等待,而解决方案其实很简单:根据不同应用类型选择合适的连接池配置。

配置建议:

针对OkHttpClient的maxIdleConnections参数(默认值为5),建议根据应用特点进行调整,根据经验常见的配置如下:

  • 高并发应用:maxIdleConnections = 30-50 
    这类应用用户活跃度高,网络请求频繁且并发量大,需要充足的连接池支撑
  • 一般应用:maxIdleConnections = 10-20 
    适中的请求频率和并发量,保持适度的连接池规模即可
  • 低频应用:maxIdleConnections = 5-10 
    用户请求较少,保持默认配置或略微增加即可满足需求

从事后优化到主动监控:

然而,这个案例也给我们带来了更深层的思考——性能优化不应该是"亡羊补牢"式的事后补救。除了掌握事后排查和优化的方法,更重要的是建立一套完善的性能监控体系,通过RUM控制台实时掌握应用的网络性能指标,将"被动救火"转变为"主动观测"。如果有需要,还可以基于RUM平台自定义配置告警规则(如连接池等待时间P95 > 500ms时触发通知),进一步提升问题响应速度。

监控告警配置建议

RUM数据允许用户创建自定义告警进行实时监控,建立科学的监控告警体系,可以在问题影响用户之前及时发现并处理。

指标告警阈值参考

根据RAIL 模型、 Google Web Vitals 等业界实践,常见的阈值参考如下

指标告警阈值严重程度说明
resource.durationP95 > 3s严重资源加载总耗时
resource.first_byte_durationP95 > 800ms警告TTFB过长
resource.dns_durationP95 > 200ms提示DNS解析慢
resource.connect_durationP95 > 400ms警告连接建立慢
resource.ssl_durationP95 > 400ms提示SSL握手慢
连接池等待时间P95 > 500ms严重连接池配置不足
连接复用率< 70%警告连接未有效复用

总结

在移动应用开发中,网络请求性能直接影响用户体验。通过接入阿里云RUM Android SDK,开发者可以获得以下核心能力:

精准定位性能瓶颈

  • 细粒度的阶段耗时(DNS、TCP、SSL、TTFB等)帮助快速识别问题
  • 从"请求慢"的模糊描述,到"连接池等待826ms"的精准定位

连接复用分析

  • 自动识别连接池使用效率
  • 发现连接泄漏、连接池配置不当等隐藏问题

真实用户体验监控

  • 基于真实用户的网络环境采集数据
  • 按地域、运营商、网络类型等维度分析性能差异

数据驱动优化

  • 优化前后对比清晰可见
  • 建立性能基准和告警机制,持续改进

阿里云RUM针对 Android 端实现了对应用性能、稳定性、和用户行为的无侵入式监控采集SDK,可以参考接入文档体验使用。除了Android外,RUM也支持Web、小程序、iOS、鸿蒙等多种平台监控分析,相关问题可以加入“RUM 用户体验监控支持群”(钉钉群号: 67370002064)进行咨询。

文章大纲