Android進階知識:ANR的定位與解決

一、前言

ANR對於Android開發者來講必定不會陌生,從剛開始學習Android時的一不注意就ANR,到後來知道主線程不能進行耗時操做注意到這點後,程序出現ANR的狀況就大大減小了,甚至於消失了。那麼真的是隻要在主線程作耗時操做就會產生ANR嗎?爲何在有時候明明以爲本身沒在主線程作耗時操做也出現了ANR呢?一旦出現莫名其妙的ANR,怎麼定位致使ANR的產生的位置和解決問題呢?那麼接下來就來一個個的解決這些問題。java

二、ANR是什麼?

ANR全稱Application Not Responding即應用程序無響應。在Android中若是應用程序有一段時間沒法響應用戶操做,系統會彈出彈窗,讓用戶選擇是繼續等待仍是強制關閉程序。一款良好應用APP是不該該出現這個彈窗的。react

三、ANR的產生緣由

ANR產生緣由和類型有如下幾種:android

一、Activity在5秒鐘以內沒法響應屏幕觸摸事件揮着鍵盤輸入事件就會產生ANR

KeyDispatchTimeout
Reason:Input event dispatching timed outshell

二、BroadcastReceiver在10秒鐘以內還未執行完成就會產生ANR

BroadcastTimeout
Reason:Timeout of broadcast BroadcastRecordbash

三、Service各個生命週期在20秒鐘以內沒有執行完成就會產生ANR

ServiceTimeout
Reason:Timeout executing service多線程

四、ContentProvider在10秒鐘以內沒有執行完成就會產生ANR

ContentProviderTimeout
Reason:timeout publishing content providerside

在以上這幾種緣由中出現最多的通常是第一種,並且每每都是由於在寫代碼時不注意,在主線程作了耗時的操做。函數

四、ANR的定位與解決

關於ANR的定位這裏舉一個例子來看。這是我以前遇到的一次出現ANR的時候所解決問題的狀況和解決步驟。學習

首先固然是復現 ANR現象,找準 ANR出現的地方,查看對應代碼,若是能直接看出來問題所在,找到代碼中作的錯誤操做那麼直接修改相應代碼就解決問題了。可是若是無法輕易看出問題緣由,接下來就只好去 Logcat中查看對應的錯誤日誌。

07-22 21:39:17.019 819-851/? E/ActivityManager: ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)
    PID: 7398
    Reason: Input dispatching timed out (com.xxxx.performance/com.xxxx.performance.view.home.activity.MainActivity, Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 29.  Wait queue head age: 8579.3ms.)
    Load: 18.22 / 18.1 / 18.18
    CPU usage from 0ms to 8653ms later:
      124% 7398/com.xxxx.performance: 118% user + 6.5% kernel / faults: 4962 minor 7 major
      82% 819/system_server: 28% user + 53% kernel / faults: 10555 minor 11 major
      23% 4402/adbd: 1% user + 22% kernel
      10% 996/com.android.systemui: 4.6% user + 6.2% kernel / faults: 4677 minor 1 major
      4.6% 2215/com.android.phone: 1.5% user + 3.1% kernel / faults: 5411 minor
      6.3% 6268/perfd: 3.4% user + 2.8% kernel / faults: 134 minor
      0.5% 1149/com.miui.whetstone: 0.1% user + 0.3% kernel / faults: 3016 minor 1 major
      0.2% 2097/com.xiaomi.finddevice: 0.1% user + 0.1% kernel / faults: 2256 minor
      0.6% 2143/com.miui.daemon: 0.2% user + 0.3% kernel / faults: 2798 minor
      1.2% 1076/com.xiaomi.xmsf: 0.6% user + 0.6% kernel / faults: 2802 minor
      0% 2122/com.android.server.telecom: 0% user + 0% kernel / faults: 2929 minor
      0% 2244/com.miui.contentcatcher: 0% user + 0% kernel / faults: 1800 minor
      0% 2267/com.mediatek.nlpservice: 0% user + 0% kernel / faults: 2052 minor
      0% 2166/com.xiaomi.mitunes: 0% user + 0% kernel / faults: 1797 minor 3 major
      0% 2190/com.fingerprints.service: 0% user + 0% kernel / faults: 1857 minor
      0.1% 154/mmcqd/0: 0% user + 0.1% kernel
      0.4% 8069/logcat: 0.3% user + 0.1% kernel
      ......
複製代碼

從日誌第一行開始看,能夠看到發生錯誤的應用包名和類名,這裏是ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)。接着看到進程號PID7398。發生ANRReasonInput dispatching timed out就是上面提到的第一種。再往下就是活躍進程的CPU佔用率日誌。測試

124% 7398/com.xxxx.performance
   82% 819/system_server
   10% 996/com.android.systemui
   4.6% 2215/com.android.phone
   ......
複製代碼

光看Logcat中的日誌只能看到這些信息,大概知道是在MainActivity出現了問題,但仍是不能清楚的定位到發生ANR的代碼行,想要得到進一步的錯誤信息只能經過查看ANR過程當中生成的堆棧信息文件traces.txt了。

traces.txt文件位置在/data/anr/目錄下,能夠經過如下adb命令將其拷貝到sd卡目錄下獲取查看。

adb shell
cat  /data/anr/traces.txt  >/mnt/sdcard/traces.txt  
exit
複製代碼

traces.txt裏的信息:

DALVIK THREADS (42):
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x75ceafb8 self=0x55933ae7e0
  | sysTid=7398 nice=0 cgrp=default sched=0/0 handle=0x7f7ddae0f0
  | state=S schedstat=( 101485399944 3411372871 31344 ) utm=9936 stm=212 core=1 HZ=100
  | stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0x74/0x8c
  kernel: futex_wait_queue_me+0xcc/0x158
  kernel: futex_wait+0x120/0x20c
  kernel: do_futex+0x184/0xa48
  kernel: SyS_futex+0x88/0x19c
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 00017750 /system/lib64/libc.so (syscall+28)
  native: #01 pc 000d1584 /system/lib64/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+140)
  native: #02 pc 00388098 /system/lib64/libart.so (_ZN3artL12GoToRunnableEPNS_6ThreadE+1068)
  native: #03 pc 000a5db8 /system/lib64/libart.so (_ZN3art12JniMethodEndEjPNS_6ThreadE+24)
  native: #04 pc 000280e4 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_graphics_Paint_native_1init__+156)
  at android.graphics.Paint.native_init(Native method)
  at android.graphics.Paint.<init>(Paint.java:435)
  at android.graphics.Paint.<init>(Paint.java:425)
  at android.text.TextPaint.<init>(TextPaint.java:49)
  at android.text.Layout.<init>(Layout.java:160)
  at android.text.StaticLayout.<init>(StaticLayout.java:111)
  at android.text.StaticLayout.<init>(StaticLayout.java:87)
  at android.text.StaticLayout.<init>(StaticLayout.java:66)
  at android.widget.TextView.makeSingleLayout(TextView.java:6543)
  at android.widget.TextView.makeNewLayout(TextView.java:6383)
  at android.widget.TextView.checkForRelayout(TextView.java:7096)
  at android.widget.TextView.setText(TextView.java:4082)
  at android.widget.TextView.setText(TextView.java:3940)
  at android.widget.TextView.setText(TextView.java:3915)
  at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)
  at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:214)
  at com.xxxx.performance.presenter.attendance.AttendanceFragmentPresenter$6.onNext(AttendanceFragmentPresenter.java:205)
  at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:198)
  at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:250)
  at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
  ......
  ......
複製代碼

仍是從頭開始看,來看每一個字段對應的含義:
線程名:main
線程優先級:prio=5
線程鎖ID: tid=1
線程狀態:Native 線程組名稱:group="main"
線程被掛起的次數:sCount=1
線程被調試器掛起的次數:dsCount=0
線程的java的對象地址:obj=0x75ceafb8
線程自己的Native對象地址:self=0x55933ae7e0
線程調度信息:
Linux系統中內核線程ID: sysTid=7398與主線程的進程號相同
線程調度優先級:nice=0
線程調度組:cgrp=default
線程調度策略和優先級:sched=0/0
線程處理函數地址:handle=0x7f7ddae0f0
線程的上下文信息:
線程調度狀態:state=S
線程在CPU中的執行時間、線程等待時間、線程執行的時間片長度:schedstat=(101485399944 3411372871 31344 )
線程在用戶態中的調度時間值:utm=9936
線程在內核態中的調度時間值:stm=212
最後執行這個線程的CPU核序號:core=1
線程的堆棧信息:
堆棧地址和大小:stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
最後看到堆棧信息裏的這一行:

at com.xxxx.performance.view.home.fragment.AttendanceCheckInFragment.onNowTimeSuccess(AttendanceCheckInFragment.java:887)
複製代碼

這裏就看清楚了是在AttendanceCheckInFragment中的887行出現的問題,再到對應代碼行中就很容易發現ANR的緣由了。

五、ANR的相關問題

  • 在Activity的onCreate方法裏調用sleep方法會發生ANR嗎?

之前一直認爲在主線程作了耗時操做就會發生ANR,那麼真的是這樣嗎?在ActivityonCreate方法裏調用Thread.sleep(60 * 1000)讓主線程sleep60秒,會致使應用程序ANR嗎?寫個Demo測試一下。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Log.d("ANR","開始sleep");
            Thread.sleep(60*1000);
            Log.d("ANR","sleep完成");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

如上代碼,運行程序,結果應用沒有發生ANR,在sleep了60秒後正常打印日誌。

再次運行程序,這回在程序運行後按下返回鍵查看現象:

此次果真就 ANR了。經過這個例子,顯而易見的獲得了這個問題的正確答案。在 ActivityonCreate方法裏調用 sleep方法或者說作耗時操做,不必定會產生 ANR。其實從 ANR自己意爲應用程序沒有響應,同時根據上面總結的 ANR緣由就能夠看出,耗時操做自己是不會產生 ANR的,致使 ANR的根本仍是應用程序沒法在必定時間內響應用戶的操做。因此由於主線程被耗時操做佔用了,主線成程沒法對下一個操做進行響應纔會 ANR,沒有須要響應的操做天然就不會產生 ANR,或者應該這樣說:主線程作耗時操做,很是容易引起 ANR

六、總結

  • 光在主線程作耗時操做不會產生ANR,超時響應用戶操做纔會產生ANR。
  • ANR的定位方法主要是根據Logcat中日誌和ANR過程當中生成的堆棧信息文件traces.txt。
  • 解決問題不如預防問題,寫代碼的時候要注意預防產生ANR。
  • 預防ANR的產生不光是在Activity中注意要把耗時操做放到子線程中去,還要注意在使用其餘三個組件時,在其生命週期中一樣不能作太耗時的操做。另外在使用多線程時候要注意同步和死鎖的狀況,一旦產生死鎖主線程一樣會引起ANR。
相關文章
相關標籤/搜索