[gitbook] Android框架分析系列之Android traces.txt文件

  • Post author:
  • Post category:其他

请支持作者原创:

https://mr-cao.gitbooks.io/android/content/android-traces.html点击打开链接

Android traces.txt文件分析

Table of Contents

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文件的。

final void logAppTooSlow(ProcessRecord app, long startTime, String msg) {
    if (true || IS_USER_BUILD) {
        return;
    }
    ……
}

因为在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权限。

欢迎评论 指出错误,及时修改


版权声明:本文为sky_pjf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。