Android 系統穩定性
如果你是一個Android應用程序開發人員,你的人生中不可避免的三件事情是:死亡、繳稅和ANR。這麼說是誇張了,但是由於Android本身的設計,以及應用程式和系統在開發過程中的缺陷,經常會在測試過程中遇到各種各樣的ANR問題。在功能性的測試中還少一些,主要是在壓力測試中(例如Monkey測試)會遇到非常多的ANR問題。本章的目的就是彙總筆者在工作中遇到的各種ANR問題,將其歸納總結出一套分析和處理ANR問題的方法,希望能夠通過這套方法為大家提供思路,有效的減少大家處理ANR問題的時間。同時也會給出一些避免ANR的最佳實踐,更多的從預防做起,更少的做事後補救。
考慮到看本文的讀者大多是有實際經驗的開發人員,我會盡量少的提到一些基礎的概念,我也希望給大家更多的“乾貨”。
本章的主要內容如下:
-
ANR簡介(什麼是ANR、為什麼會有ANR、ANR的異常長什麼樣)
-
如何分析ANR(引起ANR的原因分類、分析ANR的利器)
-
例項講解
-
避免ANR的最佳實踐(從錯誤中吸取教訓)
1.1 ANR簡介
ANR,是“Application Not Responding”的縮寫,即“應用程式無響應”。在Android中,ActivityManagerService(簡稱AMS)和WindowManagerService(簡稱WMS)會監測應用程式的響應時間,如果應用程式主執行緒(即UI執行緒)在超時時間內對輸入事件沒有處理完畢,或者對特定操作沒有執行完畢,就會出現
1.1.1為什麼會有ANR
如上所述,ANR的產生需要同時滿足三個條件:
-
主執行緒:只有應用程式程序的主執行緒響應超時才會產生ANR;
-
超時時間:產生ANR的上下文不同,超時時間也會不同,但只要在這個時間上限內沒有響應就會ANR;
-
輸入事件/特定操作:輸入事件是指按鍵、觸屏等裝置輸入事件,特定操作是指BroadcastReceiver和Service的生命週期中的各個函式,產生ANR
針對這三個條件,有以下三種情況會觸發ANR,詳細說明如下
1、主執行緒對輸入事件在5秒內沒有處理完畢
Android的事件系統從2.3開始做了完全不同的實現,原先2.2中是在Java層實現的,但在2.3中整體轉移到了C++層,本書基於2.3以後的版本進行說明。我們先簡單瞭解一下產生這種ANR的整個流程<!-- 在這章之前要先講Android的事件機制?還是放在後一章講?偏向於放在後面。 -->。
當應用程式的Window處於Active狀態並且能夠接收輸入事件(例如按鍵事件、觸控事件等)時,系統底層上報的事件就會被InputDispatcher分發給這個應用程式,應用程式的主執行緒通過InputChannel讀取輸入事件並交給介面檢視處理,介面檢視是一個樹狀結構,DecorView是檢視樹的根,事件從樹根開始一層一層向端點(例如一個Button)傳遞。我們通常會註冊一個監聽器來接收並處理事件,或者建立自定義的檢視控制元件來處理事件。
InputDispatcher執行在系統程序(程序名為system_server)的一個單獨的執行緒中,應用程式的主執行緒在處理事件的過程中,InputDispatcher會不斷的檢測處理過程是否超時,一旦超時,會通過一系列的回撥通知WMS的notifyANR函式,最終會呼叫到AMS中mHandler物件裡的SHOW_NOT_RESPONDING_MSG這個case,此時介面上就顯示系統提示對話方塊了,同時使用logcat命令檢視log(日誌資訊)也可以看到關於ANR的資訊。InputDispatcher就是那個愛打“小報告”的傢伙。
一下子出現了好幾個重要的名詞,要深入瞭解這種情況的ANR,需要熟悉Android的事件機制,本書會在別的章節中詳細分析,這裡只需要記住他們的功能:
-
Window:具體指的是PhoneWindow物件,表示一個能夠顯示的視窗,它能夠接收系統分發的各種輸入事件;
-
InputDispatcher:將系統上報的輸入事件分發給當前活動的視窗;
-
InputChannel:InputDispatcher和應用程式分別執行在兩個不同的程序中,InputDispatcher就是通過InputChannel將事件物件傳遞給應用程序的。
注意:產生這種ANR的前提是要有輸入事件,如果使用者沒有觸發任何輸入事件,即便是主執行緒阻塞了,也不會產生ANR,因為InputDispatcher沒有分發事件給應用程式,當然也不會檢測處理超時和報告ANR了。
2、主執行緒在執行BroadcastReceiver的onReceive函式時10秒內沒有執行完畢
BroadcastReceiver(簡稱BR)的onReceive函式執行在主執行緒中,當這個函式超過10秒鐘沒有返回就會觸發ANR。不過對這種情況的ANR系統不會顯示對話方塊提示,僅是輸出log而已。
3、主執行緒在執行Service的各個生命週期函式時20秒內沒有執行完畢
Service的各個生命週期函式也執行在主執行緒中,當這些函式超過20秒鐘沒有返回就會觸發ANR。同樣對這種情況的ANR系統也不會顯示對話方塊提示,僅是輸出log。
三種ANR中只有第1種會顯示系統提示對話方塊,因為使用者正在做介面互動操作,如果長時間沒有任何響應,會讓使用者懷疑裝置宕機了,大多數人此時會開始亂按,甚至拔出電池重啟,給使用者的體驗肯定是非常糟糕的。
三種ANR發生時都會在log中輸出錯誤資訊,你會發現各個應用程序和系統程序的函式堆疊資訊都輸出到了一個/data/anr/traces.txt的檔案中,這個檔案是分析ANR原因的關鍵檔案,同時在日誌中還會看到當時的CPU使用率,這也是重要資訊,在後面的章節會詳細介紹如何利用它們分析ANR問題。
這三種ANR不是孤立的,有可能會相互影響。例如一個應用程式程序中同時有一個正在顯示的Activity和一個正在處理訊息的BroadcastReceiver,它們都執行在這個程序的主執行緒中。如果BR的onReceive函式沒有返回,此時使用者點選螢幕,而onReceive超過5秒仍然沒有返回,主執行緒無法處理使用者輸入事件,就會引起第1種ANR。如果繼續超過10秒沒有返回,又會引起第2種ANR。
1.2 如何分析ANR問題
引起ANR問題的根本原因,總的來說可以歸納為兩類:
-
應用程序自身引起的,例如:
主執行緒阻塞、掛起、死迴圈
應用程序的其他執行緒的CPU佔用率高,使得主執行緒無法搶佔到CPU時間片
-
其他程序間接引起的,例如:
當前應用程序進行程序間通訊請求其他程序,其他程序的操作長時間沒有反饋
其他程序的CPU佔用率高,使得當前應用程序無法搶佔到CPU時間片
分析ANR問題時,以上述可能的幾種原因為線索,通過分析各種日誌資訊,大多數情況下你就可以很容易找到問題所在了。
注意:我很誠懇的向讀者說明,確實有一些ANR問題很難調查清楚,因為整個系統不穩定的因素很多,例如Linux Kernel本身的bug引起的記憶體碎片過多、硬體損壞等。這類比較底層的原因引起的ANR問題往往無從查起,並且這根本不是應用程式的問題,浪費了應用開發人員很多時間,如果你從事過整個系統的開發和維護工作的話會深有體會。所以我不能保證瞭解了本章的所有內容後能夠解決一切ANR問題,如果出現了很疑難的ANR問題,我建議最好去和做驅動和核心的朋友聊聊,或者,如果問題只是個十萬分之一的偶然現象,不影響程式的正常執行,我倒是建議不去理它。
1.2.1分析ANR的利器
會在ANR發生時輸出很多有用的資訊幫助分析問題原因,我們先來看一下ANR的異常資訊,使用logcat命令檢視會得到類似如下的log:
//WindowManager所在的程序是system_server,程序號是127
I/WindowManager( 127): Input event dispatching timed out sending to com.example.anrdemo/com.example.anrdemo.ANRActivity
//system_server程序中的ActivityManagerService請求kernel向5033程序傳送SIGNAL_QUIT請求
//你可以在shell中使用命令達到相同的目的:adb shell kill -3 5033
//和其他的Java虛擬機器一樣,SIGNAL_QUIT也是Dalvik內部支援的功能之一
I/Process ( 127): Sending signal. PID: 5033 SIG: 3
//5033程序的虛擬機器例項接收到SIGNAL_QUIT訊號後會將程序中各個執行緒的函式堆疊資訊輸出到traces.txt檔案中
//發生ANR的程序正常情況下會第一個輸出
I/dalvikvm( 5033): threadid=4: reacting to signal 3
I/dalvikvm( 5033): Wrote stack traces to '/data/anr/traces.txt'
... ...//另外還有其他一些程序
//隨後會輸出CPU使用情況
E/ActivityManager( 127): ANR in com.example.anrdemo (com.example.anrdemo/.ANRActivity)
//Reason表示導致ANR問題的直接原因
E/ActivityManager( 127): Reason: keyDispatchingTimedOut
E/ActivityManager( 127): Load: 3.85 / 3.41 / 3.16
//請注意ago,表示ANR發生之前的一段時間內的CPU使用率,並不是某一時刻的值
E/ActivityManager( 127): CPU usage from 26835ms to 3662ms ago with 99% awake:
E/ActivityManager( 127): 9.4% 98/mediaserver: 9.4% user + 0% kernel
E/ActivityManager( 127): 8.9% 127/system_server: 6.9% user + 2% kernel / faults: 1823 minor
... ...
E/ActivityManager( 127): +0% 5033/com.example.anrdemo: 0% user + 0% kernel
E/ActivityManager( 127): 39% TOTAL: 32% user + 6.1% kernel
//這裡是later,表示ANR發生之後
E/ActivityManager( 127): CPU usage from 601ms to 1132ms later with 99% awake:
E/ActivityManager( 127): 10% 127/system_server: 1.7% user + 8.9% kernel / faults: 5 minor
E/ActivityManager( 127): 10% 163/InputDispatcher: 1.7% user + 8.9% kernel
E/ActivityManager( 127): 1.7% 127/system_server: 1.7% user + 0% kernel
E/ActivityManager( 127): 1.7% 135/SurfaceFlinger: 0% user + 1.7% kernel
E/ActivityManager( 127): 1.7% 2814/Binder Thread #: 1.7% user + 0% kernel
... ...
E/ActivityManager( 127): 37% TOTAL: 27% user + 9.2% kernel
從log中能夠知道,發生ANR時Android為我們提供了兩種“利器”:traces檔案和CPU使用率。以上做了簡單註釋,不過稍後再詳細分析它們。
1.2.2 ANR資訊是如何輸出的
我們再來看看這些log是怎樣被輸出的,這很重要,知其然,也要知其所以然。程式碼在ActivityManagerService類中,找到它的appNotResponding函式。
final void appNotResponding(ProcessRecord app, ActivityRecord activity,ActivityRecord parent, final String annotation) {
//firstPids和lastPids兩個集合存放那些將會在traces中輸出資訊的程序的程序號
ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
//mController是IActivityController介面的例項,是為Monkey測試程式預留的,預設為null
if (mController != null) {
... ...
}
long anrTime = SystemClock.uptimeMillis();
if (MONITOR_CPU_USAGE) {
updateCpuStatsNow(); //更新CPU使用率
}
synchronized (this) { //一些特定條件下會忽略ANR
if (mShuttingDown) {
Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
return;
} else if (app.notResponding) {
Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
return;
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
}
//使用一個標誌變數避免同一個應用在沒有處理完時重複輸出log
app.notResponding = true;
... ...
//①當前發生ANR的應用程序被第一個新增進firstPids集合中
firstPids.add(app.pid);
... ...
}
... ...
//②dumpStackTraces是輸出traces檔案的函式
File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids, null);
String cpuInfo = null;
if (MONITOR_CPU_USAGE) { //MONITOR_CPU_USAGE預設為true
updateCpuStatsNow(); //再次更新CPU資訊
synchronized (mProcessStatsThread) {
//輸出ANR發生前一段時間內的CPU使用率
cpuInfo = mProcessStats.printCurrentState(anrTime);
}
info.append(processStats.printCurrentLoad());
info.append(cpuInfo);
}
//輸出ANR發生後一段時間內的CPU使用率
info.append(processStats.printCurrentState(anrTime));
... ...
//③將ANR資訊同時輸出到DropBox中
addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
... ...
//在Android4.0中可以設定是否不顯示ANR提示對話方塊,如果設定的話就不會顯示對話方塊,並且會殺掉ANR程序
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
synchronized (this) {
if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
... ...
Process.killProcessQuiet(app.pid);
return;
}
... ...
//顯示ANR提示對話方塊
Message msg = Message.obtain();
HashMap map = new HashMap();
msg.what = SHOW_NOT_RESPONDING_MSG;
msg.obj = map;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mHandler.sendMessage(msg);
}
}
有三個關鍵點需要注意:
1、當前發生ANR的應用程序被第一個新增進firstPids集合中,所以會第一個向traces檔案中寫入資訊。反過來說,traces檔案中出現的第一個程序正常情況下就是發生ANR的那個程序。不過有時候會很不湊巧,發生ANR的程序還沒有來得及輸出trace資訊,就由於某種原因退出了,所以偶爾會遇到traces檔案中找不到發生ANR的程序資訊的情況。
2、dumpStackTraces是輸出traces檔案的函式,接下來分析這個函式
3、addErrorToDropBox函式將ANR資訊同時輸出到DropBox中,它也是個非常有用的日誌存放工具,後面也會分析它的作用。
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
ProcessStats processStats, SparseArray<Boolean> lastPids, String[] nativeProcs) {
//系統屬性“dalvik.vm.stack-trace-file”用來配置trace資訊輸出檔案
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
}
File tracesFile = new File(tracesPath);
try {
File tracesDir = tracesFile.getParentFile();
if (!tracesDir.exists()) tracesFile.mkdirs();
//FileUtils.setPermissions是個很有用的函式,設定檔案屬性時經常會用到
FileUtils.setPermissions(tracesDir.getPath(), 0775, -1, -1); // drwxrwxr-x
//clearTraces為true,會刪除舊檔案,建立新檔案
if (clearTraces && tracesFile.exists()) tracesFile.delete();
tracesFile.createNewFile();
FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
} catch (IOException e) {
Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
return null;
}
//一個過載函式
dumpStackTraces(tracesPath, firstPids, processStats, lastPids, nativeProcs);
return tracesFile;
}
有兩個關鍵點需要注意:
①聰明的你肯定已經知道,之所以trace資訊會輸出到“/data/anr/traces.txt”檔案中,就是系統屬性“dalvik.vm.stack-trace-file”設定的。你可以通過在裝置的shell中使用setprop和getprop對系統屬性進行設定和讀取:
getpropdalvik.vm.stack-trace-file
setprop dalvik.vm.stack-trace-file /tmp/stack-traces.txt
②每次發生ANR時都會刪除舊的traces檔案,重新建立新檔案。也就是說Android只保留最後一次發生ANR時的traces資訊,那麼以前的traces資訊就丟失了麼?稍後回答。
接著來看過載的dumpStackTraces函式。
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
ProcessStats processStats, SparseArray<Boolean> lastPids, String[] nativeProcs) {
//使用FileObserver監聽應用程序是否已經完成寫入traces檔案的操作
//Android在判斷桌面桌布檔案是否設定完成時也是用的FileObserver,很有用的類
FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
public synchronized void onEvent(int event, String path) { notify(); }
};
... ...
//首先輸出firstPids集合中指定的程序,這些也是對ANR問題來說最重要的程序
if (firstPids != null) {
try {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
synchronized (observer) {
//前面提到的SIGNAL_QUIT
Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
observer.wait(200);
... ...
}
提示:如果你在解決其他問題時也需要檢視Java程序中各個執行緒的函式堆疊資訊,就可以使用向目標程序傳送SIGNAL_QUIT(3)這個技巧。其實這個名稱有點誤導,它並不會讓程序真正退出。
1.2.3 DropBox
剛才留了一個問題:Android只保留最後一次發生ANR時的traces資訊,那麼以前的traces資訊就丟失了麼?為了增強Android的異常資訊收集管理能力,從2.2開始增加了DropBox功能。
DropBox(簡稱DB)是系統程序中的一個服務,在system_server程序啟動時建立,並且它沒有執行在單獨的執行緒中,而是執行在system_server的ServerThread執行緒中。我們可以將ServerThread稱作system_server的主執行緒,ServerThread執行緒除了啟動並維護各個服務外,還負責檢測一些重要的服務是否死鎖,這一點到後面的Watchdog部分再分析<!-- Watchdog寫完後注意補充章節號 -->。DB被建立的程式碼如下。
SystemServer.java → ServerThread.run()
Slog.i(TAG, "DropBox Service");
ServiceManager.addService(Context.DROPBOX_SERVICE, //服務名稱為“dropbox”
new DropBoxManagerService(context,new File("/data/system/dropbox")));
“/data/system/dropbox”是DB指定的檔案存放位置。下面來看一下DB服務的主要功能。
1. DropBoxManagerService
DropBoxManagerService(簡稱DBMS
相信大部分Android 開發人員都遇到過ANR問題,本文根據一些實際的開發經歷介紹一下如何解決和避免ANR問題。
一,何為ANR
ANR是“Application Not Responding”的縮寫,即“應用程式無響應”。在Android中,
Crash、FC、froce close
發生場景
應用程序崩潰。強制關閉,android App 因為程式碼異常導致奔潰。
表現形式主要有兩種
1. Java 程式碼引起,彈出錯誤的提示框,JVM虛擬機器退出,絕大多數的工具可以捕獲。
2. C++程式
如果你是一個Android應用程序開發人員,你的人生中不可避免的三件事情是:死亡、繳稅和ANR。這麼說是誇張了,但是由於Android本身的設計,以及應用程式和系統在開發過程中的缺陷,經常會在測試過程中遇到各種各樣的ANR問題。在功能性的測試中還少一些,主要
P.S. OpenOffice貼上過來後格式有些混亂。
1.2如何分析ANR問題
引起ANR問題的根本原因,總的來說可以歸納為兩類:
應用程序自身引起的,例如:
主執行緒阻塞、掛起、死迴圈
應用程序的其他執行緒的CPU佔用率高,使得主執行緒無法搶 原生 android4 代碼 豎屏 == watermark ont 分辨率 軟件
1.1概述
Android4.0,Androd4.4源代碼能夠編譯成手機模式和平板模式,訊為iTop4412 開發平臺
的Android系統默認編譯為平板模式。客戶須要依據自己的產品 nbsp pop -m andro shel content data popu 命令
命令如 date -s "yyyymmdd.[[[hh]mm]ss]"
直接在CRT上執行,舉例:date -s "20120801.120503"
但在adb shell下 onf med cin gets get lld 系統屬性 基本 安裝模塊
從大的方面來說。Android系統的啟動能夠分為兩個部分:第一部分是Linux核心的啟動,第二部分是Android系統的啟動。第一部分主要包含系統引導,核心和驅動程序等,因為它們不屬於本篇要講的 包沖突 日誌 rep 減少 機房 並且 trace 調用接口 做到 本篇文章主要介紹了"分布式系統穩定性模式",主要涉及到分布式系統穩定性模式方面的內容,對於分布式系統穩定性模式感興趣的同學可以參考一下。
對集群大訪問量的應用很有針對性的一些註意點。
1.隔離。 發 port odi -a 個推 post setting moved ast 是不是
高速改動android系統默認日期方法 在android系統的設備上,都有一個默認的開始日期,看過非常多設備,有些設備在沒有聯網的時候沒有同步到系統時間的時候,竟然默 net 文章 圖文 str 鍵盤操作 左右移動 ima 工具 解決 樹莓派3安裝Android TV系統圖文教程
http://www.mz6.net/news/android/6866.html
樹莓派3 Android TV系統怎樣安裝?樹莓派3一個重要用途就是當智 ont hao123 provide music content vid uri android系統 系統 CONTENTpROVIDER%E5%9F%BA%E7%A1%80%E4%B9%8BURI
http://music.hao123.com/songlist/495 tps alt 系統 f11 技術文章 無限 通過 技術分享 center
為什麽要做全鏈路壓測?
對阿裏巴巴而言,每年最重要的一天莫過於雙11。這是因為在雙11的零點,系統會遭遇史無前例的巨大洪峰流量沖擊,保證雙11當天系統的穩定性對高可用團隊來說是巨大的挑戰。在這個 android 編譯出錯在Android7.1編譯過程中出現如下錯誤:Starting
build with ninjaninja: Entering directory `.‘ [ 0% 8/30301] Ensure
Jack server is installed and startedJack android targe base center lang rda 16px oca 語言 第一種方法:修改 build/tools/buildinfo.shecho "ro.product.locale.language=zh"echo "ro.product.loca 技術 場景 每次 自己 html 能說 控制 bsp 進行 說起系統穩定性,其實已經有很多文章了.我這裏結合自己實際項目中的一些情況,進行了反思.
業務場景其實也很簡單.就是我們需要做一個爬蟲去爬取別的網站的文章和圖片.
主要問題出在圖片上,當時我在想可不可以不爬取 art intent pre fonts mark bsp androi 希望 andro 在android的app開發中,經常會遇到需要跳轉至系統設置頁面的需求。但是當你使用以下代碼時:
如
Intent intent = new Intent(Settings.ACT wearable versions 應用 示例 tro tex ria col ide
雖然之前分析了gradle,但是在eclipse導入Android studio的時候,各個版本出現的問題還是很模糊,下面對各種版本進行一下說明:
參考資料:
https:// 時間排序 txt ash 可執行 字符串 awk -c 歸檔 行數
cat -n access.log -n打印行號
more access.log 空格-下一頁、回車-下一行、F-下一屏,百分比的下一個、B-上一屏
less access.log /查 option 寫入 android change failed miss 通知 target sna 前言
作為“Android框架層”這個大系列中的第一個系列,我們首先要了解的是Android系統啟動流程,在這個流程中會涉及到很多重要的知識點,這個系列我們就來一一講解它們 詳細 dia strong esc trac roc contract raw body android系統常用URI
android系統管理聯系人的URI如下:
ContactsContract.Contacts.CONTENT_URI 管理聯系人的Uri
Contact 相關推薦
Android 系統穩定性之ANR
Android系統穩定性----Crash
Android 系統穩定性
android系統穩定性-ANR(二)
【分享】迅為iTOP4412開發板-Android系統屏幕旋轉設置
改動android 系統時間
Android核心服務解析篇(三)——Android系統的啟動
分布式系統穩定性模式
高速改動android系統默認日期方法
【樹莓派】樹莓派刷Android系統
調用aNDROID系統自帶功能
技術文章 | 系統穩定性保障核武器——全鏈路壓測
Android系統編譯出錯
Android系統默認語言改為中文
實際項目中對系統穩定性的一些思考
如何直接打開android系統的wifi設置頁面,防止intent劫持
Android系統版本、Platform版本、SDK版本、gradle修改
第4章系統穩定性4.1在線日誌分析
Android系統啟動流程(一)解析init進程啟動過程
Android系統常用URI