一篇文章搞定《Android中的ANR》

一篇文章搞定《Android中的ANR》

  • 什么是ANR
  • 举个例子帮你认识ANR
  • ANR的产生原因
  • ANR的监控手段
    • 方法一: 监控trace文件夹
    • 方法二:利用我们主线程的Looper
    • 方法三:监控SIGQUIT信号
  • ANR日志Traces.txt
    • Traces文件分析
    • 几个分析案例:
      • 一、好定位的问题(简单案例)
      • 二、不好定位的
        • 主线程被锁阻塞
        • CPU被抢占
        • 内存紧张导致ANR
        • 系统服务超时导致ANR
  • 总结

什么是ANR

是系统通过与之交互的组件以及用户交互进行超时监控,用来判断应用进程是否存在卡死或响应过慢的问题,通俗来说就是很多系统中看门狗(watchdog)的设计思想。

举个例子帮你认识ANR

问题:在Activity的onCreate方法里调用sleep方法会发生ANR吗?
大多人可能会认为只要在主线程做了耗时操作就会发生ANR,那么真的是这样吗?
在Activity的onCreate方法里调用Thread.sleep(20 * 1000)让主线程sleep20秒,会导致应用程序ANR吗?写个Demo测试一下。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Log.d("ANR","开始sleep");
            Thread.sleep(20*1000);
            Log.d("ANR","结束sleep");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如上代码,运行程序,结果应用没有发生ANR,在sleep了60秒后正常打印日志。
在这里插入图片描述
看到了吗!看到了吗! 躺平了20秒后又继续了。
让我们再运行一遍,与上次吧不同的是。我们在sleep的20种,触发一个返回事件。
一篇文章搞定《Android中的ANR》_第1张图片
看到了吗!看到了吗! ANR崩溃了!
说明了什么兄弟们:从上面的结果,我们知道了其实在主线程sleep方法或者说做耗时操作,不一定会产生ANR
导致ANR的根本还是应用程序无法在一定时间内响应用户的操作。因为主线程被占用了。聪明的兄弟们应该已经知道为什么了,没错就是我们主线程的Handler And Looper。没办法去处理下一个Message。

ANR的产生原因

  • Service Timeout:比如前台服务在20s内未执行完成,后台服务Timeout时间是前台服务的10倍,200s;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成,后台60s
  • ContentProvider Timeout:内容提供者,在publish过超时10s;
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
//ActiveServices.java
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;

//ActivityManagerService.java
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

ANR的监控手段

方法一: 监控trace文件夹

权限问题只适用于Android6之前
Android M(6.0) 版本之后,应用侧无法直接通过监听 data/anr/trace 文件,监控是否发生 ANR。

方法二:利用我们主线程的Looper

先看一下我们的Looper流程

public static void loop() {
    for (;;) {
        //1、取消息
        Message msg = queue.next(); // might block
        ...
        //2、消息处理前回调
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        ...

        //3、消息开始处理
        msg.target.dispatchMessage(msg);// 分发处理消息
        ...

        //4、消息处理完回调
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
    }
    ...
}

聪明的兄弟们已经想到了:可以利用注释2、4去获取我们处理消息的时间。从而判断我们的ANR情况。

我们只需要调用Looper.getMainLooper().setMessageLogging(printer),即可从回调中拿到Handler处理一个消息的前后时间。

需要注意的是,监听到发生卡顿之后,dispatchMessage 早已调用结束,已经出栈,此时再去获取主线程堆栈,堆栈中是不包含卡顿的代码的。

所以需要在后台开一个线程,定时获取主线程堆栈,将时间点作为key,堆栈信息作为value,保存到Map中,在发生卡顿的时候,取出卡顿时间段内的堆栈信息即可。

不过这种方案只适合线下使用,原因如下:

  1. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);存在字符串拼接,频繁调用,会创建大量对象,造成内存抖动。
  2. 后台线程频繁获取主线程堆栈,对性能有一定影响,获取主线程堆栈,会暂停主线程的运行。

方法三:监控SIGQUIT信号

这种方案才是真正的监控ANR,matrix、xCrash都在使用这种方案。已经在国民应用微信等app上检验过,稳定性和可靠性都能得到保证。

ANR流程基本是在system_server系统进程完成的,系统进程的行为我们很难监控到,想要监控这个事情就得从系统进程与应用进程沟通的边界着手,看边界上有没有可以操作的地方。
现在,我们整理一下整个ANR的流程:

  1. 系统监控到app发生ANR后,收集了一些相关进程pid(包括发生ANR的进程),准备让这些进程dump堆栈,从而生成ANR Trace文件
  2. 系统开始向这些进程发送SIGQUIT信号,进程收到SIGQUIT信号之后开始dump堆栈
    微信团队解析ANR的流程示意图:
    一篇文章搞定《Android中的ANR》_第2张图片
    可以看到,一个进程发生ANR之后的整个流程,只有dump堆栈的行为会发生在发生ANR的进程中,其他过程全在系统进程进行处理的,我们无法感知。这个过程从收到SIGQUIT信号开始到使用socket写Trace结束。然后继续回到系统进程完成剩余的ANR流程,这2个边界上我们可以做做文章。

ANR日志Traces.txt

首先traces.txt文件位置在/data/anr/目录下,可以通过以下adb命令将其拷贝到sd卡目录下获取查看。

分析traces文件来定位ANR大多数适用于线下,当然我们可以在线上将我们的traces.txt。但是我们需要这么做:
1、当监控线程发现主线程卡死时,主动向系统发送SIGNAL_QUIT信号。
2、等待/data/anr/traces.txt文件生成。
3、文件生成以后进行上报。

看起来好像可行,不过有以下两个问题:
1、traces.txt 里面包含所有线程的信息,上传之后需要人工过滤分析
2、很多高版本系统需要root权限才能读取 /data/anr这个目录
在这里插入图片描述

Traces文件分析

拿到trace文件,详细分析下:

----- pid 7761 at 2022-11-02 07:02:26 -----
Cmd line: com.xfhy.watchsignaldemo
Build fingerprint: 'HUAWEI/LYA-AL00/HWLYA:10/HUAWEILYA-AL00/10.1.0.163C00:user/release-keys'
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=11918 post zygote classes=729
Dumping registered class loaders
#0 dalvik.system.PathClassLoader: [], parent #1
#1 java.lang.BootClassLoader: [], no parent
#2 dalvik.system.PathClassLoader: [/system/app/FeatureFramework/FeatureFramework.apk], no parent
#3 dalvik.system.PathClassLoader: [/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes2.dex:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes4.dex:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes3.dex], parent #1
Done dumping class loaders
Intern table: 44132 strong; 436 weak
JNI: CheckJNI is off; globals=681 (plus 67 weak)
Libraries: /data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/lib/arm64/libwatchsignaldemo.so libandroid.so libcompiler_rt.so libhitrace_jni.so libhiview_jni.so libhwapsimpl_jni.so libiAwareSdk_jni.so libimonitor_jni.so libjavacore.so libjavacrypto.so libjnigraphics.so libmedia_jni.so libopenjdk.so libsoundpool.so libwebviewchromium_loader.so (15)
//已分配堆内存大小26M,其中2442kb医用,总分配74512个对象
Heap: 90% free, 2442KB/26MB; 74512 objects

Total number of allocations 120222 //进程创建到现在一共创建了多少对象
Total bytes allocated 10MB         //进程创建到现在一共申请了多少内存
Total bytes freed 8173KB           //进程创建到现在一共释放了多少内存
Free memory 23MB                   //不扩展堆的情况下可用的内存
Free memory until GC 23MB          //GC前的可用内存
Free memory until OOME 381MB       //OOM之前的可用内存,这个值很小的话,说明已经处于内存紧张状态,app可能是占用了过多的内存
Total memory 26MB                  //当前总内存(已用+可用)
Max memory 384MB                   //进程最多能申请的内存

.....//省略GC相关信息


//当前进程共17个线程
DALVIK THREADS (17):

//Signal Catcher线程调用栈
"Signal Catcher" daemon prio=5 tid=4 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
  | sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
  | state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
  | stack=0x7253454000-0x7253456000 stackSize=991KB
  | held mutexes= "mutator lock"(shared held)
  native: #00 pc 000000000042f8e8  /apex/com.android.runtime/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+140)
  native: #01 pc 0000000000523590  /apex/com.android.runtime/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+508)
  native: #02 pc 000000000053e75c  /apex/com.android.runtime/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+844)
  native: #03 pc 000000000053735c  /apex/com.android.runtime/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*, art::Closure*)+504)
  .....
  (no managed stack frames)

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
  | sysTid=7761 nice=-10 cgrp=default sched=1073741825/2 handle=0x72e60080d0
  | state=S schedstat=( 281909898 5919799 311 ) utm=20 stm=7 core=4 HZ=100
  | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
  | held mutexes=
  at java.lang.Thread.sleep(Native method)
  - sleeping on <0x00f895d9> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:443)
  - locked <0x00f895d9> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:359)
  at android.os.SystemClock.sleep(SystemClock.java:131)
  at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:35)
  ... //此处省略剩余的N个线程

trace参数详细解读:

"Signal Catcher" daemon prio=5 tid=4 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
  | sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
  | state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
  | stack=0x7253454000-0x7253456000 stackSize=991KB
  | held mutexes= "mutator lock"(shared held)

Signal Catcher" daemon prio=5 tid=4 Runnable

  • “Signal Catcher” daemon : 线程名,有daemon表示守护线程
  • prio:线程优先级
  • tid:线程内部id
  • 线程状态:Runnable
    一篇文章搞定《Android中的ANR》_第3张图片

一般来说:main线程处于BLOCK、WAITING、TIMEWAITING状态,基本上是函数阻塞导致的ANR,如果main线程无异常,则应该排查CPU负载和内存环境。

几个分析案例:

一、好定位的问题(简单案例)

首先看到Loacat日志:

07-22 21:39:17.019 819-851/? E/ActivityManager: ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)
PID: 7398
Reason: Input dispatching timed out (com.xxxx.performance/com.xxxx.performance.view.home.activity.MainActivity, 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: 29.  Wait queue head age: 8579.3ms.)
Load: 18.22 / 18.1 / 18.18
CPU usage from 0ms to 8653ms later:
124% 7398/com.xxxx.performance: 118% user + 6.5% kernel / faults: 4962 minor 7 major
82% 819/system_server: 28% user + 53% kernel / faults: 10555 minor 11 major
23% 4402/adbd: 1% user + 22% kernel
10% 996/com.android.systemui: 4.6% user + 6.2% kernel / faults: 4677 minor 1 major
4.6% 2215/com.android.phone: 1.5% user + 3.1% kernel / faults: 5411 minor
6.3% 6268/perfd: 3.4% user + 2.8% kernel / faults: 134 minor
0.5% 1149/com.miui.whetstone: 0.1% user + 0.3% kernel / faults: 3016 minor 1 major
0.2% 2097/com.xiaomi.finddevice: 0.1% user + 0.1% kernel / faults: 2256 minor
0.6% 2143/com.miui.daemon: 0.2% user + 0.3% kernel / faults: 2798 minor
1.2% 1076/com.xiaomi.xmsf: 0.6% user + 0.6% kernel / faults: 2802 minor
......

从日志第一行开始看,可以看到发生错误的应用包名和类名,这里是

ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)

接着看到进程号PID为7398。发生ANR的Reason是Input dispatching timed out就是上面提到的第一种。再往下就是活跃进程的CPU占用率日志。

光看Logcat中的日志只能看到这些信息,大概知道是在MainActivity出现了问题,但还是不能清楚的定位到发生ANR的代码行,想要获得进一步的错误信息只能通过查看ANR过程中生成的堆栈信息文件traces.txt了。
traces.txt里的信息:

DALVIK THREADS (42):
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 obj=0x75ceafb8 self=0x55933ae7e0
| sysTid=7398 nice=0 cgrp=default sched=0/0 handle=0x7f7ddae0f0
| state=S schedstat=( 101485399944 3411372871 31344 ) utm=9936 stm=212 core=1 HZ=100
| stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
| held mutexes=
kernel: __switch_to+0x74/0x8c
kernel: futex_wait_queue_me+0xcc/0x158
kernel: futex_wait+0x120/0x20c
kernel: do_futex+0x184/0xa48
kernel: SyS_futex+0x88/0x19c
kernel: cpu_switch_to+0x48/0x4c
native: #00 pc 00017750  /system/lib64/libc.so (syscall+28)
native: #01 pc 000d1584  /system/lib64/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+140)
native: #02 pc 00388098  /system/lib64/libart.so (_ZN3artL12GoToRunnableEPNS_6ThreadE+1068)
native: #03 pc 000a5db8  /system/lib64/libart.so (_ZN3art12JniMethodEndEjPNS_6ThreadE+24)
native: #04 pc 000280e4  /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_graphics_Paint_native_1init__+156)
at android.graphics.Paint.native_init(Native method)
at android.graphics.Paint.<init>(Paint.java:435)
at android.graphics.Paint.<init>(Paint.java:425)
at android.text.TextPaint.<init>(TextPaint.java:49)
at android.text.Layout.<init>(Layout.java:160)
at android.text.StaticLayout.<init>(StaticLayout.java:111)
at android.text.StaticLayout.<init>(StaticLayout.java:87)
at android.text.StaticLayout.<init>(StaticLayout.java:66)
at android.widget.TextView.makeSingleLayout(TextView.java:6543)
at android.widget.TextView.makeNewLayout(TextView.java:6383)
at android.widget.TextView.checkForRelayout(TextView.java:7096)
at android.widget.TextView.setText(TextView.java:4082)
at android.widget.TextView.setText(TextView.java:3940)
at android.widget.TextView.setText(TextView.java:3915)
at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)
at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:214)
at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:205)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:198)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:250)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
......
......

还是从头开始看,来看每个字段对应的含义:

线程名:main
线程优先级:prio=5
线程锁ID: tid=1
线程状态:Native
线程组名称:group=“main”
线程被挂起的次数:sCount=1
线程被调试器挂起的次数:dsCount=0
线程的java的对象地址:obj=0x75ceafb8
线程本身的Native对象地址:self=0x55933ae7e0
###################################
线程调度信息:
Linux系统中内核线程ID:sysTid=7398与主线程的进程号相同
线程调度优先级:nice=0
线程调度组:cgrp=default
线程调度策略和优先级:sched=0/0
线程处理函数地址:handle=0x7f7ddae0f0
######################################
线程的上下文信息:
线程调度状态:state=S
线程在CPU中的执行时间、线程等待时间、线程执行的时间片长度:schedstat=(1014853999443411372871 31344 )
线程在用户态中的调度时间值:utm=9936
线程在内核态中的调度时间值:stm=212
最后执行这个线程的CPU核序号:core=1
#######################################
线程的堆栈信息:
堆栈地址和大小:stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB

最后看到堆栈信息里的这一行:

at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)

这里就看清楚了是在AttendanceCheckInFragment中的887行出现的问题,再到对应代码行中就很容易发现ANR的原因了。

二、不好定位的

主线程被锁阻塞

fun makeAnr(view: View) {

    val obj1 = Any()
    val obj2 = Any()

    //搞个死锁,相互等待

    thread(name = "卧槽") {
        synchronized(obj1) {
            SystemClock.sleep(100)
            synchronized(obj2) {
            }
        }
    }

    synchronized(obj2) {
        SystemClock.sleep(100)
        synchronized(obj1) {
        }
    }
}
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
  | sysTid=19900 nice=-10 cgrp=default sched=0/0 handle=0x72e60080d0
  | state=S schedstat=( 542745832 9516666 182 ) utm=48 stm=5 core=4 HZ=100
  | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
  | held mutexes=
  at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:59)
  - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22   //注释1
  - locked <0x01abeb23> (a java.lang.Object)
  at java.lang.reflect.Method.invoke(Native method)
  at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
  at android.view.View.performClick(View.java:7317)
  at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
  at android.view.View.performClickInternal(View.java:7291)
  at android.view.View.access$3600(View.java:838)
  at android.view.View$PerformClick.run(View.java:28247)
  at android.os.Handler.handleCallback(Handler.java:900)
  at android.os.Handler.dispatchMessage(Handler.java:103)
  at android.os.Looper.loop(Looper.java:219)
  at android.app.ActivityThread.main(ActivityThread.java:8668)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)

"卧槽" prio=5 tid=22 Blocked  //注释2
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x12c8a118 self=0x71d625f800
  | sysTid=20611 nice=0 cgrp=default sched=0/0 handle=0x71d4513d50
  | state=S schedstat=( 486459 0 3 ) utm=0 stm=0 core=4 HZ=100
  | stack=0x71d4411000-0x71d4413000 stackSize=1039KB
  | held mutexes=
  at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:52)
  - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
  - locked <0x0c6f8c52> (a java.lang.Object)  
  at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:49)
  at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)

......

注意看,下面几行:

"main" prio=5 tid=1 Blocked
  - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22
  - locked <0x01abeb23> (a java.lang.Object)

"卧槽" prio=5 tid=22 Blocked
  - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
  - locked <0x0c6f8c52> (a java.lang.Object)  

主线程的tid是1,线程状态是Blocked,正在等待0x0c6f8c52这个Object,而这个Object被thread 22这个线程所持有,主线程当前持有的是0x01abeb23的锁。而卧槽的tid是22,也是Blocked状态,它想请求的和已有的锁刚好与主线程相反。这样的话,ANR原因也就找到了:线程22持有了一把锁,并且一直不释放,主线程等待这把锁发生超时。在线上环境,常见因锁而ANR的场景是SharePreference写入。

CPU被抢占

CPU usage from 0ms to 10625ms later (2020-03-09 14:38:31.633 to 2020-03-09 14:38:42.257):
  543% 2045/com.test.demo: 54% user + 89% kernel / faults: 4608 minor 1 major //注意看这里
  99% 674/android.hardware.camera.provider@2.4-service: 81% user + 18% kernel / faults: 403 minor
  24% 32589/com.wang.test: 22% user + 1.4% kernel / faults: 7432 minor 1 major
  ......

可以看到,该进程占据CPU高达543%,抢占了大部分CPU资源,因为导致发生ANR,这种ANR与我们的app无关。

内存紧张导致ANR

如果一份ANR日志的CPU和堆栈都很正常,可以考虑是内存紧张。看一下ANR日志里面的内存相关部分。还可以去日志里面搜一下onTrimMemory,如果dump ANR日志的时间附近有相关日志,可能是内存比较紧张了。

10-31  E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31  E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31  E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31  E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31  E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0

系统服务超时导致ANR

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x727851e8 self=0x78d7060e00
  | sysTid=4894 nice=0 cgrp=default sched=0/0 handle=0x795cc1e9a8
  | state=S schedstat=( 8292806752 1621087524 7167 ) utm=707 stm=122 core=5 HZ=100
  | stack=0x7febb64000-0x7febb66000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0x90/0xc4
  kernel: binder_thread_read+0xbd8/0x144c
  kernel: binder_ioctl_write_read.constprop.58+0x20c/0x348
  kernel: binder_ioctl+0x5d4/0x88c
  kernel: do_vfs_ioctl+0xb8/0xb1c
  kernel: SyS_ioctl+0x84/0x98
  kernel: cpu_switch_to+0x34c/0x22c0
  native: #00 pc 000000000007a2ac  /system/lib64/libc.so (__ioctl+4)
  native: #01 pc 00000000000276ec  /system/lib64/libc.so (ioctl+132)
  native: #02 pc 00000000000557d4  /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+252)
  native: #03 pc 0000000000056494  /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+60)
  native: #04 pc 00000000000562d0  /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+216)
  native: #05 pc 000000000004ce1c  /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+72)
  native: #06 pc 00000000001281c8  /system/lib64/libandroid_runtime.so (???)
  native: #07 pc 0000000000947ed4  /system/framework/arm64/boot-framework.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+196)
  at android.os.BinderProxy.transactNative(Native method) ————————————————关键行!!!
  at android.os.BinderProxy.transact(Binder.java:804)
  at android.net.IConnectivityManager$Stub$Proxy.getActiveNetworkInfo(IConnectivityManager.java:1204)—关键行!
  at android.net.ConnectivityManager.getActiveNetworkInfo(ConnectivityManager.java:800)
  at com.xiaomi.NetworkUtils.getNetworkInfo(NetworkUtils.java:2)
  at com.xiaomi.frameworkbase.utils.NetworkUtils.getNetWorkType(NetworkUtils.java:1)
  at com.xiaomi.frameworkbase.utils.NetworkUtils.isWifiConnected(NetworkUtils.java:1)

从日志堆栈中可以看到是获取网络信息发生了ANR:getActiveNetworkInfo。系统的服务都是Binder机制(16个线程),服务能力也是有限的,有可能系统服务长时间不响应导致ANR。如果其他应用占用了所有Binder线程,那么当前应用只能等待。可进一步搜索:blockUntilThreadAvailable关键字:

at android.os.Binder.blockUntilThreadAvailable(Native method)

如果有发现某个线程的堆栈,包含此字样,可进一步看其堆栈,确定是调用了什么系统服务。此类ANR也是属于系统环境的问题,如果某类型手机上频繁发生此问题,应用层可以考虑规避策略。

总结

大部分同学可能都是做业务需求为主,对于ANR问题,可能不太注重,或者直接依赖第三方,例如Bugly,但是呢,在面试中,面试官基本不太会问你这些工具的使用。所以还是学习一下吧。

你可能感兴趣的:(一篇文章搞定Android,Android,android,java)