设为首页收藏本站

安徽论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 38769|回复: 0

Android ANR:原理分析及解决办法

[复制链接]

1

主题

0

回帖

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-3-10 08:54:33 | 显示全部楼层 |阅读模式
网站内容均来自网络,本站只提供信息平台,如有侵权请联系删除,谢谢!
一、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线程)中作耗时操作。
那么耗时操作就放在子线程中。
关于多线程可以参考:Android多线程:理解和简单使用总结
二、ANR分析办法

2.1 ANR重现

这里使用的是号称Google亲儿子的Google Pixel xl(Android 8.0系统)做的测试,生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:
@Overrideprotected 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命令把这个文件从手机里导出来:

  • cd到adb.exe所在的目录,也就是Android SDK的platform-tools目录,例如:
cd D:\Android\AndroidSdk\platform-tools
此外,除了Windows的cmd以外,还可以使用AndroidStudioTerminal来输入adb命令。

  • 到指定目录后执行以下adb命令导出traces.txt文件:
adb pull /data/anr/traces.txttraces.txt默认会被导出到Android SDK\platform-tools目录。一般来说traces.txt文件记录的东西会比较多,分析的时候需要有针对性地去找相关记录。
----- pid 23346 at 2017-11-07 11:33:57 -----  ----> 进程id和ANR产生时间Cmd line: com.sky.myjavatestBuild fingerprint: 'google/marlin/marlin:8.0.0/OPR3.170623.007/4286350:user/release-keys'ABI: 'arm64'Build type: optimizedZygote loaded classes=4681 post zygote classes=106Intern table: 42675 strong; 137 weakJNI: 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  (a java.lang.Object)  at java.lang.Thread.sleep(Thread.java:373)  - locked  (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 Test7267 Jps
具体分析参考:Android应用ANR分析 四.1节
2.5 ANR分析办法四:DDMS分析ANR问题


  • 使用DDMS——Update Threads工具
  • 阅读Update Threads的输出
具体分析参考:Android应用ANR分析 四.2节
三、造成ANR的原因及解决办法

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

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


  • CPU满负荷,I/O阻塞
解决办法:文件读写或数据库操作放在子线程异步操作。


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


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

特别声明:文章 理解Android ANR的触发原理 分别记录了由ServiceBroadcastReceiverContentProvider造成的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) {                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的触发原理第四节
五、Android ANR的信息收集

无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法。
参考:理解Android ANR的信息收集过程
参考资料:
理解Android ANR的触发原理
理解Android ANR的信息收集过程
Android App优化之ANR详解
Android 源码分析ANR
作者:Marker_Sky
链接:https://www.jianshu.com/p/388166988cef
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
免责声明
1. 本论坛所提供的信息均来自网络,本网站只提供平台服务,所有账号发表的言论与本网站无关。
2. 其他单位或个人在使用、转载或引用本文时,必须事先获得该帖子作者和本人的同意。
3. 本帖部分内容转载自其他媒体,但并不代表本人赞同其观点和对其真实性负责。
4. 如有侵权,请立即联系,本网站将及时删除相关内容。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表