Android-ANR

参考:

如何分析解决Android ANR
App性能优化系列6-ANR详解

ANR的原因

如果从根源上划分的话,导致ANR的原因有如下几点:

  • IO操作,如数据库、文件、网络
  • CPU不足,一般是别的App占用了大量的CPU,导致App无法及时处理
  • 硬件操作,如camera
  • 线程问题,如主线程被join/sleep,或wait锁等导致超时
  • service问题,如service忙导致超时无响应,或service binder的数量达到上限
  • system server问题,如WatchDog发现ANR

一:什么是ANR

ANR : Application Not Responding,即应用无响应

二:ANR的类型

ANR一般有三种类型:
1:KeyDispatch Timeout(5 seconds) –主要类型
按键或触摸事件 在特定时间内无响应
2:Broadcast Timeout(10 seconds)
BroadcastReceiver 在特定时间内无法处理完成
3:Service Timeout(20 seconds) –小概率类型
Service 在特定的时间内无法处理完成

三:为什么会超时呢?

ANR首要原因就是 在主线程(UI线程)里面做了太多的阻塞耗时操作 , 例如文件读写, 数据库读写, 网络查询等等.

超时时间的计数一般是从按键分发给app开始。超时的原因一般有两种:
(1)当前的事件没有机会得到处理(即UI线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
(2)当前的事件正在处理,但没有及时完成。

四:如何避免KeyDispatchTimeout

知道了ANR产生的原因, 那么想要避免ANR, 也就很简单了, 就一条规则:
不要在主线程(UI线程)里面做繁重的操作.

1:UI线程尽量只做跟UI相关的工作
2:耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
3:尽量用Handler来处理UIthread和别的thread之间的交互。

如何避免ANR
1.使用AsyncTask处理耗时IO操作。
2.使用Thread或者HandlerThread时,调用

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)

设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
3.使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
4.Activity的onCreate和onResume回调中尽量避免耗时的代码
5.BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。

五、如何调查来解决ANR

1:首先分析log
2: 从trace.txt文件查看调用stack.
3: 看代码
4:仔细查看ANR的成因(iowait?block?memoryleak?)

通过ANR 日志定位问题
当ANR发生时,我们往往通过Logcat和traces文件(目录/data/anr/)的相关信息输出去定位问题。主要包含以下几方面:
1)基本信息,包括进程名、进程号、包名、系统build号、ANR 类型等等;
2)CPU使用信息,包括活跃进程的CPU 平均占用率、IO情况等等;
3)线程堆栈信息,所属进程包括发生ANR的进程、其父进程、最近有活动的3个进程等等。

六、哪些地方是执行在主线程的

Activity的所有生命周期回调 都是执行在主线程的.
Service默认是执行在主线程的.
BroadcastReceiver的onReceive回调是执行在主线程的.
没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的.
AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的.
View的post(Runnable)是执行在主线程的.

七、使用子线程的方式有哪些

7.1启动Thread方式

 class PrimeThread extends Thread {
        long minPrime;

        PrimeThread(long minPrime) {
            this.minPrime = minPrime;
        }

        public void run() {

        }
    }
//继承Thread 
  PrimeThread p1 = new PrimeThread(143);
        p1.start();

 class PrimeRun implements Runnable {
        long minPrime;

        PrimeRun(long minPrime) {
            this.minPrime = minPrime;
        }

        public void run() {
        }
    }
//实现Runnable接口
 PrimeRun p2 = new PrimeRun(143);
        new Thread(p2).start();

7.2使用AsyncTask

7.3IntentService

Service是运行在主线程的, 然而IntentService是运行在子线程的.
实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式.

7.4HandlerThread

Android中结合Handler和Thread的一种方式. 前面有云, 默认情况下Handler的handleMessage是执行在主线程的, 但是如果我给这个Handler传入了子线程的looper, handleMessage就会执行在这个子线程中的. HandlerThread正是这样的一个结合体:

7.5Loader

Android 3.0引入的数据加载器, 可以在Activity/Fragment中使用. 支持异步加载数据, 并可监控数据源在数据发生变化时传递新结果. 常用的有CursorLoader, 用来加载数据库数据.

特别注意:

使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的. 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的.

结语

对于ANR问题, 个人认为还是预防为主, 认清代码中的阻塞点, 善用线程. 同时形成良好的编程习惯, 要有MainThread和Worker Thread的概念。

导出traces文件:

adb pull /data/anr/traces.txt

参考:

Android App优化之ANR详解
如何分析ANR Log的总结

CPU饥饿问题

参考:饥饿问题

程序并行化以后,还会遇到共享数据的访问问题。

如果多个线程对共享数据都是只读操作,那么对共享数据的访问不需要加锁保护。
如果多个线程对共享数据的访问存在写操作,那么对共享数据的访问必须加锁保护。

在有锁保护的共享数据访问模型中,一旦一个线程取得了锁,那么其他线程在进行锁操作时都必须等待。
这样只有一个线程在运行,导致只有一个CPU核在运行,其他CPU核都处于饥饿状态。

解决共享数据访问的最有效方案就是共享资源分布式计算。
另外一种解决共享数据访问的方案是称作"无锁(Lock-Free)编程",无锁编程需要用到原子操作。
由于原子操作的速度比锁快,因此在访问共享资源出现排队情况下,其性能比使用锁时得到提高;特别读操作频繁、写操作稀少的情况,
无锁编程也比有锁编程有更高的性能,因为读操作不需要锁保护,另外无锁编程还有一个优势是它是非阻塞的算法。
当然,无锁编程并不能解决访问共享资源出现的排队问题,终极的解决方案仍然是共享资源分布式计算。
无锁编程的另一缺陷是需要使用内存垃圾回收技术,对于不能使用垃圾收集机制的应用场合如服务器软件、嵌入式软件来说是无法接受的。
最重要的一点,无锁编程难度和复杂度非常高,非专业人士难以掌握,并且目前只能通过它实现一些简单的数据结构和算法。
虽然无锁编程对于多核编程作用有限,但是它对于理解多线程编程的许多深层次问题还是有很好的借鉴作用。
无锁编程还可以和共享资源分布式计算结合起来使用,以使程序性能获得更大的提高,当然这需要硬件支持原子操作可以并行地操作不同的变量。

任务的分解与调度问题
程序并发运行后,除了锁竞争导致的CPU饥饿问题外,还有一个问题也会导致CPU饥饿,那就是任务的分解与调度问题。
所谓任务指的是执行的某个程序功能,任务与线程不同,一个线程内可以执行一个或多个任务。
如果任务划分得不好,那么就很难均匀地分布到各个CPU核上;
对于划分好的多个任务,如何将其均匀地分配到各个CPU核上进行计算是很重要的问题,也就是所谓的任务调度问题。任务分解与调度的好坏会影响各CPU核上计算的负载均衡。

比如有6个任务,耗时分别为18、17、12、8、5、3,如何将其在一个双核CPU上执行,取得好的加速比呢?
相信经过简单的口算,大部分读者都可以发现,将任务分为两组(18、8、5),(17、12、3),使用两个硬件线程来执行,每个线程执行其中的一组任务,能取得最好的加速比效果。
但是在实际情况中,任务数量较多,任务间耗时差距通常非常大,要均匀地将任务分配到各个CPU核上执行并非易事。事实上,任务的调度算法问题是一个NP难题。
当然上面的例子没有考虑任务间的执行依赖关系,有依赖关系时,任务调度又复杂了一些。
在实际情况中,还有许多任务是动态产生的,事先并不知道任务耗时,如何调度这些动态任务,使计算均匀地分配到各个CPU核上则是更大的挑战。

实战:

1、搜索 : “main” prio=

先找到主线程的位置:

2、再搜索 : 自己APP的名字 : com.xq.mycar

定位发生ANR的代码:

"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 ucsCount=0 flags=1 obj=0x7131d468 self=0xb40000766d2a8380
  | sysTid=2685 nice=0 cgrp=foreground sched=0/0 handle=0x782fadd4f8
  | state=S schedstat=( 230801596 765696069 1056 ) utm=13 stm=9 core=6 HZ=100
  | stack=0x7feb779000-0x7feb77b000 stackSize=8188KB
  | held mutexes=
TraceBegin:
  at sun.misc.Unsafe.park(Native method)
  - waiting on an unknown object
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)
  at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:450)
  at java.util.concurrent.FutureTask.get(FutureTask.java:192)
  at com.xq.mycar.composition.biz.global.tasks.XqAutoMyDataCollectionTask.initThreadPool(SourceFile:-1)
  at com.xq.mycar.composition.biz.global.tasks.XqAutoMyDataCollectionTask.executor(SourceFile:-1)
  at com.xq.mycar.composition.biz.global.tasks.XqAutoMyDataCollectionTask.listenVehiclePowerStatus(SourceFile:-1)
  at java.lang.reflect.Method.invoke(Native method)
  at org.greenrobot.eventbus.EventBus.invokeSubscriber(SourceFile:6)
  at org.greenrobot.eventbus.EventBus.postToSubscription(SourceFile:9)
  at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(SourceFile:8)
  at org.greenrobot.eventbus.EventBus.postSingleEvent(SourceFile:6)
  at org.greenrobot.eventbus.EventBus.post(SourceFile:9)
  at com.xq.mycar.composition.biz.global.openApi.XqAutoMyCarNativeOpenSDK$1.onPowerLevelChanged(SourceFile:-1)
  at android.hardware.xqauto.bodywork.AbsXQAutoBodyworkListener.onDataChanged(AbsXQAutoBodyworkListener.java:980)
  at android.hardware.xqauto.AbsXQAutoDevice.onPostEvent(AbsXQAutoDevice.java:108)
  - locked <0x0f34f78a> (a android.hardware.xqauto.AbsXQAutoDevice$IXQAutoListenerMap)
  at android.hardware.xqauto.bodywork.XQAutoBodyworkDevice.postEvent(XQAutoBodyworkDevice.java:2218)
  at android.hardware.xqauto.XQAutoDeviceManager$XQAutoDeviceManagerImpl$2.run(XQAutoDeviceManager.java:288)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loopOnce(Looper.java:201)
  at android.os.Looper.loop(Looper.java:288)
  at android.app.ActivityThread.main(ActivityThread.java:7989)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)
TraceEnd:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ANR(Application Not Responding)是Android系统中的一种错误状态,当应用程序在主线程上执行耗时操作或者阻塞UI线程超过一定时间时,系统会认为应用程序无响应,触发ANR错误。 ANR错误会导致应用程序无法响应用户的操作,用户体验变差。通常情况下,ANR错误会在以下几种情况下发生: 1. 主线程阻塞:当应用程序在主线程上执行耗时操作,例如网络请求、数据库查询等,如果这些操作耗时过长,超过了系统规定的时间限制(通常为5秒),系统就会认为应用程序无响应,触发ANR错误。 2. 主线程死锁:当应用程序中的多个线程相互等待对方释放资源而导致死锁时,主线程也会被阻塞,从而触发ANR错误。 3. 广播接收器超时:当应用程序的广播接收器在一定时间内没有处理完接收到的广播消息时,系统也会认为应用程序无响应,触发ANR错误。 为了避免ANR错误的发生,开发者可以采取以下几种措施: 1. 将耗时操作放在子线程中执行,避免阻塞主线程。可以使用AsyncTask、Thread等方式来创建子线程。 2. 使用异步操作来执行耗时操作,例如使用Handler、AsyncTask、RxJava等方式来处理耗时操作的结果。 3. 合理管理线程,避免死锁的发生。可以使用同步锁、线程池等方式来管理线程。 4. 尽量减少主线程上的工作量,例如将复杂的计算、IO操作等放在子线程中执行。 5. 对于广播接收器,尽量避免在接收到广播后执行耗时操作,可以考虑将耗时操作放在Service中执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值