Android之ANR问题分析,触发原理,解决办法

本文详细介绍了Android应用中的ANR(Application Not Responding)问题,包括ANR的定义、触发原因、系统响应以及如何分析和解决ANR。重点探讨了主线程阻塞、CPU满负荷、内存不足等因素导致ANR,提供了避免主线程执行耗时任务、使用子线程处理I/O操作、优化内存使用等解决办法。通过Logcat和traces文件,分析了ANR发生时系统的响应行为,为开发者提供了有效的ANR问题诊断和解决途径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 1.ANR是什么?

Application Not Responding,字面意思就是应用无响应,稍加解释就是用户的一些操作无法从应用中获取反馈;在实际的应用中应当去避免这种现象,虽然它暂时不会造成应用崩溃,但是却极大地损坏用户体验;

2.ANR触发原因

出现ANR之后一个直观的现象就是系统会展示出一个ANR对话框,如图:

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

那么哪些场景会造成ANR呢?

  • Service Timeout:比如前台服务在20s内未执行完成;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  • ContentProvider Timeout:内容提供者,在publish过超时10s;
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

因此避免以上四种情况就是解决ANR的关键;

导致ANR无响应的常见原因:

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

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

  • CPU满负荷,I/O阻塞

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

  • 内存不足

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

  • 各大组件ANR

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

3.ANR时系统做了什么?

ANR时展示给用户什么内容,源码分析ANR做了哪些事情,知道ANR做了什么,更有助于我们分析ANR;

3.1展示一个应用无反应的对话框

在Activity内为Button添加点击事件,点击时让主线程休眠30秒,在连续点击几次Button按钮;

        Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
                // 该使用该函数不会抛出InterruptedException异常。
                SystemClock.sleep(30 * 1000);
            }
        });

 在点击Button按钮第一次以后,在连续点击几次Button按钮,大概七八秒,终于弹出ANR异常;

3.2将ANR信息输出到Logcat

产生ANR时Logcat会同时如下内容:

10-15 01:47:20.975 4038-4046/com.gome.childrenmanager I/art: Thread[5,tid=4046,WaitingInMainSignalCatcherLoop,Thread*=0xaf00d400,peer=0x12c00080,"Signal Catcher"]: reacting to signal 3
10-15 01:47:20.985 4038-4046/com.gome.childrenmanager I/art: Wrote stack traces to '/data/anr/traces.txt'
10-15 01:47:45.820 4038-4038/com.gome.childrenmanager I/Choreographer: Skipped 42 frames!  The application may be doing too much work on its main thread.

可以看到Logcat清晰的记录ANR发生的时间,以及线程的tid和一句话概括的原因:WaitingInMainSignalCatcherLoop主线程等待异常,最后一句话 Skipped 42 frames!  The application may be doing too much work on its main thread.告知主线程被阻塞导致帧画面无法刷新;

3.3将ANR信息输出到traces.txt文件中

Wrote stack traces to '/data/anr/traces.txt

通过Logcat输出日志看到会将ANR信息写入traces.txt文件,traces.txt文件是一个ANR记录文件,用于开发人员调试分析ANR产生原因,目录在/data/anr中,可以通过adb pull命令导出:

adb pull /data/anr/traces.txt    C:\anr

adb命令默认在Android SDK下platform-tools目录,没有配置adb环境变量的需要进入platform-tools目录执行adb命令;

不指定traces.txt导出默认,默认导出到adb所在命令目录;后面对如何分析traces.txt这个文件做详细描述;

3.4ANR源码分析

最后来看看做出上述反应的源代码,这部分代码位于ProcessRecord类中;

ActivityManagerService监听到ANR信息,调用AnrHelper下的方法appNotResponding(),AnrHelper在后台开启一个独立的线程去处理ANR消息,以便缩短处理时间;

AnrHelper
private class AnrConsumerThread extends Thread {
        @Override
        public void run() {
            AnrRecord r;
            r.appNotResponding(onlyDumpSelf);

        }
    }

private static class AnrRecord {
        final ProcessRecord mApp;
        void appNotResponding(boolean onlyDumpSelf) {
            mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
                    mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
                    onlyDumpSelf);
        }
    }

ProcessRecord.appNotResponding()

ProcessRecord.java
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
            String parentShortComponentName, WindowProcessController parentProcess,
            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
        ArrayList<Integer> firstPids = new ArrayList<>(5);
        SparseArray<Boolean> lastPids = new SparseArray<>(20);

        mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
                  ApplicationExitInfo.REASON_ANR, true));

        long anrTime = SystemClock.uptimeMillis();
        
        if (isMonitorCpuUsage()) {
            //第一次,更新CPU统计信息
            mService.updateCpuStatsNow();
        }

        final boolean isSilentAnr;
        synchronized (mService) {
            // 某些特定情况下忽略本次ANR,比如系统关机,比如该进程已经处于anr状态或者crash状态
            if (mService.mAtmInternal.isShuttingDown()) {
                Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
                return;
            } else if (isNotResponding()) {
                Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
                return;
            } else if (isCrashing()) {
                Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
                return;
            } else if (killedByAm) {
                Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
                return;
            } else if (killed) {
                Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
                return;
            }

            //为了防止多次对相同APP的anr执行重复代码,在此处标注记录,属于上面的特定情况中的一种
            setNotResponding(true);

            // 记录ANR信息到Event Log中
            EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
                    annotation);

            //将当前进程添加到firstPids
            firstPids.add(pid);

            // 如果它是一个后台的ANR或者仅仅请求导出他自己,不需要加入其它PIDS
            isSilentAnr = isSilentAnr();
            if (!isSilentAnr && !onlyDumpSelf) {
                int parentPid = pid;
                if (parentProcess != null && parentProcess.getPid() > 0) {
                    parentPid = parentProcess.getPid();
                }
                if (parentPid != pid) firstPids.add(parentPid);
                 //将system_server进程添加到firstPids
                if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
                // 添加所有进程到firstpids中
                for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
                    ProcessRecord r = getLruProcessList().get(i);
                    if (r != null && r.thread != null) {
                        int myPid = r.pid;
                        if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
                            if (r.isPersistent()) {
                                firstPids.add(myPid);    //将persistent进程添加到firstPids
                                if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
                            } else if (r.treatLikeActivity) {
                                firstPids.add(myPid);
                                if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
                            } else {    //其它进程添加到lastPids
                                lastPids.put(myPid, Boolean.TRUE);
                                if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
                            }
                        }
                    }
                }
            }
        }

        // 记录ANR输出到main log
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(processName);
        if (activityShortComponentName != null) {
            info.append(" (").append(activityShortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parentShortComponentName != null
                && parentShortComponentName.equals(activityShortComponentName)) {
            info.append("Parent: ").append(parentShortComponentName).append("\n");
        }

        StringBuilder report = new StringBuilder();
        report.append(MemoryPressureUtil.currentPsiState());
        //创建CPU tracker对象
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值