Android性能优化3-ANR

android studio 教程 | 2018-09-19 23:33

一、什么是ANR 如何避免它?

需要注意的是ANR发生在主线程。

ANR主要发生在以下情形:

1、你的app在前台,对输入事件或广播的处理超过5s

2、当没有activity在前台时,在合适的时间内没有完成广播 的处理

ActivityManagerService中的定义如下:

static final int BROADCAST_FG_TIMEOUT =10*1000;

static final int BROADCAST_BG_TIMEOUT =60*1000;

// How long we wait until we timeout on keydispatching.

static final int KEY_DISPATCHING_TIMEOUT =5*1000;

3、ActiveServices中定义service超时时间为20s

staticfinalint SERVICE_TIMEOUT =20*1000;

4、ActiveServices中定义service后台超时时间为200s

staticfinalint SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT *10;

二、发生ANR主要在以下工作情形:

1、在主线程做大量IO操作。

2、在主线程做复杂的运算。

3、在主线程同时通过binder去操作另外一个进程,这个进程长时间未返回。

4、主线程被另外一个线程阻塞,这个线程的代码块在做耗时操作,并且上锁。

5、主线程与另外一个线程处于死锁,在当前进程或通过binder调用的时候。

6、从Android 8.0开始限制后台启动service,如果从后台启动service,需要以前台的形式启动,并且在通知栏显示一条“xxx应用正在运行”的通知,同时在service的onCreate方法中调用startForeGround方法,如果超过5S没有调用,会弹出ANR对话框。

三、修复ANR

1、严格模式

第一种方式是在开发者选项中打开严格模式,这个开关打开之后,打开某个应用,如果存在主线程耗时操作,界面的边界会出现红色的闪烁框,提示有性能问题。

另外一种就是代码的方式,通过在Application的onCreate方法中自定义严格模式,并通过adb shell , logcat | grep StrictMode 过滤严格模式的日志。

if (IS_DEBUG && Build.VERSION.SDK_INT>= Build.VERSION_CODES.GINGERBREAD) {

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());

StrictMode.setVmPolicy(new VmPolicy.Builder().detectAll().penaltyLog().build());

严格模式主要检测两大问题,一个是线程策略,即TreadPolicy,另一个是VM策略,即VmPolicy。

ThreadPolicy

线程策略检测的内容有:

自定义的耗时调用 使用detectCustomSlowCalls()开启

磁盘读取操作 使用detectDiskReads()开启

磁盘写入操作 使用detectDiskWrites()开启

网络操作 使用detectNetwork()开启

虚拟机策略检测的内容有:

Activity泄露 使用detectActivityLeaks()开启

未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启

泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启

检测实例数量 使用setClassInstanceLimit()开启

对应的违例按照对应的方式解决即可。

2、开发者模式中打开ANR对话框开关,主要是一些后台应用出现ANR也可以显示ANR无响应对话框。

3、trace图表,按照一些文章的说法,有三种方法生成trace日志:

使用代码,Android Studio,DDMS,DDMS目前无法使用了,Android Studio在我的测试机器上面出现死机,无法生成trace文件,应该不是所有AS版本的共性问题,也可能是我测试机器的问题,最后只有通过代码生成trace文件比较靠谱,方法如下:

Debug.startMethodTracing("your trace file path");//开始

Debug.stopMethodTracing();//结束

两个方法中间就是需要跟踪的代码块。将设置的path路径下.trace文件pull出来,通过/sdk/tools/traceview.bat D:\log\testtrace.trace指令打开trace文件,如下:

上边不同颜色的一长条表示不同线程的执行时间

其中不同的颜色表示不同的方法

同一个颜色越长,说明执行时间越久,如图中的主线程 main

空白表示这个时间段内没有执行内容

下边分条展示的是不同方法的执行时间信息,关键指标有三个:

Cpu Time/Call:该方法平均占用 CPU 的时间

Real Time/Call:平均执行时间,包括切换、阻塞的时间,>= Cpu Time

Calls + Recur Calls/Total :调用、递归次数

Incl:表示包含该方法调用其他方法所用的时间或时间占比

Excl:表示该方法单独调用所使用的时间或时间占比

通过执行时间和执行次数大概可以判断出哪里有耗时操作。

4、/data/anr/traces.txt日志

发生ANR之后,系统会往/data/anr文件夹下面写trace日志,不同平台的文件命名可能不太一样,但是内容形式应该都是一样的,如下:

"main"prio=5 tid=1 Native

| group="main" sCount=1 dsCount=0flags=1 obj=0x737b8270 self=0x729c2c2a00

| sysTid=7830 nice=0 cgrp=default sched=0/0handle=0xa8

| state=S schedstat=( 5 21173909 174) utm=16 stm=3 core=0 HZ=100

| stack=0x7fe4b3c000-0x7fe4b3e000stackSize=8MB

| held mutexes=

kernel: __switch_to+0x88/0x94

kernel: binder_thread_read+0x3ac/0x107c

kernel:binder_ioctl_write_read.constprop.41+0x21c/0x39c

kernel: binder_ioctl+0x1f8/0x650

kernel: do_vfs_ioctl+0x608/0x6f0

kernel: SyS_ioctl+0x60/0x88

kernel: __sys_trace_return+0x0/0x4

native: #00 pc 0006a6f4  /system/lib64/libc.so (__ioctl+4)

native: #01 pc 000244b8  /system/lib64/libc.so (ioctl+136)

native: #02 pc 000549e8  /system/lib64/libbinder.so(android::IPCThreadState::talkWithDriver(bool)+256)

native: #03 pc 00055744  /system/lib64/libbinder.so(android::IPCThreadState::waitForResponse(android::Parcel*, int*)+340)

native: #04 pc 00055490  /system/lib64/libbinder.so(android::IPCThreadState::transact(int, unsigned int, android::Parcelconst&, android::Parcel*, unsigned int)+224)

native: #05 pc 0004c2c8  /system/lib64/libbinder.so(android::BpBinder::transact(unsigned int, android::Parcel const&,android::Parcel*, unsigned int)+72)

native: #06 pc 0012ae50  /system/lib64/libandroid_runtime.so (???)

native: #07 pc 00912244  /system/framework/arm64/boot-framework.oat(Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+196)

atandroid.os.BinderProxy.transactNative(Native method)

atandroid.os.BinderProxy.transact(Binder.java:764)

atandroid.content.ContentProviderProxy.query(ContentProviderNative.java:416)

atandroid.content.ContentResolver.query(ContentResolver.java:756)

atandroid.content.ContentResolver.query(ContentResolver.java:705)

atandroid.content.ContentResolver.query(ContentResolver.java:663)

at com.bb.bb.db.impl.SleepPreferenceImpl.get(SleepPreferenceImpl.java:97)

- locked <0x0cf84ae9> (ajava.lang.Class<com.bb.bb.db.impl.SleepPreferenceImpl>)

at com.bb.bb.util.DataPreferenceUtil.getBoolean(DataPreferenceUtil.java:54)

at com.bb.bb.manager.StepAndSleepNotificationManager.startRegisterStepCounter(StepAndSleepNotificationManager.java:87)

at com.bb.bb.service.bbService.onCreate(bbService.java:192)

atandroid.app.ActivityThread.handleCreateService(ActivityThread.java:3395)

atandroid.app.ActivityThread.-wrap4(ActivityThread.java:-1)

atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1698)

atandroid.os.Handler.dispatchMessage(Handler.java:106)

at android.os.Looper.loop(Looper.java:164)

atandroid.app.ActivityThread.main(ActivityThread.java:6608)

at java.lang.reflect.Method.invoke(Nativemethod)

atcom.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:453)

atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:821)

从这个日志大致可以看出是因为在主线程做了数据库的查找操作导致ANR,需要将这部分操作放到工作线程中处理。

5、下面官方网站的一些示例

在主线程有耗时操作可以放到AsyncTask中操作,另外不建议以匿名内部类的形式创建AsyncTask,容易导致泄露;

IO操作也建议放到工作线程里面操作;

死锁,避免死锁;

广播处理耗时,将广播消息的处理放到异步线程或IntentService;

如果耗时操作需要让用户等待,那么可以在界面上显示进度条;

工作线程持有资源锁,而主线程等着这个资源锁去干活,时间长了之后就导致ANR了,如下

@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<Integer[], Integer, Long> {   @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]);       }   }

还有一种情况是主线程等待工作线程的通知,而工作线程在做耗时操作,长时间没有回复导致。如下: