編輯:關於Android編程
Android的traces.txt文件記錄了進程的堆棧情況,對於分析應用響應慢,以及ANR的原因很有幫助。traces.txt文件的位置位於/data/anr/。
traces.txt文件會在三種場景下生成:
應用響應慢
發生ANR
對於應用響應慢的情況有:
Activity執行protected void onPause()方法超時.Android規定的pause響應時間為500毫秒:static final int PAUSE_TIMEOUT = 500;,這個定義位於frameworks/base/services/java/com/android/server/am/ActivityStack.java
但是值得注意的是,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沒有及時的響應;{}中記錄的是組件的名字和地址信息。
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,
appNotResponding方法內部執行邏輯:
統計cpu使用情況
將anr的進程,以及父進程,system_server進程,persistent進程加入優先輸出trace信息的進程數組;將其他進程加入普通數組
調用dumpStackTraces輸出進程信息到traces.txt文件
調用addErrorToDropBox方法將anr文件加入到dropbox中
判斷是否立即kill掉anr應用,並退出方法。如果setting裡面設置不顯示anr dialog,並且應用沒有和用戶交互,並且anr應用的pid和system_server的pid不相同,同時滿足這三個條件,就直接kill掉應用進程
下面是對是否彈出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); }
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
簡單以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行為。
上面提到的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信息
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中
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
在add方法的最後,給DropBoxManagerService的線程發送Message發送消息,請求發送Intent:DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED。如果要接收這個廣播必須要獲得android.Manifest.permission.READ_LOGS權限。
在學習使用Scroller之前,需要明白scrollTo()、scrollBy()方法。一、View的scrollTo()、scrollBy()scrollTo、scro
本文要實現仿微信微信底部菜單欄+頂部菜單欄,采用ViewPage來做,每一個page對應一個XML,當手指在ViewPage左右滑動時,就相應顯示不同的page(其實就是
菜單功能是點擊按鈕彈出分類菜單 看看效果圖 先說一下實現原理,彈出菜單采用的是Fragment實現,很方便且高效,上面的三個按鈕是RadioButton。 新建一個項目
TabActivity在API13之後被fragment替代了,所以不建議使用效果:點擊頭像標簽,進行切換。 代碼:https://github.com/ldb