Android进阶之ANR原因及定位

Android进阶之ANR原因及定位

        ANR全称是 Application Not Responding(应用程序无响应),从事Android开发的同学或多或少都有遇到过,尤其是做芯片方案平板开发,在入门级芯片开发过程中,由于内置的apk的过多,内存小,在操作中就很容易出现,如何快速分析并定位解决,提高用户体验,是开发者必须掌握的知识。
        ANR最直观的体验是用户在操作app时,感觉界面卡顿,无响应,比如按下某个按键,打开某个页面,界面在一定的时间内没有反应就会出现ANR对话框,提醒用户是否关闭应用。我们去查看logcat,就可以发现ANR以及traces.txt等信息。可以发现出现ANR一般都是我们在线程中做了太多的耗时操作,内存占用率一般都比较高。

ANR产生的原因

        只有当应用程序的UI线程相应超时才会引起ANR,超时原因一般两种:

        1. 当前事件正在处理,但是由于耗时太久没有及时完成。
        2. 当前事件由于某种原因被堵塞了,导致没有机会处理,例如UI线程正在响应另外一个事件。

具体对应到android组件中表现为:

  • KeyDispatchTimeout
    原因是View的按键事件或者触摸事件在特定的时间(5秒)内无法得到响应

  • BroadcastTimeout
    原因是BroadcastReceiver 的onReceive() 方法运行在主线程中,在特定的时间内(10秒)无法完成处理

  • ServiceTimeout

  • 原因是Service的各个生命周期函数在特定的时间(20秒)内无法完成处理

常见的ANR场景
  • 应用程序UI线程纯在耗时操作,例如在UI线程中进行网络请求,数据库操作或者文件操作等,可能会导致UI线程无法及时处理用户输入等事件。
  • 应用程序的UI线程等待子线程释放某个锁,从而无法响应用户输入。
  • 应用程序中适应耗时的动画或者算法,导致CPU负载过重。
ANR的定位和分析

        当发生ANR时,开发者可以通过结合Logcat 日志和生成的 data/anr/traces.txt 文件进行定位和分析。

1. logcat 日志信息

我们通过早主线程中设置点击事件,模拟一个耗时操作来是应用程序发生ANR,我们可以得到如下的logcat信息:

E/ANRManager: ANR in com.example.litepal_demo (com.example.litepal_demo/.MainActivity), time=1125878469
    Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 8.  Wait queue head age: 8541.2ms.)
    Load: 2.12 / 1.11 / 1.19
    Android time :[2020-06-05 16:10:45.21] [1125882.875]
    CPU usage from 5179ms to 0ms ago:
      164% 3873/com.example.litepal_demo: 93% user + 70% kernel / faults: 801499 minor
      1.9% 886/system_server: 0.7% user + 1.1% kernel / faults: 57 minor
      1.9% 1789/com.meizu.flyme.launcher: 1.5% user + 0.3% kernel / faults: 1702 minor
      1.5% 305/surfaceflinger: 0.7% user + 0.7% kernel
      1.5% 24446/com.android.systemui: 1.1% user + 0.3% kernel / faults: 17 minor
      0.9% 1203/hif_thread: 0% user + 0.9% kernel
      0.5% 1202/tx_thread: 0% user + 0.5% kernel
      0.5% 3780/com.meizu.mzsyncservice: 0.1% user + 0.3% kernel / faults: 223 minor
      0.3% 370/adbd: 0% user + 0.3% kernel / faults: 114 minor
      0.3% 810/ksdioirqd/mmc1: 0% user + 0.3% kernel
      0.3% 832/gsm0710muxd: 0% user + 0.3% kernel
      0.3% 2812/com.meizu.dataservice: 0.1% user + 0.1% kernel / faults: 123 minor
      0.3% 3509/kworker/0:1: 0% user + 0.3% kernel
      0.1% 72/cfinteractive: 0% user + 0.1% kernel
      0% 169/fpc1020_worker: 0% user + 0% kernel
      0.1% 811/stp_sdio_tx_rx: 0% user + 0.1% kernel
      0% 849/mtkrild: 0% user + 0% kernel / faults: 1 minor
      0.1% 1741/com.android.phone: 0% user + 0.1% kernel / faults: 2 minor
      0.1% 3579/kworker/u16:3: 0% user + 0.1% kernel
     +0% 3924/logcat: 0% user + 0% kernel
    58% TOTAL: 33% user + 25% kernel

可以看到Logcat 日志中主要包含的信息:

  • 导致ANR的类名和包名:com.example.litepal_demo/.MainActivity
  • 发生ANR的进程名称和时间:com.example.litepal_demo
  • ANR产生的原因:Input dispatching timed out 属于KeyDispatchTimeout
  • 系统中活跃进程 CPU的占用率
2. traces.txt 日志信息

从logcat中我们分析知道是引发ANR的具体类信息,以及ANR的类型,但是我们不能定位到具体引起ANR问题的代码行,为了获取进一步信息,我们需要分析traces.txt 文件:日志如下:

----- pid 3581 at 2020-06-05 16:08:38 -----
Cmd line: com.example.litepal_demo
ABI: arm64
Build type: optimized
Zygote loaded classes=3737 post zygote classes=500
Intern table: 53061 strong; 981 weak
JNI: CheckJNI is on; globals=721 (plus 1 weak)
...
Heap: 6% free, 18MB/20MB; 79033 objects
...
DALVIK THREADS (24):
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x76427fb0 self=0x7f9c49a000
  | sysTid=3581 nice=0 cgrp=default sched=0/0 handle=0x7fa04c20a8
  | state=S schedstat=( 801446077 2346305 202 ) utm=72 stm=8 core=2 HZ=100
  | stack=0x7fec052000-0x7fec054000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x3ff34301> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x3ff34301> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at com.example.litepal_demo.MainActivity.onClick(MainActivity.java:38)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
  at android.view.View.performClick(View.java:4903)
  at android.view.View$PerformClick.run(View.java:20390)
  at android.os.Handler.handleCallback(Handler.java:815)
  at android.os.Handler.dispatchMessage(Handler.java:104)
  at android.os.Looper.loop(Looper.java:194)
  at android.app.ActivityThread.main(ActivityThread.java:5898)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1019)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:814)

从日志中我们可以了解到一些主线程的信息机状态,
这里我们关注对信息: com.example.litepal_demo.MainActivity.onClick(MainActivity.java:38) 通过分析,是由于我们在MainActivity中的onClick函数中调用了 java.lang.System.arraycopyCharUnchecked! 函数导致的ANR。

ANR的避免和检测

为了避免在开发中引入可能导致应用发生ANR的问题,我们切记不要在主线程中做耗时的操作。也可以借助第三方的工具进行检测

1.StrictMode
StrictMode是Android SDk提供的一个用来检测代码中是否存在违规操作的工具类,StrictMode 主要检测两大类问题

线程策略(ThreadPolicy)

  • detectCustomSlowCalls:检测自定义耗时操作
  • detectDiskReads:检测是否存在磁盘读取操作
  • detectDiskWrites:检测是否存在磁盘写入操作
  • detectNetwork:检测是否存在网络操作

虚拟机策略 VmPolicy

  • detectActivityleaks:检测是否存在Activity泄漏
  • detectLeakedClosableObjects:检测是否存在未关闭的Closable对象泄漏
  • detectLeakedSqlLiteObjects:检测是否存在Sqlite对现泄漏
  • setClassInstanceLimit:检测类实例个数是否超过限制

注意我们只能在Debug版本中使用它,发布到市场的版本需要关闭,介绍StrictMode的简单用法:
我们只需要在应用初始化的地方例如Application和Activity类中的onCreate方法中执行如下代码:

if (BuildConfig.DEBUG){
            //开启线程模式
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
            //开启虚拟机模式
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
        }

上面的初始化代码调用penaltyLog() 表示在Logcat中打印日志,调用detectAll() 表示启动所有的策略,
我们也可以根据需求单独开启策略:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                                    .detectCustomSlowCalls()
                                    .detectNetwork()
                                    .build());

2.BlockCanary
BlockCanary 主要用来监控应用主线程的卡顿,它的基本原理是用主线程的消息队列处理机制,通过对比消息分发开始和结束的时间点来判断是否超过设定的时间,如果是,测判断主线程卡顿。其使用方法:
在build.gradle中添加依赖:

 // 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用
    debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
    releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
public class DemoApplication extends Application {
 
    @Override
    public void onCreate() {
        super.onCreate();
        BlockCanary.install(this, new AppContext()).start();
    }
}
  
//参数设置
public class AppContext extends BlockCanaryContext {
    private static final String TAG = "AppContext";
 
    @Override
    public String provideQualifier() {
    	//实现上下文,应用表示符,网络类型,卡慢判断,Log保存位置等
        String qualifier = "";
        try {
            PackageInfo info = DemoApplication.getAppContext().getPackageManager()
                    .getPackageInfo(DemoApplication.getAppContext().getPackageName(), 0);
            qualifier += info.versionCode + "_" + info.versionName";
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "provideQualifier exception", e);
        }
        return qualifier;
    }
}

BlockCanary 使用方法:https://www.jianshu.com/p/e58992439793

文章摘抄:《Android高级进阶》

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值