赞
踩
和你一起终身学习,这里是程序员 Android
本篇文章主要介绍 Android 开发中的 ANR 部分知识点,通过阅读本篇文章,您将收获以下内容:
1.ANR 是什么
2.检测和诊断问题
3.诊断ANR
4.解决ANR问题
ANR 是Application Not Responding的简称。
当Android应用程序的UI线程被阻止时间太长时,将触发“应用程序无响应”(ANR)错误。如果应用程序位于前台,则系统会向用户显示一个对话框,如图1所示。ANR对话框使用户有机会强制退出该应用程序。
图1.向用户显示的ANR对话框
ANR是一个问题,因为负责更新UI的应用程序主线程无法处理用户输入事件或绘制,从而使用户感到沮丧。有关应用主线程的更多信息,请参阅进程和线程。
发生以下情况之一时,将为您的应用触发ANR:
如果您的应用遇到ANR,则可以使用本文中的指导来诊断和解决问题。
Android提供了多种方法来让您知道您的应用存在问题,并帮助您进行诊断。如果您已经发布了应用程序,则Android vitals可以提醒您发生问题,并且有诊断工具可以帮助您发现问题。
Android vitals
当您的应用程序展示出过多的ANR时 ,Android生命体可以通过Play控制台提醒您,从而提高应用程序的性能 。在以下情况下,Android vitals认为ANR过多:
一个日常的会话是指在使用你的应用程序的日子。
有关Google Play如何收集Android生命数据的信息,请参阅 Play控制台文档。
诊断ANR时,需要寻找一些常见的模式:
以下技术可以帮助您找出导致ANR的原因是哪些。
StrictMode
使用StrictMode有助于您在开发应用程序时在主线程上查找意外的I / O操作。您可以StrictMode在应用程序或活动级别使用。
启用后台ANR 弹窗
仅当在设备的“ 开发人员”选项中启用了“ 显示所有ANR”时,Android才会为需要花费太长时间才能处理广播消息的应用程序显示ANR对话框。因此,并非总是向用户显示后台ANR对话框,但是该应用仍可能遇到性能问题。
使用TraceView 跟踪
您可以在查看用例时使用Traceview跟踪正在运行的应用程序,并确定主线程繁忙的位置。有关如何使用Traceview的信息,请参见使用Traceview和dmtracedump进行概要分析。
分析手机内部traces 文件
当遇到ANR时,Android会存储跟踪信息。在较旧的OS版本/data/anr/traces.txt上,设备上只有一个文件。在较新的OS版本中,存在多个/data/anr/anr_*文件。您可以通过使用Android调试桥(adb)作为root用户从设备或仿真器访问ANR跟踪 :
adb root
adb shell ls /data/anr
adb pull /data/anr/
您可以使用设备上的“获取错误报告开发者”选项或开发计算机上的adb bugreport命令从物理设备捕获错误报告。有关更多信息,请参阅捕获和阅读错误报告。
确定问题之后,可以使用本节中的提示来修复常见问题。
主线程 进行耗时操作
确定代码中应用主线程忙于5秒钟以上的位置。在您的应用中查找可疑的用例,然后尝试重现ANR。
例如,图2显示了Traceview时间线,其中主线程忙于5秒钟以上。
图2.Traceview时间线显示繁忙的主线程
图2向我们展示了大多数有问题的代码都发生在onClick(View)处理程序中,如以下代码示例所示@Overridepublic void onClick(View view) { // This task runs on the main thread. BubbleSort.sort(data);}
在这种情况下,应将在主线程中运行的工作移至工作线程。Android Framework包含可帮助将任务移至工作线程的类,有关更多信息,请参阅线程的帮助程序类。以下代码显示了如何使用[AsyncTask](https://developer.android.google.cn/reference/android/os/AsyncTask.html)助手类来处理工作线程上的任务:
@Overridepublic void onClick(View view) { // The long-running operation is run on a worker thread new AsyncTask() { @Override protected Long doInBackground(Integer[]... params) { BubbleSort.sort(params[0]); } }.execute(data);}
Traceview显示大多数代码都在工作线程上运行,如图3所示。主线程可用于响应用户事件。
图3. Traceview时间线显示了工作线程处理的工作
主线程IO操作
在主线程上执行IO操作是导致主线程执行缓慢操作的常见原因,这可能会导致ANR。建议将所有IO操作移至工作线程,如上一节中所示。
IO操作的一些示例是网络和存储操作。有关更多信息,请参阅执行网络操作和保存数据。
争夺持有对象锁
在某些情况下,导致ANR的工作不会直接在应用程序的主线程上执行。如果辅助线程对主线程完成其工作所需的资源持有锁,则可能会发生ANR。
例如,图4显示了Traceview时间线,其中大部分工作是在工作线程上执行的
图4. Traceview时间线,显示了正在工作线程上执行的工作
但是,如果您的用户仍在遇到ANR,则应查看Android Device Monitor中主线程的状态。通常,如果主线程已RUNNABLE准备好更新UI,并且处于响应状态,则主线程处于工作状态。
但是,如果主线程无法恢复执行,则它处于BLOCKED状态且无法响应事件。状态在Android设备监视器上显示为Monitor或Wait,如图5所示。
图5. Monitor状态下的主线程
以下跟踪显示了应用程序的主线程在等待资源时被阻止:...
AsyncTask #2" prio=5 tid=18 Runnable | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100 | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920 | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100 | stack=0x94a7e000-0x94a80000 stackSize=1038KB | held mutexes= "mutator lock"(shared held) at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8) at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147) - locked <0x083105ee> (a java.lang.Boolean) at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135) at android.os.AsyncTask$2.call(AsyncTask.java:305) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761)...
查看跟踪可以帮助您找到阻塞主线程的代码。以下代码负责在上一个跟踪中持有阻塞主线程的锁:
@Overridepublic void onClick(View v) { // The worker thread holds a lock on lockedResource new LockTask().execute(data); synchronized (lockedResource) { // The main thread requires lockedResource here // but it has to wait until LockTask finishes using it. }}public class LockTask extends AsyncTask { @Override protected Long doInBackground(Integer[]... params) { synchronized (lockedResource) { // This is a long-running operation, which makes // the lock last for a long time BubbleSort.sort(params[0]); } }}
另一个示例是应用程序的主线程正在等待工作线程的结果,如以下代码所示。请注意,在Kotlin中,建议不要使用wait()和 notify()模式,因为Kotlin具有处理并发的机制。使用Kotlin时,如果可能,应使用Kotlin特定的机制。
public void onClick(View v) { WaitTask waitTask = new WaitTask(); synchronized (waitTask) { try { waitTask.execute(data); // Wait for this worker thread’s notification waitTask.wait(); } catch (InterruptedException e) {} }}class WaitTask extends AsyncTask { @Override protected Long doInBackground(Integer[]... params) { synchronized (this) { BubbleSort.sort(params[0]); // Finished, notify the main thread notify(); } }}
还有其他一些情况可能会阻塞主线程,包括使用Lock,的线程Semaphore以及资源池(例如数据库连接池)或其他互斥(mutex)机制。
通常,应该评估应用程序对资源的锁定,但是如果要避免使用ANR,则应查看对主线程所需的资源所持有的锁定。
确保将锁保持的时间最少,甚至更好,请评估应用程序是否首先需要保持。如果要使用锁根据工作线程的处理确定何时更新UI,请使用诸如onProgressUpdate()和的机制 onPostExecute() 在工作线程和主线程之间进行通信。
死锁
当线程进入等待状态时会发生死锁,因为另一个线程拥有所需的资源,而另一个线程也在等待第一个线程拥有的资源。如果应用程序的主线程处于这种情况下,则可能会发生ANR。
死锁是计算机科学中经过充分研究的现象,并且可以使用死锁预防算法来避免死锁。
有关更多信息,请参见Wikipedia上的死锁和 死锁预防算法。
广播接收慢
应用程序可以通过广播接收器响应广播消息,例如启用或禁用飞行模式或连接状态更改。当应用花费太长时间来处理广播消息时,就会发生ANR。
在以下情况下会发生ANR:
@Overridepublic void onReceive(Context context, Intent intent) { // This is a long-running operation BubbleSort.sort(data);}
在这种情况下,建议将长时间运行的操作移至,IntentService因为它使用辅助线程执行工作。以下代码显示了如何使用IntentService来处理长时间运行的操作:
@Overridepublic void onReceive(Context context, Intent intent) { // The task now runs on a worker thread. Intent intentService = new Intent(context, MyIntentService.class); context.startService(intentService);}public class MyIntentService extends IntentService { @Override protected void onHandleIntent(@Nullable Intent intent) { BubbleSort.sort(data); }}
使用的结果是IntentService,长时间运行的操作在工作线程而不是主线程上执行。图7显示了在Traceview时间轴中推迟到工作线程的工作
图7. Traceview时间线显示了在工作线程上处理的广播消息
您的广播接收器可以用来goAsync() 向系统发送信号,通知它需要更多时间来处理消息。但是,您应该调用 finish() 该 PendingResult 对象。下面的示例演示如何调用finish()以使系统回收广播接收器并避免ANR:
final PendingResult pendingResult = goAsync();new AsyncTask() { @Override protected Long doInBackground(Integer[]... params) { // This is a long-running operation BubbleSort.sort(params[0]); pendingResult.finish(); }}.execute(data);
但是,goAsync()如果广播是在后台,则将代码从慢速广播接收器移动到另一个线程并使用将无法修复ANR。ANR超时仍然适用。
有关ANR的更多信息,请参阅 使应用程序保持响应状态。有关线程的更多信息,请参见 线程性能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。