ANR詳細介紹

目錄介紹

  • 1.ANR簡單介紹
  • 2.ANR發生場景
  • 3.ANR發生的原理
  • 4.ANR有哪些具體案例
  • 5.ANR具體如何分析
  • 6.解決方案
  • 7.ANR問題解答

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
  • 連接地址:github.com/yangchong21…
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!

1.ANR簡單介紹

  • 1.1 什麼是ANR
    • ANR Activity not responding(頁面沒有響應)
    • ANR Application not responding 應用沒有響應
    • Android 在4.0以後強制規定 訪問網絡必須開啓子線程
    • 若是在主線程訪問網絡,4.0以後的系統就會拋出:android.os.NetworkOnMainThreadException 在主線程聯網的異常
    • 聯網的時候必定要在子線程操做,只要是耗時的操做,可能會把主線程阻塞住的操做,都要放到子線程裏
    • 主線程(UI線程)16ms,刷新一次界面,一秒60次,60貞/秒
    • ANR(Application Not responding),是指應用程序未響應,Android系統對於一些事件須要在必定的時間範圍內完成,若是超過預約時間能未能獲得有效響應或者響應時間過長,都會形成ANR。
  • 1.2 ANR的產生須要知足三個條件
    • 主線程:只有應用程序進程的主線程響應超時纔會產生ANR;
    • 超時時間:產生ANR的上下文不一樣,超時時間也會不一樣,但只要在這個時間上限內沒有響應就會ANR;
    • 輸入事件/特定操做:輸入事件是指按鍵、觸屏等設備輸入事件,特定操做是指BroadcastReceiver和Service的生命週期中的各個函數,產生ANR的上下文不一樣,致使ANR的緣由也會不一樣;

2.ANR發生場景

  • 主線程,被阻塞5秒鐘以上,就會拋出ANR對話框。不一樣的組件發生ANR的時間不同,Activity是5秒,BroadCastReceiver是10秒,Service是20秒(均爲前臺)。
  • 點擊事件(按鍵和觸摸事件)5s內沒被處理: Input event dispatching timed out
  • service 前臺20s後臺200s未完成啓動 Timeout executing service
    • Service Timeout是位於」ActivityManager」線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息時觸發。
    • 對於Service有兩類:
      • 對於前臺服務,則超時爲SERVICE_TIMEOUT = 20s;
      • 對於後臺服務,則超時爲SERVICE_BACKGROUND_TIMEOUT = 200s
  • BroadcastReceiver的事件(onRecieve方法)在規定時間內沒處理完(前臺廣播爲10s,後臺廣播爲60s):Timeout of broadcast BroadcastRecord
    • 以BroadcastReviever爲例,在onRecieve()方法執行10秒內沒發生第一種ANR(也就是在這個過程當中沒有輸入事件或輸入事件還沒到5s)纔會發生Receiver timeout,不然將先發生事件無相應ANR,因此onRecieve()是有可能執行不到10s就發生ANR的,因此不要在onRecieve()方法裏面幹活
  • ContentProvider的publish在10s內沒進行完:timeout publishing content providers
  • 思考一下,好比service前臺是20秒,後臺是200秒沒響應會致使ANR,那麼這個時間是哪裏來的呢?
    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;
    
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
    
    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
    
        void serviceTimeout(ProcessRecord proc) {
        String anrMessage = null;
    
        synchronized(mAm) {
            if (proc.executingServices.size() == 0 || proc.thread == null) {
                return;
            }
            final long now = SystemClock.uptimeMillis();
            final long maxTime =  now -
                    (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
            ServiceRecord timeout = null;
            long nextTime = 0;
            for (int i=proc.executingServices.size()-1; i>=0; i--) {
                ServiceRecord sr = proc.executingServices.valueAt(i);
                if (sr.executingStart < maxTime) {
                    timeout = sr;
                    break;
                }
                if (sr.executingStart > nextTime) {
                    nextTime = sr.executingStart;
                }
            }
            if (timeout != null && mAm.mLruProcesses.contains(proc)) {
                Slog.w(TAG, "Timeout executing service: " + timeout);
                StringWriter sw = new StringWriter();
                PrintWriter pw = new FastPrintWriter(sw, false, 1024);
                pw.println(timeout);
                timeout.dump(pw, " ");
                pw.close();
                mLastAnrDump = sw.toString();
                mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
                anrMessage = "executing service " + timeout.shortName;
            } else {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_TIMEOUT_MSG);
                msg.obj = proc;
                mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                        ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
            }
        }
    
        if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    }
    複製代碼

3.ANR發生的原理

  • 關於ANR機制實現的原理能夠先看這篇文章,我的以爲寫的十分不錯,能夠看看:gityuan.com/2016/07/02/…
  • 大概原理以下:
    • 1.在進行相關操做調用hander.sendMessageAtTime()發送一個ANR的消息,延時時間爲ANR發生的時間(如activity是當前時間5s以後)。
    • 2.進行相關的操做
    • 3.操做結束後向remove掉該條message。若是相關的操做在規定時間沒有執行完成,該條message將被handler取出並執行,就發生了ANR。

4.ANR有哪些具體案例

  • Acitvity,Fragment中暴力相應點擊事件有可能會致使ANR
  • 斷點調試時,程序可能會出現ANR無限應
  • 主線程作了耗時操做,好比查詢數據庫數據致使ANR

5.ANR具體如何分析

  • ANR問題是因爲主線程的任務在規定時間內沒處理完任務,而形成這種狀況的緣由大體會有一下幾點:
    • 主線程在作一些耗時的工做致使線程卡死
    • 主線程被其餘線程鎖
    • cpu被其餘進程佔用,該進程沒被分配到足夠的cpu資源。
  • 而後看anr日誌。千萬別說不知道在哪裏看日誌,在發生ANR的時候,系統會收集ANR相關的信息提供給開發者:首先在Log中有ANR相關的信息,其次會收集ANR時的CPU使用狀況,還會收集trace信息,也就是當時各個線程的執行狀況。trace文件保存到了/data/anr/traces.txt中
    • 從log中找到ANR反生的信息:會包含了ANR的時間、進程、是何種ANR等信息。
    • 在該條log以後會有CPU usage的信息,代表了CPU在ANR先後的用量(log會代表截取ANR的時間),從各類CPU Usage信息中大概能夠分析以下幾點:
      • 若是某些進程的CPU佔用百分比較高,幾乎佔用了全部CPU資源,而發生ANR的進程CPU佔用爲0%或很是低,則認爲CPU資源被佔用,進程沒有被分配足夠的資源,從而發生了ANR。這種狀況多數能夠認爲是系統狀態的問題,並非由本應用形成的。
      • 若是發生ANR的進程CPU佔用較高,如到了80%或90%以上,則能夠懷疑應用內一些代碼不合理消耗掉了CPU資源,如出現了死循環或者後臺有許多線程執行任務等等緣由,這就要結合trace和ANR先後的log進一步分析了。
      • 若是CPU總用量不高,該進程和其餘進程的佔用太高,這有必定機率是因爲某些主線程的操做就是耗時過長,或者是因爲主進程被鎖形成的。
    • 除了上述分析CPU usage以後,肯定問題須要咱們進一步分析trace文件。trace文件記錄了發生ANR先後該進程的各個線程的stack。對咱們分析ANR問題最有價值的就是其中主線程的stack,通常主線程的trace可能有以下幾種狀況:
      • 主線程是running或者native而對應的棧對應了咱們應用中的函數,則頗有可能就是執行該函數時候發生了超時。
      • 主線程被block:很是明顯的線程被鎖,這時候能夠看是被哪一個線程鎖了,能夠考慮優化代碼。若是是死鎖問題,就更須要及時解決了。
      • 因爲抓trace的時刻頗有可能耗時操做已經執行完了(ANR -> 耗時操做執行完畢 ->系統抓trace),這時候的trace就沒有什麼用了,主線程的stack就是這樣的:
      • image
  • 總結,就是兩個問題
    • 1.CPU 問題
      • 在 Monkeylog.log 文件中定位到 "anr in" 位置,查看 cpu usage ,total 佔用,如發現接近100%,暫時判斷爲 cpu 問題。
      • 而後在 logcat.log 文件中定位到 "not responding" 發生時間,並截取cpuinfo.log 中時間點先後 5s 的 log,而後計算 CPU 佔中,看哪一個進程用的多,在酌情分析模塊的 CPU 佔中。
    • 2.GC 問題
      • 定位到 logcat.log 文件中 "not responding" 發生時間點;
      • 去查看發生 ANR 時間點對應的 trace 文件,定位到應用報名,若Dalvik Thread主線程顯示「SUSPENDED」,則爲內存問題;
      • 截取 ANR 發生時間點前 5s 的 log,分析 "dalvikvm" 打印的 Paused GC 耗時,若是過多則定位爲 GC 問題,須要查看這 5s 件發生了哪些耗時的操做。

6.解決方案

  • 將全部耗時操做,好比訪問網絡,Socket通訊,查詢大量SQL 語句,複雜邏輯計算等都放在子線程中去,然 後經過handler.sendMessage、runonUIThread、AsyncTask 等方式更新UI。不管如何都要確保用戶界面做的流暢 度。若是耗時操做須要讓用戶等待,那麼能夠在界面上顯示度條。
  • 使用AsyncTask處理耗時IO操做。在一些同步的操做主線程有可能被鎖,須要等待其餘線程釋放相應鎖才能繼續執行,這樣會有必定的ANR風險,對於這種狀況有時也能夠用異步線程來執行相應的邏輯。另外, 要避免死鎖的發生。
  • 使用Thread或者HandlerThread時,調用Process.setThreadPriority(Process.THREADPRIORITYBACKGROUND)設置優先級,不然仍然會下降程序響應,由於默認Thread的優先級和主線程相同。
  • 使用Handler處理工做線程結果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。
  • Activity的onCreate和onResume回調中儘可能避免耗時的代碼
  • BroadcastReceiver中onReceive代碼也要儘可能減小耗時,建議使用IntentService處理。
  • 各個組件的生命週期函數都不該該有太耗時的操做,即便對於後臺Service或者ContentProvider來說,應用在後臺運行時候其onCreate()時候不會有用戶輸入引發事件無響應ANR,但其執行時間過長也會引發Service的ANR和ContentProvider的ANR

7.ANR問題解答

  • ANR有異常日誌嗎?或者說ANR在第三方崩潰日誌中有日誌嗎?
    • 沒有異常日誌,由於自己不屬於Error或者Exception

關於其餘內容介紹

01.關於博客彙總連接

02.關於個人博客

項目地址:github.com/yangchong211

相關文章
相關標籤/搜索