译者 | 布加迪
审校 | 重楼
作为一名安卓开发者,我解决漏洞、衡量性能或改善应用程序整体体验的第一反应是在本地进行测试和分析。像Android Studio Profiler这样的工具提供了强大的功能来检测和解决各种性能问题,比如UI线程阻塞、内存泄漏或过多的CPU使用。
虽说这类本地工具不可或缺,但它们也有局限性。某些问题在受控制的环境中才会重现,这种环境有一致的网络连接、可预测即稳定的用户行为和有限的测试设备种类。而在实际情形下,用户以意想不到的方式与应用程序交互,面对不同的硬件和不同的情形,暴露出难以在本地重现的问题。
这时候OpenTelemetry就有了用武之地。
OpenTelemetry框架用于收集、处理和导出有关应用程序性能的数据。虽然对于移动端来说比较新,但它已成为一种快速增长的后端性能管理标准。
移动端使用这个框架的好处很显著。OpenTelemetry使开发者能够从生产环境中收集可观测性数据,为深入了解应用程序的真实行为提供了窗口。
本地分析和生产级可观测性
本地分析的作用
本地分析对于识别在受控制环境中可再现的问题非常重要。
有许多常见问题可以在本地加以检测和解决:
- 主线程阻塞:阻塞主线程的任务可能导致应用程序冻结或应用程序无响应(ANR),因为主线程负责处理用户交互和呈现用户界面(UI)。
- 内存泄漏:当不再需要的对象没有被正确释放时,就会发生内存泄漏。这将导致过多的内存使用,从而可能导致内存不足(OOM)错误。
- 与容量相关的卡顿:当CPU或GPU等某些资源负担过重时,UI可能无法在给定的时间内正确呈现。
这些问题在测试过程中很容易重现,本地分析工具非常适合检测和修复这些问题。
需要生产级可观测性时
虽然本地分析涵盖众多问题,但不是所有问题在本地环境中都很显然出现。生产级可观测性对于诊断至关重要:
- 意外的用户行为:用户可能会上传大堆文件、执行快速操作,或者以意外的方式浏览应用程序,从而暴露出极端情况。
- 设备特有的崩溃:安卓的多样性意味着问题可能出现在特定的设备或操作系统版本上,通常在本地测试期间无法检测到。
- 网络连接差:现实世界的用户常面临互联网缓慢或不可靠,导致超时中断或长时间加载,这种情况很难模拟。
OpenTelemetry等生产级就绪的可观测性工具对于发现和克服这些挑战至关重要。
安卓中的OpenTelemetry
OpenTelemetry是一种强大的可观测性框架,可以帮助开发者收集、处理和导出跟踪(trace)、度量指标和日志等遥测数据。
与专有工具相比,使用OpenTelemetry监测性能有许多优点。基于OpenTelemetry的SDK非常灵活,允许工程师轻松地将他们的检测机制扩展到第三方库。作为一种被广泛采用的开源框架,OpenTelemetry还帮助组织避免供应商锁定,对自己的数据拥有更大的控制度。
通过将OpenTelemetry集成到安卓应用程序中,你可以跟踪单个操作的性能、识别瓶颈,并深入了解你的应用程序在各种实际情形下的性能表现。如何做到这一点呢?
初始集成和设置
如果要将OpenTelemetry SDK添加到你的应用程序中,你可以添加OTel材料清单以及一些必要的依赖项,如下所示:
// libs.versions.toml
[versions]
opentelemetry-bom = "1.44.1"
opentelemetry-semconv = "1.28.0-alpha"[libraries]
opentelemetry-bom = { group = "io.opentelemetry", name = "opentelemetry-bom", version.ref = "opentelemetry-bom" }
opentelemetry-api = { group = "io.opentelemetry", name = "opentelemetry-api" }
opentelemetry-context = { group = "io.opentelemetry", name = "opentelemetry-context" }
opentelemetry-exporter-otlp = { group = "io.opentelemetry", name = "opentelemetry-exporter-otlp" }
opentelemetry-exporter-logging = { group = "io.opentelemetry", name = "opentelemetry-exporter-logging" }
opentelemetry-extension-kotlin = { group = "io.opentelemetry", name = "opentelemetry-extension-kotlin" }
opentelemetry-sdk = { group = "io.opentelemetry", name = "opentelemetry-sdk" }
opentelemetry-semconv = { group = "io.opentelemetry.semconv", name = "opentelemetry-semconv", version.ref = "opentelemetry-semconv" }
opentelemetry-semconv-incubating = { group = "io.opentelemetry.semconv", name = "opentelemetry-semconv-incubating", version.ref = "opentelemetry-semconv" }// build.gradle.kts
implementation(platform(libs.opentelemetry.bom))
implementation(libs.opentelemetry.api)
implementation(libs.opentelemetry.context)
implementation(libs.opentelemetry.exporter.otlp)
implementation(libs.opentelemetry.exporter.logging)
implementation(libs.opentelemetry.extension.kotlin)
implementation(libs.opentelemetry.sdk)
implementation(libs.opentelemetry.semconv)
implementation(libs.opentelemetry.semconv.incubating)
然后,我们可以创建一个 OpenTelemetry 实例,充当中央配置点,管理跟踪器提供程序(tracer provider)、资源和导出器。
跟踪器提供程序创建和管理跟踪器,跟踪器又生成跨度(span)。资源包含有关应用程序的元数据,并附加到每个跨度上,有助于将遥测数据情境化。导出器定义遥测数据将发送到何处,比如后端可观测性平台或本地文件以便检查。
// Resources that will be attached to telemetry to provide better context.
// This is a good place to add information about the app, device, and OS.
val resource = Resource.getDefault().toBuilder()
.put(ServiceAttributes.SERVICE_NAME, "[app name]")
.put(DeviceIncubatingAttributes.DEVICE_MODEL_NAME, Build.DEVICE)
.put(OsIncubatingAttributes.OS_VERSION, Build.VERSION.RELEASE)
.build()// The tracer provider will create spans and export them to the configured span processors.
// For now, we will use a simple span processor that logs the spans to the console.
val sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()))
.setResource(resource)
.build()
// The OpenTelemetry SDK is the entry point to the OpenTelemetry API. It is used to create spans, metrics, and other telemetry data.
// Create it and register it as the global instance.
val openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.buildAndRegisterGlobal()
初始化所有内容后,我们可以使用 openTelemetry.sdkTracerProvider.get() 获取跟踪器并创建跨度。
跟踪表示分布式系统中的单个操作或工作流。针对安卓应用程序,它可以捕获用户请求或操作在应用程序中的整个过程。在此过程中,跨度表示单个工作单元,比如网络请求、数据库查询或 UI 渲染任务,提供了有关其持续时间和上下文的详细信息。代码如下所示:
val tracer = openTelemetry.sdkTracerProvider.get("testAppTracer")
val span = tracer.spanBuilder("someUserAction").startSpan
try {
someAction()
} catch (e: Exception) {
span.recordException(e)
span.setStatus(StatusCode.ERROR)
} finally {
span.end()
}
使用 OpenTelemetry 解决问题
我们已了解了如何在我们的安卓应用程序中创建OpenTelemetry 实例,下面给出一些常见类型的问题以及这个框架如何切实帮助我们跟踪问题。
网络延迟问题
网络性能是生产环境中最不可预测的因素之一。虽然本地测试是在稳定高速的环境下进行,但实际用户面临各种不同的情形。在流量高峰期间,他们可能会遇到间歇性的移动连接、不可靠的公共 Wi-Fi 或后端延迟。这些挑战可能导致请求时间过长、操作失败,甚至应用程序被丢弃。
使用 OpenTelemetry,你可以检测网络请求以测量其持续时间并识别瓶颈。通过使用元数据(比如端点 URL、请求大小或响应状态)标记跨度,你可以分析以下趋势:
- 导致最长延迟的端点:识别持续性能欠佳的 API ,并优先优化它们。
- 网络情况对用户体验造成的影响:将高延迟跨度与用户流失关联起来,以衡量响应缓慢的影响。
- 响应时间在不同地区的变化:了解性能在各地区的差异,并针对受影响最严重的地区量身定制改进措施。
如例子所示:
假设我们有一个端点,用户将图像上传到服务器。网络性能可能会因图像大小、用户位置或连接类型而异。如果使用 OpenTelemetry 检测网络请求,我们可以捕获相关元数据并分析趋势,比如较大的图像或特定区域是否与较长的上传时间相关。以下是我们可以检测这种场景的方法:
fun uploadImage(image: ByteArray, networkType: String, region: String) {
val span = tracer.spanBuilder("imageUpload")
.setAttribute(HttpIncubatingAttributes.HTTP_REQUEST_SIZE, image.size.toLong())
.setAttribute(NetworkIncubatingAttributes.NETWORK_CONNECTION_TYPE, networkType)
.setAttribute("region", region)
.startSpan()
try {
doNetworkRequest()
} catch (e: Exception) {
span.recordException(e)
span.setStatus(StatusCode.ERROR)
} finally {
span.end()
}
}
操作系统版本或设备特有的问题
安卓的生态系统非常庞大,应用程序可以在各种设备、操作系统版本和硬件配置上运行。这种多样性使得确保所有设备上有一致的用户体验变得具有挑战性。某些崩溃或错误可能只会在特定设备上或在特定情形下出现,因此很难在受控制的测试环境中发现它们。
使用 OpenTelemetry,你可以集中捕获设备特有的元数据,并在OpenTelemetry设置期间将其添加到资源配置中。这确保了重要的上下文信息自动附加到跨度、日志和度量指标上。这种方法确保了跨遥测数据的一致性。
如果分析这种元数据,你可以发现以下趋势:
- 在某些型号的设备上频繁崩溃:使用较旧或廉价设备的用户可能会因资源不足而遇到崩溃,检测这种模式可能便于优化内存使用或提供更轻量级的应用程序。
- 安卓版本之间的行为变化:由于安卓API方面的变化、更严格的权限要求或更新中引入的错误,某些崩溃可能仅发生在特定的操作系统版本上。借助这些数据,你可以优先考虑兼容性修复或更新应用程序的依赖项,以避免使用弃用的 API。
- 硬件特有的渲染问题:一些设备可能有独特的图形驱动程序或硬件问题,从而导致渲染问题,比如 UI 中的视觉故障或伪影。比如说,自定义动画在非标准屏幕分辨率或刷新率的设备上可能会出现异常。有关屏幕规格或 GPU 详细信息的元数据有助于查明并解决这些不一致问题。
具体如下设置:
// Add some useful attributes to the Resource object.
val resource = Resource.getDefault().toBuilder()
.put("device.model", Build.MODEL)
.put("device.manufacturer", Build.MANUFACTURER)
.put("os.version", Build.VERSION.SDK_INT.toString())
.put("screen.resolution", getResolution())
.build()// Use the resource object to build the tracer, logs and other telemetry providers
val sdkTracerProvider = SdkTracerProvider.builder()
.setResource(resource)
.build()
意外的用户行为
真实用户经常以意想不到的方式与应用程序交互。这种不可预测性可能会导致性能问题、崩溃或未优化的用户体验,这些在本地测试中是无法发现的。
比如说,用户可能会上传比预期大得多的文件,从而导致内存或性能瓶颈。其他用户可能快速重复执行操作,比如提交表单或刷新页面,导致竞态条件或服务器过载。一些用户可能会以未经测试的顺序浏览应用程序,从而触发意外状态或错误。
如果利用OpenTelemetry来监测用户交互,你可以捕获和分析详细说明用户实际如何使用你应用程序的跨度。这些数据有助于深入了解意外模式,使你能够:
- 检测资源密集型操作:跟踪表示图像上传、数据库查询或 API 调用等操作的跨度,以识别过度使用影响性能的场景。
- 发现不常见的导航路径:通过监控用户导航流程,你可以发现经常导致错误或崩溃的顺序,从而帮助你优先提供处理实际问题的方法。
- 识别高需求功能:分析跨度以查看哪些操作或功能最常使用,即使它们不是你初始测试用例的一部分。这可以指导优化工作和功能优先考虑。
设想如下场景:用户经常在两个屏幕(比如产品列表和产品详细信息页面)之间快速连续地来回导航。虽然这种行为看似无害,但可能会无意中导致资源泄漏或降低渲染性能。
如果使用导航元数据(比如屏幕名称、时间戳和其他一些用户交互)标记跨度,你可以分析导航行为中的模式:
- 用户可能以意外的高频率在屏幕之间切换,表明了需要缓存或延迟加载机制,以减轻资源压力。
- 特定屏幕可能持续产生错误,从而暴露渲染逻辑方面的极端情况或瓶颈。
- 深入了解导航顺序有助于优化用户体验流程,使应用程序对常见行为更简单直观,同时更从容地处理极端情况。
这种发现和解决意外用户行为的能力可确保你的应用程序即使在非常规使用场景下也能保持可靠性和高性能。
下一步:将数据转发到你想要分析的地方
正如我们所讨论的,使用OpenTelemetry 检测你的安卓应用程序对于监测和了解常见的性能问题大有帮助。
一旦你开始收集数据,就需要为它设置一个存放位置。OpenTelemetry 这种框架的一大优点是,有许多可观测性工具支持摄取这种类型的数据。你可以选择将其转发到特定供应商的后端或各种开源工具,比如面向跨度的 Jaeger 或面向日志的 Loki。
从SDK转发OpenTelemetry数据需要添加一个或多个导出器,以便在实际生成数据后为你的数据提供目的地。
导出器是一个组件,它将你在使用的用于捕获数据的SDK与用于接收数据的外部OpenTelemetry 收集器连接起来。导出器在设计之初考虑到了OpenTelemetry 数据模型,可以导出OpenTelemetry数据而不会丢失任何信息。市面上有许多针对特定语言的导出器:https://opentelemetry.io/ecosystem/registry/?compnotallow=exporter&language=。
OpenTelemetry 收集器是一种与供应商无关的接收、处理和导出遥测数据的方法。它并不总是必要的,因为你可以通过导出器将数据直接发送到所选择的后端。如果你在管理多个数据摄取源,并发送到多个可观测性后端,拥有收集器不失为一种好办法。它允许你的服务快速卸载数据,收集器可以处理其他操作,比如重试、批处理、加密,甚至敏感数据过滤。
结语
如今,应用程序运行在无数设备上、不同环境中、被不同用户使用,获得最佳性能和可靠性需要的不仅仅是本地测试。虽然像Android Studio Profiler这样的工具擅长解决受控制环境中可重现的问题,但生产级可观测性填补了这一空白:发现只有在特定条件下才会出现的实际问题。
OpenTelemetry为收集和分析遥测数据提供了一种强大的框架,便于开发者深入了解和优化生产级应用程序。通过检测跨度和附加有意义的元数据,你就可以精准确定瓶颈、诊断设备或操作系统特有的问题,并发现影响应用程序性能或用户体验的意外用户行为。
原文标题:Solving Android app issues with OpenTelemetry: Beyond local profiling,作者:Francisco Prieto Cardelle