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
过程的AMS
的startService()
中。 - 3 最初会调用到
system_server
过程的ActiveService
的realStartServiceLocked()
中。
咱们就来看这个函数: 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 的工夫是 20s
static final int SERVICE_TIMEOUT = 20*1000;
// 后盾服务 ANR 的工夫是 200s
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
这里能够看到,通过 mAM.mHandler
来post
一个音讯,如果是前台服务,则检测时间是 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();
// 计算服务最晚的开始工夫,如果小于这个工夫,就是产生了 ANR
final 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 > start
final long maxTime = now - SERVICE_TIMEOUT;
// 也就是: start < maxTime
if (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.thread
是 IApplicationThread
接口,它的实现是 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
,发现它的解决在咱们的老朋友ActivityThread
的H
中,如下:
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
检测。