本部分将通过一个示例轨迹,展示如何使用 ProfilingManager 调试应用无响应 (ANR) 问题。
设置应用以收集 ANR
首先,在应用中设置 ANR 触发器:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
捕获并上传 ANR 轨迹后,在 Perfetto 界面中打开该轨迹。
分析轨迹
由于 ANR 触发了轨迹,因此您知道当系统检测到应用的主线程无响应时,轨迹已结束。图 1 显示了如何在界面中导航到已相应标记的应用主线程。
轨迹的末尾与 ANR 的时间戳相匹配,如图 2 所示。
该轨迹还显示了应用在发生 ANR 时正在运行的操作。具体来说,应用在 handleNetworkResponse 轨迹切片中运行了代码。此切片位于 MyApp:SubmitButton 切片内。它使用了 1.48 秒的 CPU 时间(图 3)。
如果您目前仅依靠 ANR 发生时的堆栈轨迹进行调试,则可能会错误地将 ANR 完全归因于在配置文件完成记录时尚未结束的 handleNetworkResponse 轨迹切片中运行的代码。不过,1.48 秒不足以单独触发 ANR,即使它是一项开销很大的操作。您需要进一步回溯时间,了解此方法之前是什么阻塞了主线程。
为了找到 ANR 的原因,我们从界面线程生成的最后一个帧(对应于 Choreographer#doFrame 551275 slice)之后开始查找,并且在开始导致 ANR 的 MyApp:SubmitButton slice 之前没有明显的延迟源(图 4)。
如需了解阻塞情况,请缩小视图以检查整个 MyApp:SubmitButton 切片。如图 4 所示,您会注意到线程状态中的一个关键细节:线程有 75% 的时间(6.7 秒)处于 Sleeping 状态,只有 24% 的时间处于 Running 状态。
这表明 ANR 的主要原因是等待,而不是计算。检查各个睡眠时段,找出规律。
前三个睡眠间隔(图 6-8)几乎完全相同,每个间隔大约为 2 秒。异常的第四次睡眠(图 9)时长为 0.7 秒。 在计算环境中,时长正好为 2 秒的情况很少是巧合。这强烈表明是程序化超时,而不是随机资源争用。最后一次休眠可能是因为线程等待的操作成功完成,因此线程结束了等待。
此假设是,应用多次达到用户定义的 2 秒超时时间,最终成功,但导致了足够的延迟来触发 ANR。
如需验证这一点,请检查与 MyApp:SubmitButton 轨迹部分关联的代码:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
此代码证实了这一假设。onClickSubmit 方法在界面线程上执行网络请求,并使用硬编码的 NETWORK_TIMEOUT_MILLISECS(2000 毫秒)。关键在于,它在 while 循环中运行,最多重试 10 次。
在此特定轨迹中,用户可能网络连接状况不佳。前三次尝试均失败,导致三次 2 秒超时(总共 6 秒)。第四次尝试在 0.7 秒后成功,使代码能够继续执行 handleNetworkResponse。不过,累计等待时间已触发 ANR。
为避免此类 ANR,请将延迟时间不定的网络相关操作放入后台线程,而不是在主线程中执行这些操作。 这样一来,即使连接不良,界面也能保持响应,从而完全消除此类 ANR。