Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android框架分析系列之Android traces.txt文件

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

編輯:關於Android編程

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權限。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved