final void logAppTooSlow(ProcessRecord app, long startTime, String msg) {
if (true || IS_USER_BUILD) {
return;
}
……
}
Android traces.txt文件
Android的traces.txt文件记录了进程的堆栈情况,对于分析应用响应慢,以及ANR的原因很有帮助。traces.txt文件的位置位于/data/anr/
。
1. traces.txt文件的生成
traces.txt文件会在三种场景下生成:
-
应用响应慢
-
发生ANR
-
WatchDog的监视没有得到回馈
1.1. logAppTooSlow
对于应用响应慢的情况有:
-
Activity执行
protected void onPause()
方法超时.Android规定的pause响应时间为500毫秒:static final int PAUSE_TIMEOUT = 500;
,这个定义位于frameworks/base/services/java/com/android/server/am/ActivityStack.java
-
Activity启动慢。在ActivityManagerService 启动一个Activity的时候,就会开启定时器,如果500毫秒还没有启动完毕,就会调用logAppTooSlow方法
但是值得注意的是,logAppTooSlow只用于开发版本,在正式的release是不会产生app slow的traces文件的。
因为在logAppTooSlow的方法开头,就直接返回了,所以需要我们修改这个判断条件,让代码能够顺利的执行。由此可见,判断app执行过慢,是开发阶段的事情。这也为在开发阶段解决系统性能提供了一个手段。logAppTooSlow的trace信息保存在slowxx.txt文件中,xx代表编号,从01一直到08,最多有9个这样的文件。
logAppTooSlow的运行逻辑:
-
首先判断
/data/anr/traces.txt
是否存在,如果存在就将其更名为临时文件/data/anr/__temp__
-
然后调用dumpStackTraces往
/data/anr/traces.txt
文件中写入traces信息 -
对/data/anr目录下面已经存在的文件进行移位覆盖。比如原先目录下面有一个文件:slow00.txt,将会把这个文件更名为slow01.txt。如果这个目录下面已经存在9个slow文件,就会把slow08.txt删除,然后其他的文件以此更名,slow07.txt更名为slow08.txt,slow06.txt更名为slow07.txt,以此类推。最后将
/data/anr/traces.txt
更名为slow00.txt。
slow文件的第一行格式如下:
2016-01-01 00:01:10: +6s579ms since launching ActivityRecord{42394f98 u0 packageName/.ComponentName}
首先记录的是slow文件生成的时间;符号+后面的6s579ms表示组件没有响应的时间,在上面的例子中就是有6秒579毫秒没有响应;since后面记录的是原因,在本例中是启动一个Activity没有及时的响应;{}中记录的是组件的名字和地址信息。
1.2. appNotResponding
ANR的全称为Application Not Respond,意思是应用没有应答。一般在UI主线程中做了繁重的工作,就可能导致ANR的产生。ANR产生的时候,ActivityManagerService的appNotResponding方法就会被调用到,这个方法会在/data/anr/traces.txt
文件写入和ANR相关进程的traces信息。
在以下场景下,appNotResponding会被调用:
-
App的service启动超时。在
ActivityServices.java
中定义了超时标准:static final int SERVICE_TIMEOUT = 20*1000;
-
input事件(按键事件和触屏事件)超时。按键超时的时间为5s,
-
BroadcastReceiver处理时间超时。如果Intent中调用flag FLAG_RECEIVER_FOREGROUND那么超时时间为10s,否则就是60s。其实无论是多少秒,我们要切记,不要在广播接收器中做耗时的工作,这样就能一劳永逸的不用担心超时问题了。
appNotResponding方法内部执行逻辑:
-
统计cpu使用情况
-
将anr的进程,以及父进程,system_server进程,persistent进程加入优先输出trace信息的进程数组;将其他进程加入普通数组
-
调用dumpStackTraces输出进程信息到traces.txt文件
-
调用addErrorToDropBox方法将anr文件加入到dropbox中
-
判断是否立即kill掉anr应用,并退出方法。如果setting里面设置不显示anr dialog,并且应用没有和用户交互,并且anr应用的pid和system_server的pid不相同,同时满足这三个条件,就直接kill掉应用进程
-
发送SHOW_NOT_RESPONDING_MSG消息给ActivityManagerService的handler处理。在这里进一步对是否需要弹出anr对话框进行判断
下面是对是否弹出anr dialog的处理:
case SHOW_NOT_RESPONDING_MSG: {
synchronized (ActivityManagerService.this) {
HashMap data = (HashMap) msg.obj;
ProcessRecord proc = (ProcessRecord)data.get("app");
if (proc != null && proc.anrDialog != null) {
Slog.e(TAG, "App already has anr dialog: " + proc);
return;
}
Intent intent = new Intent("android.intent.action.ANR");
if (!mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
}
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null,
alse, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
if (mShowDialogs) {
Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
mContext, proc, (ActivityRecord)data.get("activity"),
msg.arg1 != 0);
d.show();
proc.anrDialog = d;
} else {
// Just kill the app if there is no dialog to be shown.
killAppAtUsersRequest(proc, null);
}
}
ensureBootCompleted();
} break;
从上面代码可知,ActivityManagerService在处理SHOW_NOT_RESPONDING_MSG时,首先会发送一个Intent告知感兴趣的APP系统发生了ANR,其次会根据mShowDialogs变量来判断是显示一个dialog还是直接kill掉进程。
在ActivityManagerService的updateConfigurationLocked
方法中,有对mShowDialogs变量进行赋值:
mShowDialogs = shouldShowDialogs(newConfig);
shouldShowDialogs的实现如下:
/**
* Decide based on the configuration whether we should shouw the ANR,
* crash, etc dialogs. The idea is that if there is no affordnace to
* press the on-screen buttons, we shouldn't show the dialog.
*
* A thought: SystemUI might also want to get told about this, the Power
* dialog / global actions also might want different behaviors.
*/
private static final boolean shouldShowDialogs(Configuration config) {
return !(config.keyboard == Configuration.KEYBOARD_NOKEYS
&& config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH);
}
1.3. WatchDog
WatchDog的源码位于frameoworks/base/services/java/com/android/server/WatchDog.java
。
WatchDog定义了一个监视器接口:
public interface Monitor {
void monitor();
}
同时提供了注册监视器的方法:
public void addMonitor(Monitor monitor) {
synchronized (this) {
if (isAlive()) {
throw new RuntimeException("Monitors can't be added while the Watchdog is running");
}
mMonitors.add(monitor);
}
}
每隔30s WatchDog就会调用一次注册的监视器的monitor方法,如果超过30s没有返回,就会调用ActivityManagerService的dumpStackTraces方法,产生一个traces.txt文件。除了dump出system_server进程的traces,还会dump出如下进程的trace信息:
static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
"/system/bin/mediaserver",
"/system/bin/sdcard",
"/system/bin/surfaceflinger"
};
在dump完traces信息之后,WatchDog再次等待30s,如果还是有Monitor没有返回,那么就会再次调用dumpStackTraces方法,往traces.txt文件中追加phone进程的trace信息。然后还是dump本进程的kernel stack信息。最后将traces.txt文件加入dropbox,kill掉本进程。
Android的WatchDog,简称看门狗,做大的作用就是检测关键模块有没有陷入死锁的状态。如果陷入死锁,那么就可以用monitor方法检测。
注册的Monitor有哪些呢?
-
ActivityManagerService
-
MountService
-
NetworkManagementService
-
PowerManagerService
-
WindowManagerService
简单以ActivityManagerService介绍下Monitor的实现
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback
上面是ActivityManagerService的定义,可以看出其实现了Monitorj接口。下面是其monitor方法的实现:
/** In this method we try to acquire our lock to make
*sure that we have not deadlocked
*/
public void monitor() {
synchronized (this) { }
}
monitor方法中,其实什么事情都没有做,只是以ActivityManagerService对象为锁,做了一个同步块。但是就是这个同步块却具备了基本的死锁检测机制。如果当前有线程以及给this加锁了,那么monitor方法将会无法访问。这就导致WatchDog超时,从而引发WatchDog的dump行为。
2. DropBox
上面提到的trace文件,都会被保存到dropbox。ActivityManagerService的addErrorToDropBox负责生成dropbox文件。首先看看这个函数的定义:
/**
* Write a description of an error (crash, WTF, ANR) to the drop box.
* @param eventType to include in the drop box tag ("crash", "wtf", etc.)
* @param process which caused the error, null means the system server
* @param activity which triggered the error, null if unknown
* @param parent activity related to the error, null if unknown
* @param subject line related to the error, null if absent
* @param report in long form describing the error, null if absent
* @param logFile to include in the report, null if none
* @param crashInfo giving an application stack trace, null if absent
*/
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, ActivityRecord activity,
ActivityRecord parent, String subject,
final String report, final File logFile,
final ApplicationErrorReport.CrashInfo crashInfo)
evenType:已知的有"lowmem","anr","crash","wtf","watchdog",可以使用grep命令在frameworks目录下面搜索addErrorToDropBox得到全部的方法调用处。
addErrorToDropBox方法执行逻辑如下:
-
构造一份StringBuilder用来生成dropbox信息
-
根据进程属性生成dropboxtag
final String dropboxTag = processClass(process) + "_" + eventType;
private static String processClass(ProcessRecord process) {
if (process == null || process.pid == MY_PID) {
return "system_server";
} else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return "system_app";
} else {
return "data_app";
}
}
-
检查dropboxtag是否是允许的
-
输出ProcessHeader信息到StringBuilder中,Header信息中包括进程名,版本等基础信息
-
读取traces.txt文件的内容到StringBuilder中
-
输出当前时刻log内容,通过查询settings获得当前允许输出的log行数,然后调用logcat从log驱动中读取这么多行数,保存到StringBuilder中
-
最后调用DropBoxManager的addText方法,将StringBuffer的内容保存到dropbox中
2.1. DropBoxManagerService
DropBoxManagerService是一个binder服务,存活在system_server中。其构造代码在frameworks/base/services/java/com/android/server/SystemServer.java
:
try {
Slog.i(TAG, "DropBox Service");
ServiceManager.addService(Context.DROPBOX_SERVICE,
new DropBoxManagerService(context, new File("/data/system/dropbox")));
} catch (Throwable e) {
reportWtf("starting DropBoxManagerService", e);
}
构造函数的第二个参数表示dropbox的目录:/data/system/dropbox。 上个小节提到的,DropBoxManager的addText方法,最终调用的是DropBoxmManagerService的add方法。add方法生成dropbox文件的逻辑如下:
-
add方法首先会在dropbox目录创建一个临时文件,如果输入的dropbox数据size小于block size(4096),那么就以直接将输入数据写入到临时文件;否则将输入数据先进行压缩,然后写入到临时文件
-
然后以临时文件为参数构造一份EntryFile,在EntryFile的构造函数中对临时文件进行更名。更名后的dropbox文件满足这样的约束:dropboxtag + @ + timestap + 后缀名。如果临时文件为压缩文件,那么后缀名为.txt.gz,否则后缀名直接为.txt
-
DropBoxManagerService中保存这所有的dropbox文件记录。每份dropbox文件抽象为EntryFile。每个EntryFile都隶属于一个FileList,FileList中使用TreeSet来存放EntryFile。DropBoxManagerService的成员变量mAllFiles存放了所有的EntryFile记录;mFilesByTag是一个HashMap,以dropboxtag为key,FileList为value,将EntryFile进行归类存放。这几个类之间的关系见下图:
-
在add方法的最后,给DropBoxManagerService的线程发送Message发送消息,请求发送Intent:
DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED
。如果要接收这个广播必须要获得android.Manifest.permission.READ_LOGS权限。