譯者 | 布加迪
審校 | 重樓
作為一名安卓開發(fā)者,我解決漏洞、衡量性能或改善應(yīng)用程序整體體驗(yàn)的第一反應(yīng)是在本地進(jìn)行測(cè)試和分析。像Android Studio Profiler這樣的工具提供了強(qiáng)大的功能來檢測(cè)和解決各種性能問題,比如UI線程阻塞、內(nèi)存泄漏或過多的CPU使用。
雖說這類本地工具不可或缺,但它們也有局限性。某些問題在受控制的環(huán)境中才會(huì)重現(xiàn),這種環(huán)境有一致的網(wǎng)絡(luò)連接、可預(yù)測(cè)即穩(wěn)定的用戶行為和有限的測(cè)試設(shè)備種類。而在實(shí)際情形下,用戶以意想不到的方式與應(yīng)用程序交互,面對(duì)不同的硬件和不同的情形,暴露出難以在本地重現(xiàn)的問題。
這時(shí)候OpenTelemetry就有了用武之地。

OpenTelemetry框架用于收集、處理和導(dǎo)出有關(guān)應(yīng)用程序性能的數(shù)據(jù)。雖然對(duì)于移動(dòng)端來說比較新,但它已成為一種快速增長(zhǎng)的后端性能管理標(biāo)準(zhǔn)。
移動(dòng)端使用這個(gè)框架的好處很顯著。OpenTelemetry使開發(fā)者能夠從生產(chǎn)環(huán)境中收集可觀測(cè)性數(shù)據(jù),為深入了解應(yīng)用程序的真實(shí)行為提供了窗口。
本地分析和生產(chǎn)級(jí)可觀測(cè)性
本地分析的作用
本地分析對(duì)于識(shí)別在受控制環(huán)境中可再現(xiàn)的問題非常重要。

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





























