仅做学习和记载,计划非原创。
1. ANR是什么
ANR全称是Applicatipon No Response,Android规划ANR的意图,是系统经过与之交互的组件以及用户交互进行超时监控,用来判别运用进程是否存在卡死或呼应过慢的问题,通俗来说便是许多系统中看门狗(watchdog)的规划思想。
2. 导致ANR的原因
耗时操作导致ANR,并不必定是app的问题,实际上,有很大的概率是系统原因导致的ANR。下面简略分析一下哪些操作是运用层导致的ANR,哪些是系统导致的ANR。
运用层导致ANR:
- 函数堵塞:如死循环、主线程IO、处理大数据
- 锁出错:主线程等候子线程的锁
- 内存严峻:系统分配给一个运用的内存是有上限的,长期处于内存严峻,会导致频频内存交换,进而导致运用的一些操作超时
系统导致ANR:
- CPU被抢占:一般来说,前台在玩游戏,或许会导致你的后台播送被抢占
- 系统服务无法及时呼应:比方获取系统联系人等,系统的服务都是Binder机制,服务才能也是有限的,有或许系统服务长期不呼应导致ANR
- 其他运用占用大量内存
3. 线下拿到ANR日志
- adb pull /data/anr/
- adb bugreport
缺点:
- 只能线下,用户反应时,无法获取ANR日志
- 或许没有库房信息
4. ANR场景
- Service Timeout:比方前台服务在20s内未履行完结,后台服务Timeout时刻是前台服务的10倍,200s;
- BroadcastQueue Timeout:比方前台播送在10s内未履行完结,后台60s
- ContentProvider Timeout:内容提供者,在publish过超时10s;
- InputDispatching Timeout: 输入事情分发超时5s,包括按键和接触事情。
//ActiveServices.java
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;
//ActivityManagerService.java
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
5. ANR触发流程
ANR触发流程大致可分为2种,一种是Service、Broadcast、Provider触发ANR,别的一种是Input触发ANR。
5.1 Service、Broadcast、Provider触发ANR
大体流程可分为3个步骤:
- 埋守时炸弹
- 拆炸弹
- 引爆破弹
下面举个startService的例子,具体说说这3个步骤:
1.埋守时炸弹
在Activity中调用startService后,调用链:ContextImpl.startService()->ContextImpl.startServiceCommon()->ActivityManagerService.startService()->ActiveServices.startServiceLocked()->ActiveServices.startServiceInnerLocked()->ActiveServices.bringUpServiceLocked()->ActiveServices.realStartServiceLocked()
//com.android.server.am.ActiveServices.java
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
......
//发个推迟音讯给AMS的Handler
bumpServiceExecutingLocked(r, execInFg, "create");
......
try {
//IPC告诉app进程发动Service,履行handleCreateService
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
app.getReportedProcState());
} catch (DeadObjectException e) {
} finally {
}
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
scheduleServiceTimeoutLocked(r.app);
.....
}
final ActivityManagerService mAm;
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
//mAm是AMS,mHandler是AMS里边的一个Handler
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//发个推迟音讯给AMS里边的一个Handler
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
在startService流程中,在告诉app进程发动Service之前,会进行预埋一个炸弹,也便是推迟发送一个音讯给AMS的mHandler。当AMS的这个Handler收到SERVICE_TIMEOUT_MSG
这个音讯时,就以为Service超时了,触发ANR。也便是说,特守时刻内,没人来拆这个炸弹,这个炸弹就会爆破。
2. 拆炸弹
在AMS校验经往后,app这边能够发动Service,所以来到了ApplicationThread的scheduleCreateService办法,该办法是运转在binder线程里边的,所以得切到主线程去履行,也便是ActivityThread的handleCreateService办法:
//android.app.ActivityThread.java
@UnsupportedAppUsage
private void handleCreateService(CreateServiceData data) {
......
Service service = null;
try {
//1. 初始化Service
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);
......
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//2. Service履行onCreate,发动完结
service.onCreate();
mServices.put(data.token, service);
try {
//3. Service发动完结,需求告诉AMS
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
}
} catch (Exception e) {
}
}
在app进程这边发动完Service之后,需求IPC通讯告知AMS我这边现已发动完结了。AMS.serviceDoneExecuting()->ActiveServices.serviceDoneExecutingLocked()
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
......
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
......
}
很清晰,便是把之前推迟发送的SERVICE_TIMEOUT_MSG
音讯给移除去,也便是拆炸弹。只要在规则的时刻内把炸弹拆了,那就没事,要是没拆,炸弹就要爆破,触发ANR。
3. 引爆破弹
之前推迟给AMS的handler发送了一个音讯,mAm.mHandler.sendMessageDelayed(msg,proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
,下面咱们来看一下这条音讯的逻辑
//com.android.server.am.ActivityManagerService.java
final MainHandler mHandler;
final class MainHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
......
case SERVICE_TIMEOUT_MSG: {
//这个mServices是ActiveServices
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
}
......
}
......
}
//com.android.server.am.ActiveServices.java
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
//核算是否有service超时
final long now = SystemClock.uptimeMillis();
final long maxTime = now -
(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
for (int i=proc.executingServices.size()-1; i>=0; i--) {
ServiceRecord sr = proc.executingServices.valueAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
}
}
if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {
anrMessage = "executing service " + timeout.shortInstanceName;
}
}
if (anrMessage != null) {
//有超时的Service,mAm是AMS,mAnrHelper是AnrHelper
mAm.mAnrHelper.appNotResponding(proc, anrMessage);
}
}
AMS这边假如收到了SERVICE_TIMEOUT_MSG
音讯,也便是超时了,没人来拆炸弹,那么它会让ActiveServices承认一下是否有Service超时,有的话,再使用AnrHelper来触发ANR。
void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
//增加AnrRecord到List里边
synchronized (mAnrRecords) {
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
parentShortComponentName, parentProcess, aboveSystem, annotation));
}
startAnrConsumerIfNeeded();
}
private void startAnrConsumerIfNeeded() {
if (mRunning.compareAndSet(false, true)) {
//开个子线程来处理
new AnrConsumerThread().start();
}
}
private class AnrConsumerThread extends Thread {
@Override
public void run() {
AnrRecord r;
while ((r = next()) != null) {
......
//这儿的r便是AnrRecord
r.appNotResponding(onlyDumpSelf);
......
}
}
}
private static class AnrRecord {
void appNotResponding(boolean onlyDumpSelf) {
//mApp是ProcessRecord
mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
onlyDumpSelf);
}
}
开了个子线程,然后调用ProcessRecord的appNotResponding办法来处理ANR的流程(弹出app无呼应弹窗、dump库房什么的),具体流程下面会细说。到这儿,炸弹就彻底引爆了,触发了ANR。
5.2 Input触发ANR
input的超时检测机制跟Service、Broadcast、Provider截然不同,并非时刻到了就必定被爆破,而是处理后续上报事情的进程才会去检测是否该爆破,所以更像是扫雷的进程。
input超机遇制为什么是扫雷,而非守时爆破?由于关于input来说即便某次事情履行时刻超越Timeout时长,只要用户后续没有再生成输入事情,则不会触发ANR。这儿的扫雷是指当时输入系统中正在处理着某个耗时事情的前提下,后续的每一次input事情都会检测前一个正在处理的事情是否超时(进入扫雷状况),检测当时的时刻距离上次输入事情分发时刻点是否超越timeout时长。假如没有超越,则会重置anr的Timeout,然后不会爆破。
5.3 哪些路径会引发ANR?
从埋下炸弹到拆炸弹之间的任何一个或多个路径履行慢都会导致ANR。这儿以Service为例,如:
- Service的生命周期的回调办法履行慢
- 主线程的音讯行列存在其他耗时音讯让Service回调办法迟迟得不到履行
- sp操作履行慢
-
system_server
进程的binder线程繁忙而导致没有及时收到拆炸弹的指令
5.4 ANR dump主要流程
ANR流程基本是在
system_server
系统进程完结的,系统进程的行为咱们很难监控到,想要监控这个事情就得从系统进程与运用进程沟通的鸿沟着手,看鸿沟上有没有能够操作的地方。
不管是怎样产生的ANR,最终都会走到appNotResponding
,比方输入超时的路径
ActivityManagerService#inputDispatchingTimedOut
AnrHelper#appNotResponding
AnrConsumerThread#run
AnrRecord#appNotResponding
ProcessRecord#appNotResponding
那咱们直接分析这个appNotResponding
办法:
//com.android.server.am.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()) {
mService.updateCpuStatsNow();
}
final boolean isSilentAnr;
synchronized (mService) {
//注释1
// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
//正在重启
if (mService.mAtmInternal.isShuttingDown()) {
Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
return;
} else if (isNotResponding()) {
//现已处于ANR流程中
Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
return;
} else if (isCrashing()) {
//正在crash的状况
Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
return;
} else if (killedByAm) {
//app现已被killed
Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
return;
} else if (killed) {
//app现已死亡了
Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
return;
}
// In case we come through here for the same app before completing
// this one, mark as anring now so we will bail out.
//做个符号
setNotResponding(true);
// Log the ANR to the event log.
EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
firstPids.add(pid);
// Don't dump other PIDs if it's a background ANR or is requested to only dump self.
//注释2
//缄默沉静的anr : 这儿表明后台anr
isSilentAnr = isSilentAnr();
if (!isSilentAnr && !onlyDumpSelf) {
int parentPid = pid;
if (parentProcess != null && parentProcess.getPid() > 0) {
parentPid = parentProcess.getPid();
}
if (parentPid != pid) firstPids.add(parentPid);
if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
//挑选需求dump的进程
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);
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.put(myPid, Boolean.TRUE);
if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
}
}
}
}
}
}
......
int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
ArrayList<Integer> nativePids = null;
if (pids != null) {
nativePids = new ArrayList<>(pids.length);
for (int i : pids) {
nativePids.add(i);
}
}
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
StringWriter tracesFileException = new StringWriter();
// To hold the start and end offset to the ANR trace file respectively.
final long[] offsets = new long[2];
//注释4
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePids, tracesFileException, offsets);
......
}
代码比较长,咱们一步一步来看。
注释1处首要是针对几种特殊状况:正在重启、现已处于ANR流程中、正在crash、app现已被killed和app现已死亡了,不必处理ANR,直接return。
注释2处isSilentAnr是表明当时是否为一个后台ANR,后台ANR跟前台ANR体现不同,前台ANR会弹出无呼应的Dialog,后台ANR会直接杀死进程。什么是前台ANR:产生ANR的进程对用户来说有感知,便是前台ANR,否则便是后台ANR。
注释3处,挑选需求dump的进程。产生ANR时,为了便利定位问题,会dump许多信息到Trace文件中。而Trace文件里包括着与ANR相关联的进程的Trace信息,由于产生ANR的原因有或许是其他的进程抢占了太多资源,或许IPC到其他进程的时分卡住导致的。需求被dump的进程分为3类:
- firstPids:firstPids是需求首要dump的重要进程,产生ANR的进程无论怎么是必定要被dump的,也是首要被dump的,所以第一个被加到firstPids中。假如是SilentAnr(即后台ANR),不必再参加任何其他的进程。假如不是,需求进一步增加其他的进程:假如产生ANR的进程不是system_server进程的话,需求增加system_server进程;接下来轮询AMS维护的一个LRU的进程List,假如最近访问的进程包括了persistent的进程,或许带有
*BIND_TREAT_LIKE_ACTVITY
* 标签的进程,都增加到firstPids中。 - extraPids:LRU进程List中的其他进程,都会首要增加到lastPids中,然后lastPids会进一步被选出最近CPU运用率高的进程,进一步组成extraPids;
- nativePids:nativePids最为简略,是一些固定的native的系统进程,界说在WatchDog.java中
注释4处,拿到需求dump的一切进程的pid后,AMS开端依照firstPids、nativePids、extraPids的次序dump这些进程的库房。这儿比较重要,咱们需求跟进去看看具体做了什么。
public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {
// 最多dump 20秒
long remainingTime = 20 * 1000;
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
final int pid = firstPids.get(i);
final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
remainingTime -= timeTaken;
if (remainingTime <= 0) {
Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
+ "); deadline exceeded.");
return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
}
}
}
......
}
便是依据次序取出前面传入的firstPids、nativePids
、extraPids
的pid,然后逐一去dump这些进程中一切的线程,当然这是一个十分重的操作,一个进程就有那么多线程,更甭说这么多进程了。所以,这儿规则了个最长dump时刻为20秒,超越则及时回来,这样能够确保ANR弹窗能够及时弹出(或许被kill掉)。接下来咱们接着跟进dumpJavaTracesTombstoned
。经过一连串的逻辑:ActivityManagerService#dumpJavaTracesTombstoned() → Debug#dumpJavaBacktraceToFileTimeout() → android_os_Debug#android_os_Debug_dumpJavaBacktraceToFileTimeout() → android_os_Debug#dumpTraces() → debuggerd_client#dump_backtrace_to_file_timeout() → debuggerd_client#debuggerd_trigger_dump()。
bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) {
//pid是从AMS那儿传过来的,即需求dump库房的进程
pid_t pid = tid;
//......
// Send the signal.
//从android_os_Debug_dumpJavaBacktraceToFileTimeout过来的,dump_type为kDebuggerdJavaBacktrace
const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
//sigqueue:在行列中向指定进程发送一个信号和数据,成功回来0
if (sigqueue(pid, signal, val) != 0) {
log_error(output_fd, errno, "failed to send signal to pid %d", pid);
return false;
}
//......
LOG(INFO) << TAG "done dumping process " << pid;
return true;
}
留意,这儿适当所以AMS进程间接给需求dump库房那个进程发送了一个SIGQUIT信号,那个进程收到SIGQUIT信号之后便开端dump。这儿也便是前面所说的鸿沟。现在看起来是当一个进程产生ANR时,则会收到SIGQUIT信号。假如,咱们能监控到系统发送的SIGQUIT信号,也许就能感知到产生了ANR,到达监控的目的。
关于进程信号的处理,这儿简略提一下:除Zygote进程外,每个进程都会创立一个SignalCatcher守护线程,用于捕获SIGQUIT、SIGUSR1信号,并采纳相应的行为。
//art/runtime/signal_catcher.cc
void* SignalCatcher::Run(void* arg) {
SignalCatcher* signal_catcher = reinterpret_cast<SignalCatcher*>(arg);
CHECK(signal_catcher != nullptr);
Runtime* runtime = Runtime::Current();
//查看当时线程是否依附到Android Runtime
CHECK(runtime->AttachCurrentThread("Signal Catcher", true, runtime->GetSystemThreadGroup(), !runtime->IsAotCompiler()));
Thread* self = Thread::Current();
DCHECK_NE(self->GetState(), kRunnable);
{
MutexLock mu(self, signal_catcher->lock_);
signal_catcher->thread_ = self;
signal_catcher->cond_.Broadcast(self);
}
SignalSet signals;
signals.Add(SIGQUIT); //增加对信号SIGQUIT的处理
signals.Add(SIGUSR1); //增加对信号SIGUSR1的处理
//死循环,不断等候监听2个信号的dao'l
while (true) {
//等候信号到来,这是个堵塞操作
int signal_number = signal_catcher->WaitForSignal(self, signals);
//当信号捕获需求停止时,则取消当时线程跟Android Runtime的关联。
if (signal_catcher->ShouldHalt()) {
runtime->DetachCurrentThread();
return nullptr;
}
switch (signal_number) {
case SIGQUIT:
signal_catcher->HandleSigQuit(); //输出线程trace
break;
case SIGUSR1:
signal_catcher->HandleSigUsr1(); //强制GC
break;
default:
LOG(ERROR) << "Unexpected signal %d" << signal_number;
break;
}
}
}
在SignalCatcher线程里边,死循环,经过WaitForSignal监听SIGQUIT和SIGUSR1信号的到来,前面系统进程system_server
进程发送的SIGQUIT信号也便是在这儿被监听到,然后开端dump库房。
现在,咱们收拾一下整个ANR的流程:
- 系统监控到app产生ANR后,搜集了一些相关进程pid(包括产生ANR的进程),预备让这些进程dump库房,然后生成ANR Trace文件
- 系统开端向这些进程发送SIGQUIT信号,进程收到SIGQUIT信号之后开端dump库房
整个进程的示意图:
图片转自微信客户端技能团队
能够看到,一个进程产生ANR之后的整个流程,只要dump库房的行为会产生在产生ANR的进程中,其他进程全在系统进程进行处理的,咱们无法感知。这个进程从收到SIGQUIT信号开端到运用socket写Trace完毕。然后持续回到系统进程完结剩下的ANR流程,这2个鸿沟上咱们能够做做文章。后边咱们会具体叙述。
6. ANR监控
Android M(6.0) 版本之后,运用侧无法直接经过监听 data/anr/trace
文件,监控是否产生 ANR。目前了解到的能用的计划主要有下面2种:
6.1 WatchDog
开个子线程,不断往主线程发送音讯,并设置超时检测,假如超时还没履行相应音讯,则断定为或许产生ANR。需求进一步从系统服务获取相关数据(可经过ActivityManagerService.getProcessesInErrorState()办法获取进程的ANR信息),进一步断定是否真的产生了ANR。
这个计划对应的开源库为ANR-WatchDog,源码比较简略,只要2个源文件。简略解析一下中心代码:
private final Handler _uiHandler = new Handler(Looper.getMainLooper());
private final int _timeoutInterval;
private volatile long _tick = 0;
private volatile boolean _reported = false;
private final Runnable _ticker = new Runnable() {
@Override public void run() {
_tick = 0;
_reported = false;
}
};
@Override
public void run() {
setName("|ANR-WatchDog|");
//_timeoutInterval为设定的超时时长
long interval = _timeoutInterval;
while (!isInterrupted()) {
//_tick为标志,主线程履行了下面发送的_ticker这个Runnable, 那么_tick就会被置为0
boolean needPost = _tick == 0;
//在子线程里边需求把标志改为非0,待会儿主线程履行了才知道
_tick += interval;
if (needPost) {
//发个音讯给主线程
_uiHandler.post(_ticker);
}
//子线程睡一段时刻,起来的时分要是标志位_tick没有被改成0,阐明主线程太忙了,或许卡顿了,没来得及履行该音讯
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// If the main thread has not handled _ticker, it is blocked. ANR.
if (_tick != 0 && !_reported) {
//noinspection ConstantConditions
//扫除debug的状况
if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
_reported = true;
continue ;
}
//能够自界说一个Interceptor告诉watchDog,当时上下文环境是否能够进行上报
interval = _anrInterceptor.intercept(_tick);
if (interval > 0) {
continue;
}
//上报线程库房
final ANRError error;
if (_namePrefix != null) {
error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
} else {
error = ANRError.NewMainOnly(_tick);
}
//回调
_anrListener.onAppNotResponding(error);
interval = _timeoutInterval;
_reported = true;
}
}
}
中心代码十分简洁,基本上便是上面计划的完成了。有一点需求补偿的是,需求进一步从系统服务获取相关数据(可经过ActivityManagerService.getProcessesInErrorState()办法获取进程的ANR信息,具体完成方法下面会具体阐明),进一步断定是否真的产生了ANR。能够自界说一个_anrInterceptor
,在里边完成这些内容。
6.2 监控SIGQUIT信号
这种计划才是实在的监控ANR,matrix、xCrash都在运用这种计划。现已在国民运用微信等app上查验过,稳定性和可靠性都能得到确保。
在文章上面的ANR流程分析中,咱们找到了系统与产生ANR进程之间的鸿沟(即下图中的1和2)。咱们能否监听到系统发送给咱们的SIGQUIT信号呢?答案当然是可行的。
这儿需求一点预备常识,首要咱们得知道什么是SIGQUIT信号,前面咱们说到了除Zygote进程以外的其他进程都有个Signal Catcher线程在不断地经过sigwait监听SIGQUIT信号,当收到SIGQUIT信号时开端dump线程库房。咱们需求阻拦或许监听SIGQUIT信号,首要需求了解信号处理的相关函数,如kill、signal、sigaction、sigwait、pthread_sigmask
等,本文就不具体展开这些函数的具体运用了,如需具体了解,推荐阅读《UNIX环境高档编程》。
下面是我写的监控SIGQUIT信号demo的中心代码,完整源码在这儿:
void signalHandler(int sig, siginfo_t *info, void *uc) {
__android_log_print(ANDROID_LOG_DEBUG, "xfhy_anr", "我监听到SIGQUIT信号了,或许产生anr了");
//在这儿去dump主线程库房
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_xfhy_watchsignaldemo_MainActivity_startWatch(JNIEnv *env, jobject thiz) {
sigset_t set, old_set;
sigemptyset(&set);
sigaddset(&set, SIGQUIT);
/*
* 这儿需求调用SIG_UNBLOCK,由于方针进程被Zogyte fork出来的时分,主线程继承了
* Zogyte的主线程的信号屏蔽关系,Zogyte主线程在初始化的时分,经过
* pthread_sigmask SIG_BLOCK把SIGQUIT的信号给屏蔽了,因此咱们需求在自己进程的主线程,
* 设置pthread_sigmask SIG_UNBLOCK ,这会导致本来的SignalCatcher sigwait将失效,
* 原因是SignalCatcher 线程会对SIGQUIT 信号处理
*/
int r = pthread_sigmask(SIG_UNBLOCK, &set, &old_set);
if (0 != r) {
return false;
}
struct sigaction sa{};
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
return sigaction(SIGQUIT, &sa, nullptr) == 0;
}
Android默认把SIGQUIT设置成了BLOCKED,所以只会呼应Signal Catcher线程的sigwait监听SIGQUIT信号,咱们用sigaction监听的则收不到,所以这儿还需求处理一下。咱们经过pthread_sigmask或许sigprocmask把SIGQUIT设置为UNBLOCK,那么再次收到SIGQUIT时,就必定会进入到咱们的signalHandler办法中。
除了上面这个之外,还需求留意的是:咱们用sigaction抢了Signal Catcher线程的SIGQUIT信号,那Signal Catcher线程就收不到该信号了,那本来的系统dump库房的流程就没了,这是不太合适的。所以咱们需求将该信号从头发送出去,让Signal Catcher线程接收到该信号。
int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid
tgkill(getpid(), tid, SIGQUIT);
以上,咱们得到了一个不改变系统行为的前提下,比较完善的监控SIGQUIT信号的机制,尽管不是特别完美,但这是监控ANR的基础。接下来咱们渐渐完善。
6.2.1 完善的ANR监控计划
监控到SIGQUIT信号并不等于就监控到了ANR。
6.2.1.1 误报
产生ANR的进程必定会收到SIGQUIT信号;可是收到SIGQUIT信号的进程并不必定产生了ANR。
或许是下面2种状况:
- 其他进程的ANR:产生ANR之后,产生ANR的进程并不是唯一需求dump库房的进程,系统会搜集许多其他的进程进行dump,也便是说当一个运用产生ANR的时分,其他的运用也有或许收到SIGQUIT信号。所以,咱们收到SIGQUIT信号,或许是其他进程产生了ANR,这个时分上报的话就属所以误报了。
- 非ANR发送SIGQUIT:发送SIGQUIT信号十分简单,系统和运用级app都能轻易发送SIGQUIT信号:java层调用android.os.Process.sendSignal办法;Native层调用kill或许tgkill办法。咱们收到SIGQUIT信号时,或许并非是ANR流程发送的SIGQUIT信号,也会产生误报。
怎么处理上面2个误报的问题?回到ANR流程开端的地方细看
//com.android.server.am.ProcessRecord.java
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
//......
synchronized (mService) {
//留意,假如是后台ANR,直接就kill进程然后return了,并不会走到下面的makeAppNotRespondingLocked,当时进程也不会有NOT_RESPONDING这个flag
if (isSilentAnr() && !isDebugging()) {
kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
return;
}
// Set the app's notResponding state, and look up the errorReportReceiver
makeAppNotRespondingLocked(activityShortComponentName,
annotation != null ? "ANR " + annotation : "ANR", info.toString());
// show ANR dialog ......
}
}
private void makeAppNotRespondingLocked(String activity, String shortMsg, String longMsg) {
setNotResponding(true);
// mAppErrors can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mAppErrors != null) {
notRespondingReport = mService.mAppErrors.generateProcessError(this,
ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
activity, shortMsg, longMsg, null);
}
startAppProblemLocked();
getWindowProcessController().stopFreezingActivities();
}
void setNotResponding(boolean notResponding) {
mNotResponding = notResponding;
mWindowProcessController.setNotResponding(notResponding);
}
在ANR弹窗前,会履行makeAppNotRespondingLocked办法,在这儿会给产生ANR的进程符号一个NOT_RESPONDING
的flag,这个flag能够经过ActivityManager来获取:
private static boolean checkErrorState() {
try {
Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
if (procs == null) return false;
for (ActivityManager.ProcessErrorStateInfo proc : procs) {
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
return true;
}
return false;
} catch (Throwable t){
MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
}
return false;
}
监控到SIGQUIT后,咱们在20秒内(20秒是ANR dump的timeout时刻)不断轮询自己是否有NOT_RESPONDING
的flag,一旦发现有这个flag,那么马上就能够确定产生了一次ANR。
ps: 你或许会想,有这么便利的办法,监控SIGQUIT信号不是剩余么?我直接搞个死循环,不断监听该flag,一旦发现不就监控到ANR了么?能够是能够,但不高雅,而且有缺点(低效、耗电、不环保、无法处理下面说到的漏报问题)。
6.2.1.2 漏报
进程处于NOT_RESPONDING
的状况能够承认该进程产生了ANR。可是产生ANR的进程并不必定会被设置为NOT_RESPONDING
状况
下面2种是特殊状况:
- 后台ANR(SilentAnr):假如ANR被符号为了后台ANR(即SilentAnr),那么杀死进程后就会直接return,不会履行到makeAppNotRespondingLocked,那么该进程就不会有
NOT_RESPONDING
这个flag。这意味着,后台的ANR没办法捕捉到,但后台ANR的量也挺大的,而且后台ANR会直接杀死进程,对用户的体验也是十分负面的,这么大一部分ANR监控不到,当然是无法接受的。 - 闪退ANR:想当一部分机型(如OPPO、VIVO两家的高Android版本的机型)修改了ANR的流程,即便是产生在前台的ANR,也并不会弹窗,而是直接杀死进程,即闪退。
基于上面2种状况,咱们需求一种机制,在收到SIGQUIT信号后,需求十分快速的侦办出自己是否现已处于ANR的状况,进行快速的dump和上报。此刻咱们能够经过主线程开释处于卡顿状况来判别,怎样快速的知道主线程是否卡住了?能够经过Looper的mMessage目标,该目标的when变量,表明的是当时正在处理的音讯入队的时刻,咱们能够经过when变量减去当时时刻,得到的便是等候时刻,假如等候时刻过长,就阐明主线程是处于卡住的状况。这时分收到SIGQUIT信号基本上就能够以为确实产生了一次ANR:
private static boolean isMainThreadStuck(){
try {
MessageQueue mainQueue = Looper.getMainLooper().getQueue();
Field field = mainQueue.getClass().getDeclaredField("mMessages");
field.setAccessible(true);
final Message mMessage = (Message) field.get(mainQueue);
if (mMessage != null) {
long when = mMessage.getWhen();
if(when == 0) {
return false;
}
long time = when - SystemClock.uptimeMillis();
long timeThreshold = BACKGROUND_MSG_THRESHOLD;
if (foreground) {
timeThreshold = FOREGROUND_MSG_THRESHOLD;
}
return time < timeThreshold;
}
} catch (Exception e){
return false;
}
return false;
}
经过上面几种机制来综合判别收到SIGQUIT信号后,是否真的产生了一次ANR,最大程度地减少误报和漏报。
6.2.1.3 获取ANR Trace
回到上面的ANR流程示意图,Signal Catcher线程写Trace也是一个鸿沟,它是经过socket的write办法来写trace的。那咱们能够直接hook这儿的write,就能直接拿到系统dump的ANR Trace内容。这个内容十分全面,包括了一切线程的各种状况、锁和库房(包括native库房),关于咱们排查问题十分有用,尤其是一些native问题和死锁等问题。native hook采用PLT Hook计划,稳得很,这种计划现已在微信上验证了其稳定性。
int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);
int my_connect(int __fd, const struct sockaddr* __addr, socklen_t __addr_length) {
if (strcmp(__addr->sa_data, "/dev/socket/tombstoned_java_trace") == 0) {
isTraceWrite = true;
signalCatcherTid = gettid();
}
return original_connect(__fd, __addr, __addr_length);
}
int (*original_open)(const char *pathname, int flags, mode_t mode);
int my_open(const char *pathname, int flags, mode_t mode) {
if (strcmp(pathname, "/data/anr/traces.txt") == 0) {
isTraceWrite = true;
signalCatcherTid = gettid();
}
return original_open(pathname, flags, mode);
}
ssize_t (*original_write)(int fd, const void* const __pass_object_size0 buf, size_t count);
ssize_t my_write(int fd, const void* const buf, size_t count) {
if(isTraceWrite && signalCatcherTid == gettid()) {
isTraceWrite = false;
signalCatcherTid = 0;
char *content = (char *) buf;
printAnrTrace(content);
}
return original_write(fd, buf, count);
}
void hookAnrTraceWrite() {
int apiLevel = getApiLevel();
if (apiLevel < 19) {
return;
}
if (apiLevel >= 27) {
plt_hook("libcutils.so", "connect", (void *) my_connect, (void **) (&original_connect));
} else {
plt_hook("libart.so", "open", (void *) my_open, (void **) (&original_open));
}
if (apiLevel >= 30 || apiLevel == 25 || apiLevel ==24) {
plt_hook("libc.so", "write", (void *) my_write, (void **) (&original_write));
} else if (apiLevel == 29) {
plt_hook("libbase.so", "write", (void *) my_write, (void **) (&original_write));
} else {
plt_hook("libart.so", "write", (void *) my_write, (void **) (&original_write));
}
}
有几点需求留意:
- 只Hook ANR流程:有些状况下,基础库中的connect/open/write办法或许调用的比较频频,咱们需求把hook的影响降到最低。所以咱们只会在接收到SIGQUIT信号后(从头发送SIGQUIT信号给Signal Catcher前)进行hook,ANR流程完毕后再unhook。
- 只处理Signal Catcher线程open/connect后的第一次write:除了Signal Catcher线程中的dump trace的流程,其他地方调用的write办法咱们并不关心,并不需求处理。
- Hook点因API Level而不同:需求hook的write办法在不同的Android版本中,地点so库也不同,需别离处理。
到此,matrix监控SIGQUIT信号然后监控ANR的计划的中心逻辑已悉数出现,更多具体源码请移步matrix库房。
总结一下,该计划经曩昔监听SIGQUIT信号,然后感知当时进程或许产生了ANR,需合作当时进程是否处于NOT_RESPONDING
状况以及主线程是否卡顿来进行甄别,以免误判。注册监听SIGQUIT信号之后,系统本来的Signal Catcher线程就监听不到这个信号了,需求把该信号转发出去,让它接收到,以免影响。当时进程的Signal Catcher线程要dump库房的时分,会经过socket的write向system server进程进行传输dump好的数据,咱们能够hook这个write,然后拿到系统dump好的ANR Trace内容,适当于咱们并没有影响系统的任何流程,还拿到了想要拿到的东西。这个计划彻底是在系统的正常dump anr trace的进程中获取信息,所以能拿到的东西愈加全面,可是系统的dump进程其实是对功能影响比较大的,时刻也比较久。
7. ANR分析
监控当然重要,更重要的是分析是什么原因导致的ANR,然后修复好。
7.1 trace文件分析
拿到trace文件,具体分析下:
----- pid 7761 at 2022-11-02 07:02:26 -----
Cmd line: com.xfhy.watchsignaldemo
Build fingerprint: 'HUAWEI/LYA-AL00/HWLYA:10/HUAWEILYA-AL00/10.1.0.163C00:user/release-keys'
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=11918 post zygote classes=729
Dumping registered class loaders
#0 dalvik.system.PathClassLoader: [], parent #1
#1 java.lang.BootClassLoader: [], no parent
#2 dalvik.system.PathClassLoader: [/system/app/FeatureFramework/FeatureFramework.apk], no parent
#3 dalvik.system.PathClassLoader: [/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes2.dex:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes4.dex:/data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/base.apk!classes3.dex], parent #1
Done dumping class loaders
Intern table: 44132 strong; 436 weak
JNI: CheckJNI is off; globals=681 (plus 67 weak)
Libraries: /data/app/com.xfhy.watchsignaldemo-4tkKMWojrpHAf-Q3iecaHQ==/lib/arm64/libwatchsignaldemo.so libandroid.so libcompiler_rt.so libhitrace_jni.so libhiview_jni.so libhwapsimpl_jni.so libiAwareSdk_jni.so libimonitor_jni.so libjavacore.so libjavacrypto.so libjnigraphics.so libmedia_jni.so libopenjdk.so libsoundpool.so libwebviewchromium_loader.so (15)
//已分配堆内存巨细26M,其间2442kb医用,总分配74512个目标
Heap: 90% free, 2442KB/26MB; 74512 objects
Total number of allocations 120222 //进程创立到现在总共创立了多少目标
Total bytes allocated 10MB //进程创立到现在总共申请了多少内存
Total bytes freed 8173KB //进程创立到现在总共开释了多少内存
Free memory 23MB //不扩展堆的状况下可用的内存
Free memory until GC 23MB //GC前的可用内存
Free memory until OOME 381MB //OOM之前的可用内存,这个值很小的话,阐明现已处于内存严峻状况,app或许是占用了过多的内存
Total memory 26MB //当时总内存(已用+可用)
Max memory 384MB //进程最多能申请的内存
.....//省略GC相关信息
//当时进程共17个线程
DALVIK THREADS (17):
//Signal Catcher线程调用栈
"Signal Catcher" daemon prio=5 tid=4 Runnable
| group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
| sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
| state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
| stack=0x7253454000-0x7253456000 stackSize=991KB
| held mutexes= "mutator lock"(shared held)
native: #00 pc 000000000042f8e8 /apex/com.android.runtime/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+140)
native: #01 pc 0000000000523590 /apex/com.android.runtime/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+508)
native: #02 pc 000000000053e75c /apex/com.android.runtime/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+844)
native: #03 pc 000000000053735c /apex/com.android.runtime/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*, art::Closure*)+504)
native: #04 pc 0000000000536744 /apex/com.android.runtime/lib64/libart.so (art::ThreadList::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool)+1048)
native: #05 pc 0000000000536228 /apex/com.android.runtime/lib64/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+884)
native: #06 pc 00000000004ee4d8 /apex/com.android.runtime/lib64/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+196)
native: #07 pc 000000000050250c /apex/com.android.runtime/lib64/libart.so (art::SignalCatcher::HandleSigQuit()+1356)
native: #08 pc 0000000000501558 /apex/com.android.runtime/lib64/libart.so (art::SignalCatcher::Run(void*)+268)
native: #09 pc 00000000000cf7c0 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
native: #10 pc 00000000000721a8 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)
(no managed stack frames)
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
| sysTid=7761 nice=-10 cgrp=default sched=1073741825/2 handle=0x72e60080d0
| state=S schedstat=( 281909898 5919799 311 ) utm=20 stm=7 core=4 HZ=100
| stack=0x7fca180000-0x7fca182000 stackSize=8192KB
| held mutexes=
at java.lang.Thread.sleep(Native method)
- sleeping on <0x00f895d9> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:443)
- locked <0x00f895d9> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:359)
at android.os.SystemClock.sleep(SystemClock.java:131)
at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:35)
at java.lang.reflect.Method.invoke(Native method)
at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
at android.view.View.performClick(View.java:7317)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
at android.view.View.performClickInternal(View.java:7291)
at android.view.View.access$3600(View.java:838)
at android.view.View$PerformClick.run(View.java:28247)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
... //此处省略剩下的N个线程
trace参数具体解读:
"Signal Catcher" daemon prio=5 tid=4 Runnable
| group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
| sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
| state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
| stack=0x7253454000-0x7253456000 stackSize=991KB
| held mutexes= "mutator lock"(shared held)
第1行:
"Signal Catcher" daemon prio=5 tid=4 Runnable
- “Signal Catcher” daemon : 线程名,有daemon表明守护线程
- prio:线程优先级
- tid:线程内部id
- 线程状况:Runnable
ps: 一般来说:main线程处于BLOCK、WAITING、TIMEWAITING状况,基本上是函数堵塞导致的ANR,假如main线程无异常,则应该排查CPU负载和内存环境。
第2行:
| group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
- group:线程所属的线程组
- sCount:线程挂起次数
- dsCount:用于调试的线程挂起次数
- obj:当时线程关联的Java线程目标
- self:当时线程地址
第3行:
| sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
- sysTid:线程实在含义上的tid
- nice:调度优先级,值越小则优先级越高
- cgrp:进程所属的进程调度组
- sched:调度战略
- handle:函数处理地址
第4行:
| state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
- state:线程状况
- schedstat:CPU调度时刻核算(schedstat括号中的3个数字依次是Running、Runable、Switch,Running时刻:CPU运转的时刻,单位ns,Runable时刻:RQ行列的等候时刻,单位ns,Switch次数:CPU调度切换次数)
- utm/stm:用户态/内核态的CPU时刻
- core:该线程的最终运转地点核
- HZ:时钟频率
第5行:
| stack=0x7253454000-0x7253456000 stackSize=991KB
- stack:线程栈的地址区间
- stackSize:栈的巨细
第6行:
| held mutexes= "mutator lock"(shared held)
- mutex:所持有mutex类型,有独占锁exclusive和同享锁shared两类
7.2 ANR案例分析
7.2.1 主线程无卡顿,处于正常状况库房
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74b38080 self=0x7ad9014c00
| sysTid=23081 nice=0 cgrp=default sched=0/0 handle=0x7b5fdc5548
| state=S schedstat=( 284838633 166738594 505 ) utm=21 stm=7 core=1 HZ=100
| stack=0x7fc95da000-0x7fc95dc000 stackSize=8MB
| held mutexes=
kernel: __switch_to+0xb0/0xbc
kernel: SyS_epoll_wait+0x288/0x364
kernel: SyS_epoll_pwait+0xb0/0x124
kernel: cpu_switch_to+0x38c/0x2258
native: #00 pc 000000000007cd8c /system/lib64/libc.so (__epoll_pwait+8)
native: #01 pc 0000000000014d48 /system/lib64/libutils.so (android::Looper::pollInner(int)+148)
native: #02 pc 0000000000014c18 /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
native: #03 pc 00000000001275f4 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:330)
at android.os.Looper.loop(Looper.java:169)
at android.app.ActivityThread.main(ActivityThread.java:7073)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:536)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
比方这个主线程库房,看起来很正常,主线程是空闲的,由于它正处于nativePollOnce,正在等候新音讯。处于这个状况,那还产生了ANR,或许有2个原因:
- dump库房机遇太晚了,ANR现已产生过了,才去dump库房,此刻主线程现已康复正常了
- CPU抢占或许内存严峻等其他要素引起
遇到这种状况,要先去分析CPU、内存的运用状况。其次能够关注抓取日志的时刻和ANR产生的时刻是否相隔太久,时刻太久这个库房就没有分析的含义了。
7.2.2 主线程履行耗时操作
//模仿主线程耗时操作,View点击的时分调用这个函数
fun makeAnr(view: View) {
var s = 0L
for (i in 0..99999999999) {
s += i
}
Log.d("xxx", "s=$s")
}
当主线程履行到makeAnr时,会由于里边的东西履行太耗时而一向在这儿进行核算,假设此刻有其他事情要想交给主线程处理,则必须得等到makeAnr函数履行完才行。主线程在履行makeAnr时,输入事情无法被处理,用户屡次点击屏幕之后,就会输入超时,触发InputEvent Timeout,导致ANR。而假如主线程在履行上面这段耗时操作的进程中,没有其他事情需求处理,那其实是不会产生ANR的。
suspend all histogram: Sum: 206us 99% C.I. 0.098us-46us Avg: 7.629us Max: 46us
DALVIK THREADS (16):
"main" prio=5 tid=1 Runnable
| group="main" sCount=0 dsCount=0 flags=0 obj=0x73907540 self=0x725f010800
| sysTid=32298 nice=-10 cgrp=default sched=1073741825/2 handle=0x72e60080d0
| state=R schedstat=( 6746757297 5887495 256 ) utm=670 stm=4 core=6 HZ=100
| stack=0x7fca180000-0x7fca182000 stackSize=8192KB
| held mutexes= "mutator lock"(shared held)
at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:58)
at java.lang.reflect.Method.invoke(Native method)
at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
at android.view.View.performClick(View.java:7317)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
at android.view.View.performClickInternal(View.java:7291)
at android.view.View.access$3600(View.java:838)
at android.view.View$PerformClick.run(View.java:28247)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
从日志上看,主线程处于履行状况,不是空闲状况,导致ANR了,阐明com.xfhy.watchsignaldemo.MainActivity.makeAnr
这儿有耗时操作。
7.2.3 主线程被锁堵塞
模仿主线程等候子线程的锁:
fun makeAnr(view: View) {
val obj1 = Any()
val obj2 = Any()
//搞个死锁,彼此等候
thread(name = "卧槽") {
synchronized(obj1) {
SystemClock.sleep(100)
synchronized(obj2) {
}
}
}
synchronized(obj2) {
SystemClock.sleep(100)
synchronized(obj1) {
}
}
}
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
| sysTid=19900 nice=-10 cgrp=default sched=0/0 handle=0x72e60080d0
| state=S schedstat=( 542745832 9516666 182 ) utm=48 stm=5 core=4 HZ=100
| stack=0x7fca180000-0x7fca182000 stackSize=8192KB
| held mutexes=
at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:59)
- waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22 //注释1
- locked <0x01abeb23> (a java.lang.Object)
at java.lang.reflect.Method.invoke(Native method)
at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
at android.view.View.performClick(View.java:7317)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
at android.view.View.performClickInternal(View.java:7291)
at android.view.View.access$3600(View.java:838)
at android.view.View$PerformClick.run(View.java:28247)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
"卧槽" prio=5 tid=22 Blocked //注释2
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12c8a118 self=0x71d625f800
| sysTid=20611 nice=0 cgrp=default sched=0/0 handle=0x71d4513d50
| state=S schedstat=( 486459 0 3 ) utm=0 stm=0 core=4 HZ=100
| stack=0x71d4411000-0x71d4413000 stackSize=1039KB
| held mutexes=
at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:52)
- waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
- locked <0x0c6f8c52> (a java.lang.Object)
at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:49)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
......
留意看,下面几行:
"main" prio=5 tid=1 Blocked
- waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22
- locked <0x01abeb23> (a java.lang.Object)
"卧槽" prio=5 tid=22 Blocked
- waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
- locked <0x0c6f8c52> (a java.lang.Object)
主线程的tid是1,线程状况是Blocked,正在等候0x0c6f8c52
这个Object,而这个Object被thread 22这个线程所持有,主线程当时持有的是0x01abeb23
的锁。而卧槽
的tid是22,也是Blocked状况,它想请求的和已有的锁刚好与主线程相反。这样的话,ANR原因也就找到了:线程22持有了一把锁,而且一向不开释,主线程等候这把锁产生超时。在线上环境,常见因锁而ANR的场景是SharePreference写入。
7.2.4 CPU被抢占
CPU usage from 0ms to 10625ms later (2020-03-09 14:38:31.633 to 2020-03-09 14:38:42.257):
543% 2045/com.test.demo: 54% user + 89% kernel / faults: 4608 minor 1 major //留意看这儿
99% 674/android.hardware.camera.provider@2.4-service: 81% user + 18% kernel / faults: 403 minor
24% 32589/com.wang.test: 22% user + 1.4% kernel / faults: 7432 minor 1 major
......
能够看到,该进程占据CPU高达543%,抢占了大部分CPU资源,由于导致产生ANR,这种ANR与咱们的app无关。
7.2.5 内存严峻导致ANR
假如一份ANR日志的CPU和库房都很正常,能够考虑是内存严峻。看一下ANR日志里边的内存相关部分。还能够去日志里边搜一下onTrimMemory,假如dump ANR日志的时刻附近有相关日志,或许是内存比较严峻了。
10-31 22:37:19.749 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:37:33.458 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:38:00.153 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:38:58.731 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:39:02.816 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
7.2.6 系统服务超时导致ANR
系统服务超时一般会包括BinderProxy.transactNative要害字,来看一段日志:
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x727851e8 self=0x78d7060e00
| sysTid=4894 nice=0 cgrp=default sched=0/0 handle=0x795cc1e9a8
| state=S schedstat=( 8292806752 1621087524 7167 ) utm=707 stm=122 core=5 HZ=100
| stack=0x7febb64000-0x7febb66000 stackSize=8MB
| held mutexes=
kernel: __switch_to+0x90/0xc4
kernel: binder_thread_read+0xbd8/0x144c
kernel: binder_ioctl_write_read.constprop.58+0x20c/0x348
kernel: binder_ioctl+0x5d4/0x88c
kernel: do_vfs_ioctl+0xb8/0xb1c
kernel: SyS_ioctl+0x84/0x98
kernel: cpu_switch_to+0x34c/0x22c0
native: #00 pc 000000000007a2ac /system/lib64/libc.so (__ioctl+4)
native: #01 pc 00000000000276ec /system/lib64/libc.so (ioctl+132)
native: #02 pc 00000000000557d4 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+252)
native: #03 pc 0000000000056494 /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+60)
native: #04 pc 00000000000562d0 /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+216)
native: #05 pc 000000000004ce1c /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+72)
native: #06 pc 00000000001281c8 /system/lib64/libandroid_runtime.so (???)
native: #07 pc 0000000000947ed4 /system/framework/arm64/boot-framework.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+196)
at android.os.BinderProxy.transactNative(Native method) ————————————————要害行!!!
at android.os.BinderProxy.transact(Binder.java:804)
at android.net.IConnectivityManager$Stub$Proxy.getActiveNetworkInfo(IConnectivityManager.java:1204)—要害行!
at android.net.ConnectivityManager.getActiveNetworkInfo(ConnectivityManager.java:800)
at com.xiaomi.NetworkUtils.getNetworkInfo(NetworkUtils.java:2)
at com.xiaomi.frameworkbase.utils.NetworkUtils.getNetWorkType(NetworkUtils.java:1)
at com.xiaomi.frameworkbase.utils.NetworkUtils.isWifiConnected(NetworkUtils.java:1)
从日志库房中能够看到是获取网络信息产生了ANR:getActiveNetworkInfo。系统的服务都是Binder机制(16个线程),服务才能也是有限的,有或许系统服务长期不呼应导致ANR。假如其他运用占用了一切Binder线程,那么当时运用只能等候。可进一步搜索:blockUntilThreadAvailable要害字:
at android.os.Binder.blockUntilThreadAvailable(Native method)
假如有发现某个线程的库房,包括此字样,可进一步看其库房,确定是调用了什么系统服务。此类ANR也是属于系统环境的问题,假如某类型手机上频频产生此问题,运用层能够考虑躲避战略。
8. ANR影响要素
即便咱们使用上面的一系列骚操作,在产生ANR时,咱们拿到了Trace库房。但实际状况下这些Trace库房中,有许多不是导致ANR的根本原因。Trace库房提示某个Service或Receiver导致的ANR,但其实很或许并不是这些组件自身的问题导致的ANR,至于为什么,下面一一道来。
影响ANR的实质要素大体来说分为2个:运用内部环境和系统环境。当系统负载正常,可是运用内部主线程音讯过多或耗时验证;别的一类是系统或运用内部其他线程或资源负载过高,主线程调度被严峻抢占。
系统负载高咱们没有办法,但系统负载正常时,主线程的调度问题主要有下面几个:
- 当时Trace库房地点事务耗时严峻
- 当时Trace库房地点事务耗时并不严峻,但前史调度有一个严峻耗时
- 当时Trace库房地点事务耗时并不严峻,但前史调度有多个音讯耗时
- 当时Trace库房地点事务耗时并不严峻,可是前史调度存在巨量重复音讯(事务频频发送音讯)
- 当时Trace库房事务逻辑并不耗时,可是其他线程存在严峻资源抢占,如IO、Mem、CPU;
- 当时Trace库房事务逻辑并不耗时,可是其他进程存在严峻资源抢占,如IO、Mem、CPU。
请留意,这儿的6个影响要素中,除了第一个以外,其他的依据ANR Trace有或许无法进行判别。这就会导致许多时分看到的ANR Trace里边主线程库房对应的事务其实并不耗时(由于或许是前面的音讯导致的耗时,但它现已履行完了),怎么处理这个问题?
9. 补偿不足
字节跳动内部有一个监控东西:Raster,这个库专门处理上面的问题。有一点可惜的是该东西暂时还没开源,可是咱们从字节发出来的Raster原理相关的文章能了解到该库的具体原理。原文 : 今天头条 ANR 优化实践系列 – 监控东西与分析思路
Raster的大致原理:该东西主要是在主线程音讯调度进程进行监控,并依照必定的战略聚合,以确保监控东西本身对运用功能和内存颤动影响降至最低。比较耗时的音讯会抓取主线从库房,这样能够知道那个耗时的音讯具体是在干什么,然后针对性优化。一起对运用四大组件音讯履行进程进行监控,便于对这类音讯的调度及耗时状况进行跟踪和记载。别的对当时正在调度的音讯及音讯行列中待调度音讯进行核算,然后在产生问题时,能够回放主线程的整体调度状况。此外,该库将系统服务的CheckTime机制迁移到运用侧,运用为线程CheckTime机制,以便于系统信息不足时,从线程调度及时性推测曩昔一段时刻系统负载和调度状况。因此该东西用一句话来归纳便是:由点到面,回放曩昔,现在和将来。
细说一下线程 Checktime:经过凭借其他子线程的周期检测机制,在每次调度前获取当时系统时刻,然后减去咱们设置推迟的时刻,即可得到本次线程调度前的实在距离时刻,如设置线程每隔300ms调度一次,结果发现实际呼应时刻距离有时会超越300ms,假如误差越大则阐明线程没有及时调度,进一步反映系统呼应才能变差。经过这样的方法,即便线上环境获取不到系统日志,也能够从侧面反映不一起段系统负载对线程调度影响。当连续产生屡次严峻Delay时,阐明线程调度受到了影响。
经过上诉监控才能,咱们就能够清晰的知道ANR产生时主线程前史音讯调度以及耗时严峻音讯的采样库房,一起能够知道正在履行音讯的耗时,以及音讯行列中调度音讯的状况。一起经过线程CheckTime机制从侧面反映线程调度呼应才能,由此完结了运用侧监控信息从点到面的掩盖。
有大佬依据该文章的原理完成了一个类似的开源库: MoonlightTreasureBox,MoonlightTreasureBox 开源地址。
10. QA
10.1 在Activity#onCreate中sleep会导致ANR吗?
不会,ANR的场景只要下面4种:Service Timeout、BroadcastQueue Timeout、ContentProvider Timeout、InputDispatching Timeout。
当然,假如在Activity#onCreate中sleep的进程中,用户点击了屏幕,那是有或许触发InputDispatching Timeout的。
11. 小结
很侥幸地祝贺你,读完了整篇文章。
ANR是陈词滥调的问题了,本文从界说、原因、产生场景、触发流程、监控与分析等多方面下手,极力补全ANR这块的常识。
ANR的产生场景只要4种:Service Timeout、BroadcastQueue Timeout、ContentProvider Timeout、InputDispatching Timeout,但导致ANR的原因是多种多样的,或许是App这边导致的,也或许是系统那儿导致的。触发ANR的进程大致又能够分为2种,一种是Service、Broadcast、Provider触发ANR:埋炸弹、拆炸弹、引爆破弹,别的一种是Input触发ANR:处理后续时检测之前的。触发ANR之后,会走dump ANR Trace的流程,搜集相关进程的库房信息写入文件。咱们能够监听SIGQUIT信号,感知到系统在走dump ANR Trace的流程,咱们能够进一步承认一下当时进程是否处于ANR的状况,然后经过hook系统与App的鸿沟,然后经过socket拿到系统dump好的ANR Trace内容。拿到ANR Trace内容之后,当然便是分析了,具体请看文章。可是有时分,拿到的ANR Trace并不能把实在的ANR原因给分析出来,这时就得上字节内部的大杀器了:Raster,尽管暂时还没开源,但字节已将其原理如数家珍的共享出来了。Raster主要是能知道主线程的音讯调度在曩昔、现在、将来的具体状况,合作线程 CheckTime 感知线程调度才能,要比单单分析 ANR Trace要便利许多。
12. 资料
感谢以下一切大佬的精彩文章。
- 卡顿、ANR、死锁,线上怎么监控? /post/697356…
- 你管这破玩意叫 IO 多路复用?mp.weixin.qq.com/s?__biz=Mzk…
- epoll或许kqueue的原理是什么? www.zhihu.com/question/20…
- Gityuan了解Android ANR的信息搜集进程 gityuan.com/2016/12/02/…
- Gityuan 了解Android ANR的触发原理 gityuan.com/2016/07/02/…
- Gityuan Input系统—ANR原理分析 gityuan.com/2017/01/01/…
- Gityuan 彻底了解安卓运用无呼应机制 gityuan.com/2019/04/06/…
- Gityuan Input系统—事情处理全进程 gityuan.com/2016/12/31/…
- 微信Android客户端的卡顿监控计划 mp.weixin.qq.com/s/3dubi2GVW…
- Touch事情怎么传递到Activity www.jianshu.com/p/7d442ed0a…
- 浅析 Android 输入事情处理(一) zhuanlan.zhihu.com/p/26893970
- 【Android】事情处理系统 www.cnblogs.com/lcw/p/33732…
- Android 输入系统 & ANR机制的规划与完成 mp.weixin.qq.com/s/OyyP_BQqz…
- Android PLT hook 概述 github.com/iqiyi/xHook…
- Android 输入系统 & ANR机制的规划与完成 mp.weixin.qq.com/s/OyyP_BQqz…
- 今天头条 ANR 优化实践系列 – 规划原理及影响要素 mp.weixin.qq.com/s/ApNSEWxQd…
- 今天头条 ANR 优化实践系列 – 监控东西与分析思路 mp.weixin.qq.com/s/_Z6GdGRVW…
- Matrix – ANR 原了解析 www.dalvik.work/2021/12/03/…
- 西瓜视频稳定性管理系统建造三:Sliver 原理及实践mp.weixin.qq.com/s/LW3eMK9O2… (这篇文章说到,looper音讯分发和监控Signal信号有或许无法监控到实在的ANR,或许dump库房时现已错失实在的机遇,需求获取到dump库房时的前面的音讯库房,好像matrix有,届时看一下)
- 西瓜卡顿 & ANR 优化管理及监控系统建造 mp.weixin.qq.com/s/2sjG5qkrU…
- 微信Android客户端的ANR监控计划 监控signal信号 blog.csdn.net/stone_cold_…
- 今天头条 ANR 优化实践系列共享 – 实例分析集锦 mp.weixin.qq.com/s/4-_SnG4df…
- 今天头条 ANR 优化实践系列 – Barrier 导致主线程假死 mp.weixin.qq.com/s/OBYWrUBkW…
- 今天头条 ANR 优化实践系列 – 离别 SharedPreference 等候 mp.weixin.qq.com/s/kfF83UmsG…
- 了解杀进程的完成原理 – Gityuan博客 | 袁辉辉的技能博客
- 了解Android进程创立流程 – Gityuan博客 | 袁辉辉的技能博客
- 「ANR」Android SIGQUIT(3) 信号阻拦与处理_阿里巴巴终端技能的博客-CSDN博客
- 干货:ANR日志分析全面解析 zhuanlan.zhihu.com/p/378902923
- Android ANR www.jianshu.com/p/487771a67…