Framework篇 - ANR 原理分析

本文源代码基于 Android 7.0。

这篇文章来分析一下 Android 中 ANR 的原理。

 

目录:

  1. ANR 简介
  2. Service ANR 分析
  3. 如何避免 ANR

 

1. ANR 简介

ANR (Application Not responding),是指应用程序未响应,Android 系统对于一些事件需要在一定的时间范围内完成,
如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成 ANR。一般地,这时往往会弹出一个提示框,告知用户当前xxx未响应,用户可选择继续等待或者 Force Close。

ANR 场景:

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

分析 ANR 从埋炸弹,拆炸弹和引爆炸弹三个方面分析,本文章只分析 Service 的 ANR。

 

 

2. Service ANR 分析

对于前台服务,超时为 SERVICE_TIMEOUT = 20s;对于后台服务,则超时为 SERVICE_BACKGROUND_TIMEOUT = 200s。先从 startService() 作为入口。

 

  • 2.1 埋炸弹

在 Service 进程 attach 到 system_server 进程的过程中会调用 realStartServiceLocked() 方法来埋下炸弹:

/base/services/core/java/com/android/server/am/ActiveServices.java

    // 前台 Service 超时时间 20s
    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;

    // 后台 Service 超时时间 200s
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
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) {
            // ...
        } finally {
            // ...
        }
        // ...
    }
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
        long now = SystemClock.uptimeMillis();
        if (r.executeNesting == 0) {
            r.executeFg = fg;
            ServiceState stracker = r.getTracker();
            if (stracker != null) {
                stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
            }
            if (r.app != null) {
                r.app.executingServices.add(r);
                r.app.execServicesFg |= fg;
                if (r.app.executingServices.size() == 1) {
                    // 超时调度
                    scheduleServiceTimeoutLocked(r.app);
                }
            }
        } else if (r.app != null && fg && !r.app.execServicesFg) {
            r.app.execServicesFg = true;
            // 超时调度
            scheduleServiceTimeoutLocked(r.app);
        }
        r.executeFg |= fg;
        r.executeNesting++;
        r.executingStart = now;
    }
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;
        // 发送serive超时延时
        mAm.mHandler.sendMessageAtTime(msg,
                proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
    }

最后如果炸弹没有在指定时间内拆掉,就会调用 ActivityManagerService 的 Handler 发送超时消息。

 

  • 2.2 拆炸弹

在 system_server 进程 ActiveServices.realStartServiceLocked() 调用的过程会埋下一颗炸弹,超时没有启动完成则会爆炸。那么什么时候会拆除这颗炸弹呢? 经过 Binder 等层层调用进入目标进程的主线程 handleCreateService(),前面文章讲了,每个进程都有它自己的主线程 ActivityThread。

/base/core/java/android/app/ActivityThread.java

// ActivityThread 内部的 Handler 类
private class H extends Handler {
    // ...
    case CREATE_SERVICE:
       handleCreateService((CreateServiceData)msg.obj);
       break;
}

private void handleCreateService(CreateServiceData data) {
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            // 反射出服务对象
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = (Service) cl.loadClass(data.info.name).newInstance();
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }

        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();
            mServices.put(data.token, service);
            try {
                // 拆除炸弹
                ActivityManagerNative.getDefault().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }

OK,最后会回到 system_server 进程,到 ActivityManagerService 中去拆除炸弹。

/base/services/core/java/com/android/server/am/ActivityManagerService.java

    public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
        synchronized(this) {
            if (!(token instanceof ServiceRecord)) {
                Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                throw new IllegalArgumentException("Invalid service token");
            }
            mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
        }
    }

最终又绕回到 ActivityServices:

/base/services/core/java/com/android/server/am/ActiveServices.java

    private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
        // ...
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        // ...
    }

当 Service 在指定时间内启动完成,就会调用 ActivityManagerService 的 Handler 移除炸弹。

 

  • 2.3 引爆炸弹

/base/services/core/java/com/android/server/am/ActivityManagerService.java

final class MainHandler extends Handler {
        public MainHandler(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            // ...
            case SERVICE_TIMEOUT_MSG: {
                 // ...
                // Serivce在指定时间内没有执行完成,则会触发ANR
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
         }

}

/base/services/core/java/com/android/server/am/ActiveServices.java

    // 处理Service超时
    void serviceTimeout(ProcessRecord proc) {
        // ANR 信息
        String anrMessage = null;

        synchronized(mAm) {
            // ...
            if (timeout != null && mAm.mLruProcesses.contains(proc)) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new FastPrintWriter(sw, false, 1024);
                pw.println(timeout);
                timeout.dump(pw, "    ");
                pw.close();
                mLastAnrDump = sw.toString();
                mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
                anrMessage = "executing service " + timeout.shortName;
            } else {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_TIMEOUT_MSG);
                msg.obj = proc;
                mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                        ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
            }
        }

        if (anrMessage != null) {
            // 显示 ANR 弹窗
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    }

代码中包括收集 ANR 信息,然后调用 ActivityManagerService 的 AppErrors 对象的 appNotResponding()。

/base/services/core/java/com/android/server/am/AppErrors.java

    // app无相应,在AppErrors类中处理
    final void appNotResponding(ProcessRecord app, ActivityRecord activity,
            ActivityRecord parent, boolean aboveSystem, final String annotation) {
        boolean isSilentANR;
        // ...
        synchronized (mService) {
            // PowerManager.reboot() 会阻塞很长时间,因此忽略关机时的ANR
            if (mService.mShuttingDown) {
                Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
                return;
            } else if (app.notResponding) {
                Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
                return;
            } else if (app.crashing) {
                Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
                return;
            }

            app.notResponding = true;

            // 记录ANR到EventLog
            EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
                    app.processName, app.info.flags, annotation);

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

            // 将system_server进程添加到firstPids
            // 后台ANR的情况, 则直接杀掉
            isSilentANR = !showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID;
            if (!isSilentANR) {
                //..
            }
        }

        // 记录ANR输出到main log
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(app.processName);
        if (activity != null && activity.shortComponentName != null) {
            info.append(" (").append(activity.shortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(app.pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parent != null && parent != activity) {
            info.append("Parent: ").append(parent.shortComponentName).append("\n");
        }
        // ...
        synchronized (mService) {
            mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);

            if (isSilentANR) {
                // 后台anr,直接杀死app
                app.kill("bg anr", true);
                return;
            }

            // 设置app的ANR状态,病查询错误报告receiver
            makeAppNotRespondingLocked(app,
                    activity != null ? activity.shortComponentName : null,
                    annotation != null ? "ANR " + annotation : "ANR",
                    info.toString());

            // 弹出ANR对话框
            Message msg = Message.obtain();
            HashMap<String, Object> map = new HashMap<String, Object>();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = map;
            msg.arg1 = aboveSystem ? 1 : 0;
            map.put("app", app);
            if (activity != null) {
                map.put("activity", activity);
            }
            // 向ui线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息
            mService.mUiHandler.sendMessage(msg);
        }
    }

代码中会判断各种条件,然后收集日志,最后弹出 ANR 窗口。

// 弹出 ANR 弹窗
    void handleShowAnrUi(Message msg) {
        Dialog d = null;
        synchronized (mService) {
        // ...
        // If we've created a crash dialog, show it without the lock held
        if (d != null) {
            d.show();
        }
    }

 

 

3. 如何避免 ANR

如果产生了 ANR,如何分析呢,根据前面的代码,可以知道,ANR 的日志存放在 /data/anr/traces.txt 中,可以查看它来定位原因。

那么如何预防 ANR 呢?

  • 避免在主线程上进行复杂耗时的操作,比如说发送接收网络数据/进行大量计算/操作数据库/读写文件等。这个可以通过使用AsyncTask 或者使用多线程来实现。
  • BroadCastReceiver 要进行复杂操作的的时候,可以在 onReceive() 方法中启动一个 Service 来处理。
  • 在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ANR Trace分析是一种通过分析ANR错误日志(或称为ANR跟踪文件)来确定ANR错误的根本原因的方法。通过分析ANR跟踪文件,您可以了解应用程序中哪些线程阻塞了主线程,并确定导致线程阻塞的原因。 在Android设备上,您可以使用命令行工具 `adb shell dumpsys activity ANR` 来获取ANR错误日志。此命令将打印出最近的ANR错误日志,其中包含了主线程的堆栈跟踪信息、CPU使用情况、线程状态等信息。 下面是一个ANR错误日志的示例: ``` ANR in com.example.myapp PID: 1234 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. Waited for 1.0s.) Load: 98% CPU usage from 10000ms to 0ms ago: 60% com.example.myapp: 50% user + 9.7% kernel / faults: 250 minor 4 major 39% system_server: 14% user + 24% kernel / faults: 324 minor 3 major 0.1% com.android.systemui: 0% user + 0.1% kernel / faults: 17 minor 1 major 0% com.android.phone: 0% user + 0% kernel / faults: 11 minor 0% com.android.launcher: 0% user + 0% kernel / faults: 7 minor 0% kswapd0: 0% user + 0% kernel 0% kworker/u:1: 0% user + 0% kernel ``` 通过分析上面的ANR错误日志,您可以了解以下信息: - 应用程序包名为 com.example.myapp,进程ID为 1234。 - ANR出现的原因是输入事件分发超时,即应用程序在等待某个窗口处理输入事件时超时了。 - 应用程序的CPU负载达到了98%。 - 应用程序占用了60%的CPU时间。 - 系统服务 system_server 占用了39%的CPU时间。 - 其他进程的CPU使用率非常低。 通过分析这些信息,您可以确定ANR错误的原因,并尝试采取相应的措施来解决问题,比如将耗时操作移到后台线程中执行、优化代码、调整系统配置等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值