ANR的设计原理

定时期待问题

先来看个小故事

老师给我安排了个作业,要求我10分钟内实现,他说10分钟后再来查看。

10分钟后,老师来查看,发现我作业没实现,就把我的名字写在黑板上,来警示其他人。

10分钟后,老师来查看,发现我作业写完了,就接着安排下一个作业了。

然而,这里有个问题,如果我5分钟就写完了作业,是不是能够被动去通知老师,而不是让他再多等5分钟呢?

当然能够!

这样就能够提前结束本次期待过程,大大节省时间从而提高效率。

上述过程就简略的模仿了ANR的实现原理,更术语的说法如下。

ANR的实现原理简述

  • 1 ANR的检测逻辑有两个参与者: 观测者A和被观测者B,当然,这两者是不在同一个线程中的。
  • 2 A在调用B中的逻辑时,同时在A中保留一个标记F,而后做个延时操作C,延时工夫设为T,这一步称为: 埋雷
  • 3 B中的逻辑如果被执行到,就会告诉A去革除标记F,并且告诉A解除C,这一步称为: 拆雷
  • 4 如果C没被拆除,那么在工夫T后就会被触发,就会去检测标记F是否还在,如果在,就阐明B没有在指定的工夫T内实现,那么就提醒B产生了ANR,这一步称为: 爆雷
  • 5 因为A和B是在不同线程中的,所以B即便死循环,也不会影响C的检测过程。

上述的情理也很容易了解,A和B肯定不能在同一个线程,因为如果是同一个线程,B如果陷入死循环,那么C永远都执行不到了,还检测个毛。

如果B执行完了,只去告诉A革除标记F,而不革除C能够吗,也能够!然而这个时候C还会持续期待,等到T工夫后,去检测F,F必定是不在的,就检测了个寂寞,还不如间接勾销。就像上述例子我提前去通知老师一样,B提前去通知A完结C。

所以,咱们能够将ANR更精炼的总结为: 埋雷、拆雷和爆雷三个步骤

理解了根本情理,咱们就能够通过代码来验证下,咱们来看下四大组件中的Service的ANR检测逻辑。

Service的ANR源码剖析

埋雷的过程

咱们通过context.startService(intent)来启动service最终都会调用到ContextImpl外面去,最终通过AMS来发动一次跨过程通信,最终调用到system_server过程中去启动service,这里不再废话,间接列出流程。

  • 1 A过程中调用 context.startService(intent)
  • 2 最终调用到system_server过程的AMSstartService()中。
  • 3 最初会调用到system_server过程的ActiveServicerealStartServiceLocked()中。

咱们就来看这个函数: ActiveService.realStartServiceLocked(),这里只贴出外围局部:

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {    // 外围函数: 开启ANR检测,也是埋雷和爆雷的中央    bumpServiceExecutingLocked(r, execInFg, "create");    try {        // 外围函数: 启动服务,也是拆雷的中央        app.thread.scheduleCreateService(r, r.serviceInfo,                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),                app.getReportedProcState());    } catch (DeadObjectException e) {        throw e;    } finally {    }}

外围逻辑有两个: 1 开启ANR检测; 2 启动服务。咱们先来看ANR检测函数bumpServiceExecutingLocked():

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {    //...    // 将ServiceRecord增加到ProcessRecord的executingServices外面去    r.app.executingServices.add(r);    // 开始进行ANR检测    scheduleServiceTimeoutLocked(r.app);    //...}

上述代码只贴出外围局部,r.app是一个ProcessRecord,示意以后服务所属的过程,r.app.excutingServices示意以后过程正在执行的服务的汇合,如下:

final class ServiceRecord extends Binder implements ComponentName.WithComponentName {    // 以后服务所属的过程    ProcessRecord app;}class ProcessRecord implements WindowProcessListener {    // 正在执行的服务的汇合    final ArraySet<ServiceRecord> executingServices = new ArraySet<>();}

也就是说,当初咱们曾经把要启动的Service,增加到过程的executingServices外面了,等价于增加了Flag了。

接着咱们看进行ANR检测的办法 scheduleServiceTimeoutLocked,也就是爆雷的过程

爆雷的过程

以下代码位于ActiveService中,这里只贴出外围局部。

void scheduleServiceTimeoutLocked(ProcessRecord proc) {    // 如果没有正在执行的服务 或者 过程曾经不再了,就返回    if (proc.executingServices.size() == 0 || proc.thread == null) {        return;    }    // 构建ANR音讯,记住这个Flag: SERVICE_TIMEOUT_MSG    Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);    msg.obj = proc;    // 发射delay音讯,如果是前台服务,delay工夫就是SERVICE_TIMEOUT,否则delay工夫就是SERVICE_BACKGROUND_TIMEOUT    mAm.mHandler.sendMessageDelayed(msg,proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);}// 前台服务ANR的工夫是20sstatic final int SERVICE_TIMEOUT = 20*1000;// 后盾服务ANR的工夫是200sstatic final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

这里能够看到,通过mAM.mHandlerpost一个音讯,如果是前台服务,则检测时间是20s,如果是后盾服务,检测时间是200s,那么咱们就来看下这个mAm.mHandler外面被执行时候的逻辑吧。

mAm就是ActivityManagerService,以下代码位于ActivityManagerService中,这里只贴出外围局部。

final class MainHandler extends Handler {    @Override    public void handleMessage(Message msg) {        switch (msg.what) {            // case到这个Flag了            case SERVICE_TIMEOUT_MSG: {                // 进行检测,最初调到了ActiveService.serviceTimeout()                mServices.serviceTimeout((ProcessRecord)msg.obj);            } break;        }    }}

咱们跟着主线代码ActiveService.serviceTimeout()

void serviceTimeout(ProcessRecord proc) {    // anr的音讯    String anrMessage = null;    synchronized(mAm) {        // 如果是debug引起的anr,忽视        if (proc.isDebugging()) {            return;        }        // 如果过程曾经没有要执行的服务 或者 过程不在了,就忽视        if (proc.executingServices.size() == 0 || proc.thread == null) {            return;        }        // 记录以后工夫        final long now = SystemClock.uptimeMillis();        // 计算服务最早的开始工夫,如果小于这个工夫,就是产生了ANR        final long maxTime =  now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);        // 记录超时的服务        ServiceRecord timeout = null;        // 如果没有产生ANR,则记录下一条服务的开始工夫        long nextTime = 0;        // 这里就从后面保留的executingServices列表中开始倒序比拟了,还记得咱们后面的: r.app.executingServices.add(r)吗        // 遍历寻找产生ANR的Service        for (int i=proc.executingServices.size()-1; i>=0; i--) {            ServiceRecord sr = proc.executingServices.valueAt(i);            // 如果小于最晚开始工夫,则产生了ANR            if (sr.executingStart < maxTime) {                // 记录超时的服务                timeout = sr;                break;            }            // 如果没有产生ANR,就保留下一条服务的开始工夫            if (sr.executingStart > nextTime) {                nextTime = sr.executingStart;            }        }        // 分支1: 如果产生了ANR,并且过程还在,就提醒ANR音讯        if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {            StringWriter sw = new StringWriter();            PrintWriter pw = new FastPrintWriter(sw, false, 1024);            pw.println(timeout);            timeout.dump(pw, "    ");            pw.close();            // 构建ANR音讯            anrMessage = "executing service " + timeout.shortInstanceName;        } else {            // 分支2: 如果没产生ANR,就进行下一轮观测            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));        }    }        // 如果anrMessage不为null,也就是产生了ANR音讯,就交给零碎解决(开启了ANR音讯提醒就会弹出提示框)    if (anrMessage != null) {        mAm.mAnrHelper.appNotResponding(proc, anrMessage);    }}

这块代码的逻辑是: 遍历正在执行的服务列表,查找产生了ANR的服务,如果找到了,就构建ANR音讯并交给零碎解决,否则就找到最小的下一条服务的开始执行工夫,而后从新计算工夫并进行ANR检测。

那么,ANR工夫是怎么判断的呢?咱们先看下它的相干计算:

// 记录以后工夫final long now = SystemClock.uptimeMillis();// 计算服务最晚的开始工夫,如果小于这个工夫,就是产生了ANRfinal long maxTime =  now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);if (sr.executingStart < maxTime) {  // 产生了ANR}

接着,咱们来逐条解说,咱们假如本服务是前台服务:

// 首先,获取以后工夫final long now = SystemClock.uptimeMillis();// (咱们假如是前台服务),假如服务的开始工夫是start,那么如果产生了ANR,就满足: now - start > SERVICE_TIMEOUT// 也就是: now - SERVICE_TIMEOUT > startfinal long maxTime =  now - SERVICE_TIMEOUT;// 也就是: start < maxTimeif (sr.executingStart < maxTime) {    // 产生了ANR}

看上面的图更间接:

接着,咱们来看下拆雷的过程

拆雷的过程

咱们先回顾下入口函数: ActiveService.realStartServiceLocked()

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {    // 外围函数: 开启ANR检测,也是埋雷和爆雷的中央    bumpServiceExecutingLocked(r, execInFg, "create");    try {        // 外围函数: 启动服务,也是拆雷的中央        app.thread.scheduleCreateService(r, r.serviceInfo,                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),                app.getReportedProcState());    } catch (DeadObjectException e) {        throw e;    } finally {    }}

其中,app.threadIApplicationThread接口,它的实现是ApplicationThread,是ActivityThread的一个外部类。代码如下所示:

private class ApplicationThread extends IApplicationThread.Stub {    public final void scheduleCreateService(IBinder token,ServiceInfo info, CompatibilityInfo compatInfo, int processState) {        updateProcessState(processState, false);        // 创立数据        CreateServiceData s = new CreateServiceData();        s.token = token;        s.info = info;        s.compatInfo = compatInfo;        // 通过handler发射进来,那咱们只须要跟这个 H.CREATE_SERVICE 就能够了        sendMessage(H.CREATE_SERVICE, s);    }}

咱们跟着H.CREATE_SERVICE,发现它的解决在咱们的老朋友ActivityThreadH中,如下:

class H extends Handler {    case CREATE_SERVICE:        // 又是调用了handleXXXXYYYY系列函数        handleCreateService((CreateServiceData)msg.obj);        break;}

咱们点进去:

private void handleCreateService(CreateServiceData data) {    // 暂停GC的解决    unscheduleGcIdler();    LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);    Service service = null;    try {                // 通过反射创立Service(记得当初Activity也是这么干的)        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);        Application app = packageInfo.makeApplication(false, mInstrumentation);        java.lang.ClassLoader cl = packageInfo.getClassLoader();        service = packageInfo.getAppFactory().instantiateService(cl, data.info.name, data.intent);        context.getResources().addLoaders(app.getResources().getLoaders().toArray(new ResourcesLoader[0]));        context.setOuterContext(service);        // 调用service.attach(这里保留了context)        service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService());        // 回调onCreate()函数        service.onCreate();        mServices.put(data.token, service);        try {            // 外围函数,也就是拆雷的中央!            ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    } catch (Exception e) {           //...    }}

上述逻辑: 通过反射创立服务;回调onCreate();拆雷。咱们看拆雷的外围函数serviceDoneExecuting,位于ActivityManagerService中,这里只展现外围函数。

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {    boolean inDestroying = mDestroyingServices.contains(r);    if (r != null) {        // ...                // 拆雷的外围函数        serviceDoneExecutingLocked(r, inDestroying, inDestroying);    } else {        // ...    }}

紧跟着:

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {    r.executeNesting--;    if (r.executeNesting <= 0) {        if (r.app != null) {            // 重置前台服务标记            r.app.execServicesFg = false;            // 从executingServices中移除            r.app.executingServices中移除.remove(r);            if (r.app.executingServices.size() == 0) {                // 如果没有正在执行的服务了,也就没必要再进行ANR检测了,就间接移除,也就是拆雷。                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);            } else if (r.executeFg) {                // 解决其余逻辑            }        }    }}

这就是拆雷的过程,这里有个问题,如果r.app.executingServices.size() == 0不满足呢,就不移除了吗?没错!不移除也没有影响的,因为既然跑到了这里,阐明本个服务曾经执行结束了,即便这个检测不移除,等它被执行到了,也检测不到本个服务的ANR,也就是爆雷阶段的分支2,会检测下一轮ANR信息。

当然,移除是最好的,然而为什么不移除呢?这里我也不懂,可能是因为post音讯的时候,传递的object参数是r.app,是所有服务共享的过程,而不是单个服务独有的信息,从而导致不能移除,因为一旦移除,就导致所有检测的Message都被移除。如下:

// 检测的时候Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = proc; // 这里传递的是过程proc,所有的service都用的它mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);// 移除的时候// 如果这么干了,那么如果service1跑完了,就会导致service2的检测逻辑也会被移除,// 因为service2检测用的msg.obj跟service1一样都是过程proc,所以不能移除,只能等没有服务执行了才全副移除。mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);

如果能改成如下,会更好:

// 检测的时候Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = serviceRecord.xxx; // 传递本服务的独立信息mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);// 移除的时候// 间接应用服务独立的信息,不影响其余服务mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, serviceRecord.xxx);

也可能是我了解的不对!有了解的小伙伴能够在评论区指点迷津。

总结

ANR的检测信息很简略,这里再反复下:

  • 1 将要执行的service增加到零碎过程的executingServices中。
  • 2 开启检测逻辑,检测将在指定工夫后执行,具体工夫决定与是前台服务还是后盾服务。
  • 3 一旦服务被执行完,就会尝试移除检测逻辑。
  • 4 如果检测逻辑没被移除,就会被执行,而后去检测哪个服务产生了ANR
  • 5 如果产生了ANR,就将构建ANR信息提供给零碎,否则就检测并执行下一轮ANR检测。