深入学习Android ANR 的原理分析及解决办法

目录
  • 一、ANR说明和原因
    • 1.1 简介
    • 1.2 原因
    • 1.3 避免
  • 二、ANR分析办法
    • 2.1 ANR重现
    • 2.2 ANR分析办法一:Log
    • 2.3 ANR分析办法二:traces.txt
    • 2.4 ANR分析办法三:Java线程调用分析
    • 2.5 ANR分析办法四:DDMS分析ANR问题
  • 三、造成ANR的原因及解决办法
  • 四、ANR源码分析
    • 4.1 Service造成的Service Timeout
    • 4.2 BroadcastReceiver造成的BroadcastQueue Timeout
    • 4.3 ContentProvider的ContentProvider Timeout
  • 五、Android ANR的信息收集

一、ANR说明和原因

1.1 简介

ANR全称:Application Not Responding,也就是应用程序无响应。

1.2 原因

Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。

以下四个条件都可以造成ANR发生:

  • InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
  • BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
  • Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
  • ContentProvider Timeout :ContentProvider的publish在10s内没进行完。

1.3 避免

尽量避免在主线程(UI线程)中作耗时操作。

那么耗时操作就放在子线程中。

二、ANR分析办法

2.1 ANR重现

这里使用的是号称Google亲儿子的Google Pixel xl(Android 8.0系统)做的测试,生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_anr_test);
    // 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
    // 该使用该函数不会抛出InterruptedException异常。
    SystemClock.sleep(20 * 1000);
}

在进入ANRTestActivity后黑屏一段时间,大概有七八秒,终于弹出了ANR异常。

2.2 ANR分析办法一:Log

刚才产生ANR后,看下Log:

可以看到logcat清晰地记录了ANR发生的时间,以及线程的tid和一句话概括原因:WaitingInMainSignalCatcherLoop,大概意思为主线程等待异常。

最后一句The application may be doing too much work on its main thread.告知可能在主线程做了太多的工作。

2.3 ANR分析办法二:traces.txt

刚才的log有第二句Wrote stack traces to '/data/anr/traces.txt',说明ANR异常已经输出到traces.txt文件,使用adb命令把这个文件从手机里导出来:

1.cd到adb.exe所在的目录,也就是Android SDK的platform-tools目录,例如:

cd D:\Android\AndroidSdk\platform-tools

此外,除了Windows的cmd以外,还可以使用AndroidStudio的Terminal来输入adb命令。

2.到指定目录后执行以下adb命令导出traces.txt文件:

adb pull /data/anr/traces.txt

traces.txt默认会被导出到Android SDK的\platform-tools目录。一般来说traces.txt文件记录的东西会比较多,分析的时候需要有针对性地去找相关记录。

----- pid 23346 at 2017-11-07 11:33:57 -----  ----> 进程id和ANR产生时间
Cmd line: com.sky.myjavatest
Build fingerprint: 'google/marlin/marlin:8.0.0/OPR3.170623.007/4286350:user/release-keys'
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=4681 post zygote classes=106
Intern table: 42675 strong; 137 weak
JNI: CheckJNI is on; globals=526 (plus 22 weak)
Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so
/system/lib64/libjavacrypto.so
/system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libsoundpool.so
/system/lib64/libwebviewchromium_loader.so libjavacore.so libopenjdk.so (9)
Heap: 22% free, 1478KB/1896KB; 21881 objects    ----> 内存使用情况

...

"main" prio=5 tid=1 Sleeping    ----> 原因为Sleeping
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x733d0670 self=0x74a4abea00
  | sysTid=23346 nice=-10 cgrp=default sched=0/0 handle=0x74a91ab9b0
  | state=S schedstat=( 391462128 82838177 354 ) utm=33 stm=4 core=3 HZ=100
  | stack=0x7fe6fac000-0x7fe6fae000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep(Native method)
  - sleeping on <0x053fd2c2> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:373)
  - locked <0x053fd2c2> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:314)
  at android.os.SystemClock.sleep(SystemClock.java:122)
  at com.sky.myjavatest.ANRTestActivity.onCreate(ANRTestActivity.java:20) ----> 产生ANR的包名以及具体行数
  at android.app.Activity.performCreate(Activity.java:6975)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
  at android.app.ActivityThread.-wrap11(ActivityThread.java:-1)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
  at android.os.Handler.dispatchMessage(Handler.java:105)
  at android.os.Looper.loop(Looper.java:164)
  at android.app.ActivityThread.main(ActivityThread.java:6541)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

在文件中使用 ctrl + F 查找包名可以快速定位相关代码。

  • 通过上方log可以看出相关问题:
  • 进程id和包名:pid 23346 com.sky.myjavatest
  • 造成ANR的原因:Sleeping
  • 造成ANR的具体行数:ANRTestActivity.java:20类的第20行

特别注意:产生新的ANR,原来的 traces.txt 文件会被覆盖。

2.4 ANR分析办法三:Java线程调用分析

通过JDK提供的命令可以帮助分析和调试Java应用,命令为:

jstack {pid}

其中pid可以通过jps命令获得,jps命令会列出当前系统中运行的所有Java虚拟机进程,比如

7266 Test
7267 Jps

2.5 ANR分析办法四:DDMS分析ANR问题

  • 使用DDMS——Update Threads工具
  • 阅读Update Threads的输出

三、造成ANR的原因及解决办法

上面例子只是由于简单的主线程耗时操作造成的ANR,造成ANR的原因还有很多:

主线程阻塞或主线程数据读取

解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreferenceS

CPU满负荷,I/O阻塞

解决办法:文件读写或数据库操作放在子线程异步操作。

内存不足

解决办法:AndroidManifest.xml文件<applicatiion>中可以设置 android:largeHeap="true",以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。

各大组件ANR

各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。

四、ANR源码分析

4.1 Service造成的Service Timeout

Service Timeout是位于"ActivityManager"线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

4.1.1 发送延时消息

Service进程attach到system_server进程的过程中会调用realStartServiceLocked,紧接着mAm.mHandler.sendMessageAtTime()来发送一个延时消息,延时的时常是定义好的,如前台Service的20秒。ActivityManager线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时会触发。

AS.realStartServiceLocked

ActiveServices.java

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //发送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        //最终执行服务的onCreate()方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        ...
    }
}

AS.bumpServiceExecutingLocked

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ...
    scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    long now = SystemClock.uptimeMillis();
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;

    //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

4.1.2 进入目标进程的主线程创建Service

经过Binder等层层调用进入目标进程的主线程 handleCreateService(CreateServiceData data)。

ActivityThread.java

   private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...

        try {
            //创建ContextImpl对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //创建Application对象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //调用服务onCreate()方法
            service.onCreate();

            //取消AMS.MainHandler的延时消息
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }

这个方法中会创建目标服务对象,以及回调常用的Service的onCreate()方法,紧接着通过serviceDoneExecuting()回到system_server执行取消AMS.MainHandler的延时消息。

4.1.3 回到system_server执行取消AMS.MainHandler的延时消息

AS.serviceDoneExecutingLocked

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
    ...
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                //当前服务所在进程中没有正在执行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...
}

此方法中Service逻辑处理完成则移除之前延时的消息SERVICE_TIMEOUT_MSG。如果没有执行完毕不调用这个方法,则超时后会发出SERVICE_TIMEOUT_MSG来告知ANR发生。

4.2 BroadcastReceiver造成的BroadcastQueue Timeout

BroadcastReceiver Timeout是位于"ActivityManager"线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。

4.2.1 处理广播函数 processNextBroadcast() 中 broadcastTimeoutLocked(false) 发送延时消息

广播处理顺序为先处理并行广播,再处理当前有序广播。

final void processNextBroadcast(boolean fromMsg) {
    synchronized(mService) {
        ...
        // 处理当前有序广播
        do {
            r = mOrderedBroadcasts.get(0);
            //获取所有该广播所有的接收者
            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
            if (mService.mProcessesReady && r.dispatchTime > 0) {
                long now = SystemClock.uptimeMillis();
                if ((numReceivers > 0) &&
                        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                    //step 1\. 发送延时消息,这个函数处理了很多事情,比如广播处理超时结束广播
                    broadcastTimeoutLocked(false);
                    ...
                }
            }
            if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
                if (r.resultTo != null) {
                    //2\. 处理广播消息消息
                    performReceiveLocked(r.callerApp, r.resultTo,
                        new Intent(r.intent), r.resultCode,
                        r.resultData, r.resultExtras, false, false, r.userId);
                    r.resultTo = null;
                }
                //3\. 取消广播超时ANR消息
                cancelBroadcastTimeoutLocked();
            }
        } while (r == null);
        ...

        // 获取下条有序广播
        r.receiverTime = SystemClock.uptimeMillis();
        if (!mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            //设置广播超时
            setBroadcastTimeoutLocked(timeoutTime);
        }
        ...
    }
}

上文的step 1. broadcastTimeoutLocked(false)函数:记录时间信息并调用函数设置发送延时消息

final void broadcastTimeoutLocked(boolean fromMsg) {
    ...
        long now = SystemClock.uptimeMillis();
        if (fromMsg) {
            if (mService.mDidDexOpt) {
                // Delay timeouts until dexopt finishes.
                mService.mDidDexOpt = false;
                long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
            if (!mService.mProcessesReady) {
                return;
            }

            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            if (timeoutTime > now) {
                // step 2
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
        }

上文的step 2.setBroadcastTimeoutLocked函数: 设置广播超时具体操作,同样是发送延时消息

final void setBroadcastTimeoutLocked(long timeoutTime) {
    if (! mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
    }
}

4.2.2 setBroadcastTimeoutLocked(long timeoutTime)函数的参数timeoutTime是当前时间加上设定好的超时时间。

也就是上文的

long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;

mTimeoutPeriod 也就是前台队列的10s和后台队列的60s。

public ActivityManagerService(Context systemContext) {
    ...
    static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
    static final int BROADCAST_BG_TIMEOUT = 60 * 1000;
    ...
    mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "foreground", BROADCAST_FG_TIMEOUT, false);
    mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "background", BROADCAST_BG_TIMEOUT, true);
    ...
}

4.2.3 在processNextBroadcast()过程,执行完performReceiveLocked后调用cancelBroadcastTimeoutLocked

cancelBroadcastTimeoutLocked :处理广播消息函数 processNextBroadcast() 中 performReceiveLocked() 处理广播消息完毕则调用 cancelBroadcastTimeoutLocked() 取消超时消息。

final void cancelBroadcastTimeoutLocked() {
    if (mPendingBroadcastTimeoutMessage) {
        mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
        mPendingBroadcastTimeoutMessage = false;
    }
}

4.3 ContentProvider的ContentProvider Timeout

ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。

五、Android ANR的信息收集

无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法。

参考:理解Android ANR的信息收集过程

以上就是深入学习Android ANR 的原理分析及解决办法的详细内容,更多关于Android ANR的资料请关注我们其它相关文章!

时间: 2021-11-24

全面解析Android之ANR日志

目录 一.概述 二.ANR产生机制 2.1 输入事件超时(5s) 2.2 广播类型超时(前台15s,后台60s) 2.3 服务超时(前台20s,后台200s) 2.4 ContentProvider 类型 三.导致ANR的原因 3.1 应用层导致ANR(耗时操作) 3.2 系统导致ANR 四.分析日志 4.1 CPU 负载 4.2 内存信息 4.3 堆栈消息 五.典型案例分析 5.1 主线程无卡顿,处于正常状态堆栈 5.2 主线程执行耗时操作 5.3 主线程被锁阻塞 5.4 CPU被抢占 5.5

Android ANR(Application Not Responding)的分析

Android ANR(Application Not Responding)的分析 ANR (Application Not Responding) ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框.用户可以选择"等待"而让程序继续运行,也可以选择"强制关闭".所以一个流畅的合理的应用程序中不能出现anr,而让用户每

Android ANR原理分析

目录 卡顿原理 卡顿监控 ANR原理 卡顿原理 主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR. 应用进程启动时候,Zygote会反射调用ActivityThread的main方法,启动loop循环. ActivityThread(api29) public static void main(String[] args) { Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thr

解析Android ANR问题

一.ANR介绍 ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度和超时处理.ANR 机制主体实现在系统层,所有与 ANR 相关的消息,都会经过系统进程system_server调度,具体是ActivityManagerService服务,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理. 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如 CPU/IO 使用情况.进程函数调用栈

浅谈Android ANR在线监控原理

Android中的Watchdog 在Android中,Watchdog是用来监测关键服务是否发生了死锁,如果发生了死锁就kill进程,重启SystemServer Android的Watchdog是在SystemServer中进行初始化的,所以Watchdog是运行在SystemServer进程中 Watchdog是运行一个单独的线程中的,每次wait 30s之后就会发起一个监测行为,如果系统休眠了,那Watchdog的wait行为也会休眠,此时需要等待系统唤醒之后才会重新恢复监测 想要被Wa

通过实例解析android Activity启动过程

注:只是说明启动activity的过程(ActivityThread如何与ActivityManagerService简称AmS进行进程间通信调用全过程),不解析android从zygote(受精卵)到整个系统服务的启动 具体来讲,启动activity的方式有以下几种: 在应用程序中startActivity()或startActivityForResult()方法启动指定activity 在HOME(桌面)程序中单击应用图标,启动新的activity 按"BACK"键结束当前acti

实例解析Android中使用Pull解析器解析XML的方法

1.Pull简介 Pull解析器是Android系统内置的的,Pull解析器与SAX解析器类似,他提供了类似的事件,如开始元素和介绍元素的事件,使用parser.next()可以进入下一个元素并触发相应的事件,然后进行相应的处理,当元素开始解析时,调用perser.nextText()方法就可以获取到下一个Text类型元素的值. 2.pull特点 (1)简单的结构,一个接口,一个另外,一个工厂组成了Pull解析器 (2)简单易用,Pull解析器只有一个重要的方法next(),他被用来检索下一个事

深入解析Android中View创建的全过程

前言 吸进这几天在看View的尺寸是怎样计算出来的,于是看了整个View被初始化的过程,结合系统源码总结了一下分享出来,方便需要的朋友或者自己以后有需要的时候看看,下面话不多说了,来看看详细的介绍吧. 从布局文件到LayoutParams 首先从Activity的setContentView(int)方法开始,只要设置了R.layout的布局文件,那么界面上就会显示出来对应的内容.所以以这个方法为初发点,然后往后跟踪代码. public void setContentView(@LayoutRe

解析android 流量监测的实现原理

Linux 系统下所有的信息都是以文件的形式存在的,所以应用程序的流量信息也会被保存在操作系统的文件中.Android 2.2 版本以前的系统的流量信息都存放在 proc/net/dev(或者 proc/self/net/dev)文件下,读取文件然后对其进行解析就行了.读取某一个应用的流量,则读取proc/uid_stat/uid /tcp_rcv 文件进行解析(注:模拟器下不存在这个目录).如需查看某个应用的流量信息,可以通过以下命令来实现: 复制代码 代码如下: adb devices   

从源码解析Android中View的容器ViewGroup

这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识.以便为以后能灵活的使用自定义空间打更近一步的基础.希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android. 一.ViewGroup是什么?        一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类.在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类. 其实ViewGroup也就是Vie

深入解析Android App的LayoutInflate布局

1. 题外话   相信大家对LayoutInflate都不陌生,特别在ListView的Adapter的getView方法中基本都会出现,使用inflate方法去加载一个布局,用于ListView的每个Item的布局.Inflate有三个参数,我在初学Android的时候这么理解的: (1)对于Inflate的三个参数(int resource, ViewGroup root, boolean attachToRoot): (2)如果inflate(layoutId, null )则layout

解析Android截取手机屏幕两种实现方案

最近在开发的过程中,遇到了一个需要截取屏幕保存为图片的需求,具体为截取webview的视图保存图片. 方法1:首先想到的思路是利用SDK提供的View.getDrawingCache()方法: public void printScreen(View view) { String imgPath = "/sdcard/test.png"; view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bitmap

实例解析Android系统中的ContentProvider组件用法

ContentProvider为Android四大组件之一,主要用来应用程序之间的数据共享,也就是说一个应用程序用ContentProvider将自己的数据暴露出来,其他应用程序通过ContentResolver来对其暴露出来的数据进行增删改查. ContenProvider与ContentResolver之间的对话同过Uri(通用资源标识符),一个不恰当的比喻就好像浏览器要显示一个网页要有一个东西发送请求,这相当于ContentResolver,你要拿东西就要知道去哪里拿,你就得知道服务器的域