譯者 | 布加迪
審校 | 重樓
作為一名安卓開發(fā)者,我解決漏洞、衡量性能或改善應用程序整體體驗的第一反應是在本地進行測試和分析。像Android Studio Profiler這樣的工具提供了強大的功能來檢測和解決各種性能問題,比如UI線程阻塞、內(nèi)存泄漏或過多的CPU使用。
雖說這類本地工具不可或缺,但它們也有局限性。某些問題在受控制的環(huán)境中才會重現(xiàn),這種環(huán)境有一致的網(wǎng)絡(luò)連接、可預測即穩(wěn)定的用戶行為和有限的測試設(shè)備種類。而在實際情形下,用戶以意想不到的方式與應用程序交互,面對不同的硬件和不同的情形,暴露出難以在本地重現(xiàn)的問題。
這時候OpenTelemetry就有了用武之地。
OpenTelemetry框架用于收集、處理和導出有關(guān)應用程序性能的數(shù)據(jù)。雖然對于移動端來說比較新,但它已成為一種快速增長的后端性能管理標準。
移動端使用這個框架的好處很顯著。OpenTelemetry使開發(fā)者能夠從生產(chǎn)環(huán)境中收集可觀測性數(shù)據(jù),為深入了解應用程序的真實行為提供了窗口。
本地分析和生產(chǎn)級可觀測性
本地分析的作用
本地分析對于識別在受控制環(huán)境中可再現(xiàn)的問題非常重要。
有許多常見問題可以在本地加以檢測和解決:
- 主線程阻塞:阻塞主線程的任務可能導致應用程序凍結(jié)或應用程序無響應(ANR),因為主線程負責處理用戶交互和呈現(xiàn)用戶界面(UI)。
- 內(nèi)存泄漏:當不再需要的對象沒有被正確釋放時,就會發(fā)生內(nèi)存泄漏。這將導致過多的內(nèi)存使用,從而可能導致內(nèi)存不足(OOM)錯誤。
- 與容量相關(guān)的卡頓:當CPU或GPU等某些資源負擔過重時,UI可能無法在給定的時間內(nèi)正確呈現(xiàn)。
這些問題在測試過程中很容易重現(xiàn),本地分析工具非常適合檢測和修復這些問題。
需要生產(chǎn)級可觀測性時
雖然本地分析涵蓋眾多問題,但不是所有問題在本地環(huán)境中都很顯然出現(xiàn)。生產(chǎn)級可觀測性對于診斷至關(guān)重要:
- 意外的用戶行為:用戶可能會上傳大堆文件、執(zhí)行快速操作,或者以意外的方式瀏覽應用程序,從而暴露出極端情況。
- 設(shè)備特有的崩潰:安卓的多樣性意味著問題可能出現(xiàn)在特定的設(shè)備或操作系統(tǒng)版本上,通常在本地測試期間無法檢測到。
- 網(wǎng)絡(luò)連接差:現(xiàn)實世界的用戶常面臨互聯(lián)網(wǎng)緩慢或不可靠,導致超時中斷或長時間加載,這種情況很難模擬。
OpenTelemetry等生產(chǎn)級就緒的可觀測性工具對于發(fā)現(xiàn)和克服這些挑戰(zhàn)至關(guān)重要。
安卓中的OpenTelemetry
OpenTelemetry是一種強大的可觀測性框架,可以幫助開發(fā)者收集、處理和導出跟蹤(trace)、度量指標和日志等遙測數(shù)據(jù)。
與專有工具相比,使用OpenTelemetry監(jiān)測性能有許多優(yōu)點。基于OpenTelemetry的SDK非常靈活,允許工程師輕松地將他們的檢測機制擴展到第三方庫。作為一種被廣泛采用的開源框架,OpenTelemetry還幫助組織避免供應商鎖定,對自己的數(shù)據(jù)擁有更大的控制度。
通過將OpenTelemetry集成到安卓應用程序中,你可以跟蹤單個操作的性能、識別瓶頸,并深入了解你的應用程序在各種實際情形下的性能表現(xiàn)。如何做到這一點呢?
初始集成和設(shè)置
如果要將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)
然后,我們可以創(chuàng)建一個 OpenTelemetry 實例,充當中央配置點,管理跟蹤器提供程序(tracer provider)、資源和導出器。
跟蹤器提供程序創(chuàng)建和管理跟蹤器,跟蹤器又生成跨度(span)。資源包含有關(guān)應用程序的元數(shù)據(jù),并附加到每個跨度上,有助于將遙測數(shù)據(jù)情境化。導出器定義遙測數(shù)據(jù)將發(fā)送到何處,比如后端可觀測性平臺或本地文件以便檢查。
// 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()
初始化所有內(nèi)容后,我們可以使用 openTelemetry.sdkTracerProvider.get() 獲取跟蹤器并創(chuàng)建跨度。
跟蹤表示分布式系統(tǒng)中的單個操作或工作流。針對安卓應用程序,它可以捕獲用戶請求或操作在應用程序中的整個過程。在此過程中,跨度表示單個工作單元,比如網(wǎng)絡(luò)請求、數(shù)據(jù)庫查詢或 UI 渲染任務,提供了有關(guān)其持續(xù)時間和上下文的詳細信息。代碼如下所示:
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 解決問題
我們已了解了如何在我們的安卓應用程序中創(chuàng)建OpenTelemetry 實例,下面給出一些常見類型的問題以及這個框架如何切實幫助我們跟蹤問題。
網(wǎng)絡(luò)延遲問題
網(wǎng)絡(luò)性能是生產(chǎn)環(huán)境中最不可預測的因素之一。雖然本地測試是在穩(wěn)定高速的環(huán)境下進行,但實際用戶面臨各種不同的情形。在流量高峰期間,他們可能會遇到間歇性的移動連接、不可靠的公共 Wi-Fi 或后端延遲。這些挑戰(zhàn)可能導致請求時間過長、操作失敗,甚至應用程序被丟棄。
使用 OpenTelemetry,你可以檢測網(wǎng)絡(luò)請求以測量其持續(xù)時間并識別瓶頸。通過使用元數(shù)據(jù)(比如端點 URL、請求大小或響應狀態(tài))標記跨度,你可以分析以下趨勢:
- 導致最長延遲的端點:識別持續(xù)性能欠佳的 API ,并優(yōu)先優(yōu)化它們。
- 網(wǎng)絡(luò)情況對用戶體驗造成的影響:將高延遲跨度與用戶流失關(guān)聯(lián)起來,以衡量響應緩慢的影響。
- 響應時間在不同地區(qū)的變化:了解性能在各地區(qū)的差異,并針對受影響最嚴重的地區(qū)量身定制改進措施。
如例子所示:
假設(shè)我們有一個端點,用戶將圖像上傳到服務器。網(wǎng)絡(luò)性能可能會因圖像大小、用戶位置或連接類型而異。如果使用 OpenTelemetry 檢測網(wǎng)絡(luò)請求,我們可以捕獲相關(guān)元數(shù)據(jù)并分析趨勢,比如較大的圖像或特定區(qū)域是否與較長的上傳時間相關(guān)。以下是我們可以檢測這種場景的方法:
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()
}
}
操作系統(tǒng)版本或設(shè)備特有的問題
安卓的生態(tài)系統(tǒng)非常龐大,應用程序可以在各種設(shè)備、操作系統(tǒng)版本和硬件配置上運行。這種多樣性使得確保所有設(shè)備上有一致的用戶體驗變得具有挑戰(zhàn)性。某些崩潰或錯誤可能只會在特定設(shè)備上或在特定情形下出現(xiàn),因此很難在受控制的測試環(huán)境中發(fā)現(xiàn)它們。
使用 OpenTelemetry,你可以集中捕獲設(shè)備特有的元數(shù)據(jù),并在OpenTelemetry設(shè)置期間將其添加到資源配置中。這確保了重要的上下文信息自動附加到跨度、日志和度量指標上。這種方法確保了跨遙測數(shù)據(jù)的一致性。
如果分析這種元數(shù)據(jù),你可以發(fā)現(xiàn)以下趨勢:
- 在某些型號的設(shè)備上頻繁崩潰:使用較舊或廉價設(shè)備的用戶可能會因資源不足而遇到崩潰,檢測這種模式可能便于優(yōu)化內(nèi)存使用或提供更輕量級的應用程序。
- 安卓版本之間的行為變化:由于安卓API方面的變化、更嚴格的權(quán)限要求或更新中引入的錯誤,某些崩潰可能僅發(fā)生在特定的操作系統(tǒng)版本上。借助這些數(shù)據(jù),你可以優(yōu)先考慮兼容性修復或更新應用程序的依賴項,以避免使用棄用的 API。
- 硬件特有的渲染問題:一些設(shè)備可能有獨特的圖形驅(qū)動程序或硬件問題,從而導致渲染問題,比如 UI 中的視覺故障或偽影。比如說,自定義動畫在非標準屏幕分辨率或刷新率的設(shè)備上可能會出現(xiàn)異常。有關(guān)屏幕規(guī)格或 GPU 詳細信息的元數(shù)據(jù)有助于查明并解決這些不一致問題。
具體如下設(shè)置:
// 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()
意外的用戶行為
真實用戶經(jīng)常以意想不到的方式與應用程序交互。這種不可預測性可能會導致性能問題、崩潰或未優(yōu)化的用戶體驗,這些在本地測試中是無法發(fā)現(xiàn)的。
比如說,用戶可能會上傳比預期大得多的文件,從而導致內(nèi)存或性能瓶頸。其他用戶可能快速重復執(zhí)行操作,比如提交表單或刷新頁面,導致競態(tài)條件或服務器過載。一些用戶可能會以未經(jīng)測試的順序瀏覽應用程序,從而觸發(fā)意外狀態(tài)或錯誤。
如果利用OpenTelemetry來監(jiān)測用戶交互,你可以捕獲和分析詳細說明用戶實際如何使用你應用程序的跨度。這些數(shù)據(jù)有助于深入了解意外模式,使你能夠:
- 檢測資源密集型操作:跟蹤表示圖像上傳、數(shù)據(jù)庫查詢或 API 調(diào)用等操作的跨度,以識別過度使用影響性能的場景。
- 發(fā)現(xiàn)不常見的導航路徑:通過監(jiān)控用戶導航流程,你可以發(fā)現(xiàn)經(jīng)常導致錯誤或崩潰的順序,從而幫助你優(yōu)先提供處理實際問題的方法。
- 識別高需求功能:分析跨度以查看哪些操作或功能最常使用,即使它們不是你初始測試用例的一部分。這可以指導優(yōu)化工作和功能優(yōu)先考慮。
設(shè)想如下場景:用戶經(jīng)常在兩個屏幕(比如產(chǎn)品列表和產(chǎn)品詳細信息頁面)之間快速連續(xù)地來回導航。雖然這種行為看似無害,但可能會無意中導致資源泄漏或降低渲染性能。
如果使用導航元數(shù)據(jù)(比如屏幕名稱、時間戳和其他一些用戶交互)標記跨度,你可以分析導航行為中的模式:
- 用戶可能以意外的高頻率在屏幕之間切換,表明了需要緩存或延遲加載機制,以減輕資源壓力。
- 特定屏幕可能持續(xù)產(chǎn)生錯誤,從而暴露渲染邏輯方面的極端情況或瓶頸。
- 深入了解導航順序有助于優(yōu)化用戶體驗流程,使應用程序對常見行為更簡單直觀,同時更從容地處理極端情況。
這種發(fā)現(xiàn)和解決意外用戶行為的能力可確保你的應用程序即使在非常規(guī)使用場景下也能保持可靠性和高性能。
下一步:將數(shù)據(jù)轉(zhuǎn)發(fā)到你想要分析的地方
正如我們所討論的,使用OpenTelemetry 檢測你的安卓應用程序對于監(jiān)測和了解常見的性能問題大有幫助。
一旦你開始收集數(shù)據(jù),就需要為它設(shè)置一個存放位置。OpenTelemetry 這種框架的一大優(yōu)點是,有許多可觀測性工具支持攝取這種類型的數(shù)據(jù)。你可以選擇將其轉(zhuǎn)發(fā)到特定供應商的后端或各種開源工具,比如面向跨度的 Jaeger 或面向日志的 Loki。
從SDK轉(zhuǎn)發(fā)OpenTelemetry數(shù)據(jù)需要添加一個或多個導出器,以便在實際生成數(shù)據(jù)后為你的數(shù)據(jù)提供目的地。
導出器是一個組件,它將你在使用的用于捕獲數(shù)據(jù)的SDK與用于接收數(shù)據(jù)的外部OpenTelemetry 收集器連接起來。導出器在設(shè)計之初考慮到了OpenTelemetry 數(shù)據(jù)模型,可以導出OpenTelemetry數(shù)據(jù)而不會丟失任何信息。市面上有許多針對特定語言的導出器:https://opentelemetry.io/ecosystem/registry/?compnotallow=exporter&language=。
OpenTelemetry 收集器是一種與供應商無關(guān)的接收、處理和導出遙測數(shù)據(jù)的方法。它并不總是必要的,因為你可以通過導出器將數(shù)據(jù)直接發(fā)送到所選擇的后端。如果你在管理多個數(shù)據(jù)攝取源,并發(fā)送到多個可觀測性后端,擁有收集器不失為一種好辦法。它允許你的服務快速卸載數(shù)據(jù),收集器可以處理其他操作,比如重試、批處理、加密,甚至敏感數(shù)據(jù)過濾。
結(jié)語
如今,應用程序運行在無數(shù)設(shè)備上、不同環(huán)境中、被不同用戶使用,獲得最佳性能和可靠性需要的不僅僅是本地測試。雖然像Android Studio Profiler這樣的工具擅長解決受控制環(huán)境中可重現(xiàn)的問題,但生產(chǎn)級可觀測性填補了這一空白:發(fā)現(xiàn)只有在特定條件下才會出現(xiàn)的實際問題。
OpenTelemetry為收集和分析遙測數(shù)據(jù)提供了一種強大的框架,便于開發(fā)者深入了解和優(yōu)化生產(chǎn)級應用程序。通過檢測跨度和附加有意義的元數(shù)據(jù),你就可以精準確定瓶頸、診斷設(shè)備或操作系統(tǒng)特有的問題,并發(fā)現(xiàn)影響應用程序性能或用戶體驗的意外用戶行為。
原文標題:Solving Android app issues with OpenTelemetry: Beyond local profiling,作者:Francisco Prieto Cardelle