Android进阶知识:ANR的定位与解决
1、前言
ANR對(duì)于Android開發(fā)者來說一定不會(huì)陌生,從剛開始學(xué)習(xí)Android時(shí)的一不注意就ANR,到后來知道主線程不能進(jìn)行耗時(shí)操作注意到這點(diǎn)后,程序出現(xiàn)ANR的情況就大大減少了,甚至于消失了。那么真的是只要在主線程做耗時(shí)操作就會(huì)產(chǎn)生ANR嗎?為什么在有時(shí)候明明覺得自己沒在主線程做耗時(shí)操作也出現(xiàn)了ANR呢?一旦出現(xiàn)莫名其妙的ANR,怎么定位導(dǎo)致ANR的產(chǎn)生的位置和解決問題呢?那么接下來就來一個(gè)個(gè)的解決這些問題。
2、ANR是什么?
ANR全稱Application Not Responding即應(yīng)用程序無響應(yīng)。在Android中如果應(yīng)用程序有一段時(shí)間無法響應(yīng)用戶操作,系統(tǒng)會(huì)彈出彈窗,讓用戶選擇是繼續(xù)等待還是強(qiáng)制關(guān)閉程序。一款良好應(yīng)用APP是不應(yīng)該出現(xiàn)這個(gè)彈窗的。
3、ANR的產(chǎn)生原因
ANR產(chǎn)生原因和類型有以下幾種:
1、Activity在5秒鐘之內(nèi)無法響應(yīng)屏幕觸摸事件揮著鍵盤輸入事件就會(huì)產(chǎn)生ANR。
KeyDispatchTimeout
Reason:Input event dispatching timed out
2、BroadcastReceiver在10秒鐘之內(nèi)還未執(zhí)行完成就會(huì)產(chǎn)生ANR。
BroadcastTimeout
Reason:Timeout of broadcast BroadcastRecord
3、Service各個(gè)生命周期在20秒鐘之內(nèi)沒有執(zhí)行完成就會(huì)產(chǎn)生ANR。
ServiceTimeout
Reason:Timeout executing service
4、ContentProvider在10秒鐘之內(nèi)沒有執(zhí)行完成就會(huì)產(chǎn)生ANR。
ContentProviderTimeout
Reason:timeout publishing content providers
在以上這幾種原因中出現(xiàn)最多的一般是第一種,而且往往都是因?yàn)樵趯懘a時(shí)不注意,在主線程做了耗時(shí)的操作。
4、ANR的定位與解決
關(guān)于ANR的定位這里舉一個(gè)例子來看。這是我之前遇到的一次出現(xiàn)ANR的時(shí)候所解決問題的情況和解決步驟。
從日志第一行開始看,可以看到發(fā)生錯(cuò)誤的應(yīng)用包名和類名,這里是ANR in com.xxxx.performance (com.xxxx.performance/.view.home.activity.MainActivity)。接著看到進(jìn)程號(hào)PID為7398。發(fā)生ANR的Reason是Input dispatching timed out就是上面提到的第一種。再往下就是活躍進(jìn)程的CPU占用率日志。
124% 7398/com.xxxx.performance82% 819/system_server10% 996/com.android.systemui4.6% 2215/com.android.phone...... 復(fù)制代碼光看Logcat中的日志只能看到這些信息,大概知道是在MainActivity出現(xiàn)了問題,但還是不能清楚的定位到發(fā)生ANR的代碼行,想要獲得進(jìn)一步的錯(cuò)誤信息只能通過查看ANR過程中生成的堆棧信息文件traces.txt了。
traces.txt文件位置在/data/anr/目錄下,可以通過以下adb命令將其拷貝到sd卡目錄下獲取查看。
adb shell cat /data/anr/traces.txt >/mnt/sdcard/traces.txt exit 復(fù)制代碼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/0x8ckernel: futex_wait_queue_me+0xcc/0x158kernel: futex_wait+0x120/0x20ckernel: do_futex+0x184/0xa48kernel: SyS_futex+0x88/0x19ckernel: cpu_switch_to+0x48/0x4cnative: #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)............ 復(fù)制代碼還是從頭開始看,來看每個(gè)字段對(duì)應(yīng)的含義:
線程名:main
線程優(yōu)先級(jí):prio=5
線程鎖ID: tid=1
線程狀態(tài):Native 線程組名稱:group="main"
線程被掛起的次數(shù):sCount=1
線程被調(diào)試器掛起的次數(shù):dsCount=0
線程的java的對(duì)象地址:obj=0x75ceafb8
線程本身的Native對(duì)象地址:self=0x55933ae7e0
線程調(diào)度信息:
Linux系統(tǒng)中內(nèi)核線程ID: sysTid=7398與主線程的進(jìn)程號(hào)相同
線程調(diào)度優(yōu)先級(jí):nice=0
線程調(diào)度組:cgrp=default
線程調(diào)度策略和優(yōu)先級(jí):sched=0/0
線程處理函數(shù)地址:handle=0x7f7ddae0f0
線程的上下文信息:
線程調(diào)度狀態(tài):state=S
線程在CPU中的執(zhí)行時(shí)間、線程等待時(shí)間、線程執(zhí)行的時(shí)間片長(zhǎng)度:schedstat=(101485399944 3411372871 31344 )
線程在用戶態(tài)中的調(diào)度時(shí)間值:utm=9936
線程在內(nèi)核態(tài)中的調(diào)度時(shí)間值:stm=212
最后執(zhí)行這個(gè)線程的CPU核序號(hào):core=1
線程的堆棧信息:
堆棧地址和大小:stack=0x7fc8d40000-0x7fc8d42000 stackSize=8MB
最后看到堆棧信息里的這一行:
這里就看清楚了是在AttendanceCheckInFragment中的887行出現(xiàn)的問題,再到對(duì)應(yīng)代碼行中就很容易發(fā)現(xiàn)ANR的原因了。
5、ANR的相關(guān)問題
- 在Activity的onCreate方法里調(diào)用sleep方法會(huì)發(fā)生ANR嗎?
以前一直認(rèn)為在主線程做了耗時(shí)操作就會(huì)發(fā)生ANR,那么真的是這樣嗎?在Activity的onCreate方法里調(diào)用Thread.sleep(60 * 1000)讓主線程sleep60秒,會(huì)導(dǎo)致應(yīng)用程序ANR嗎?寫個(gè)Demo測(cè)試一下。
public class MainActivity extends AppCompatActivity {@Overrideprotected 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();}} } 復(fù)制代碼如上代碼,運(yùn)行程序,結(jié)果應(yīng)用沒有發(fā)生ANR,在sleep了60秒后正常打印日志。
再次運(yùn)行程序,這回在程序運(yùn)行后按下返回鍵查看現(xiàn)象: 這次果然就ANR了。通過這個(gè)例子,顯而易見的得到了這個(gè)問題的正確答案。在Activity的onCreate方法里調(diào)用sleep方法或者說做耗時(shí)操作,不一定會(huì)產(chǎn)生ANR。其實(shí)從ANR本身意為應(yīng)用程序沒有響應(yīng),同時(shí)根據(jù)上面總結(jié)的ANR原因就可以看出,耗時(shí)操作本身是不會(huì)產(chǎn)生ANR的,導(dǎo)致ANR的根本還是應(yīng)用程序無法在一定時(shí)間內(nèi)響應(yīng)用戶的操作。所以因?yàn)橹骶€程被耗時(shí)操作占用了,主線成程無法對(duì)下一個(gè)操作進(jìn)行響應(yīng)才會(huì)ANR,沒有需要響應(yīng)的操作自然就不會(huì)產(chǎn)生ANR,或者應(yīng)該這樣說:主線程做耗時(shí)操作,非常容易引發(fā)ANR。6、總結(jié)
- 光在主線程做耗時(shí)操作不會(huì)產(chǎn)生ANR,超時(shí)響應(yīng)用戶操作才會(huì)產(chǎn)生ANR。
- ANR的定位方法主要是根據(jù)Logcat中日志和ANR過程中生成的堆棧信息文件traces.txt。
- 解決問題不如預(yù)防問題,寫代碼的時(shí)候要注意預(yù)防產(chǎn)生ANR。
- 預(yù)防ANR的產(chǎn)生不光是在Activity中注意要把耗時(shí)操作放到子線程中去,還要注意在使用其他三個(gè)組件時(shí),在其生命周期中同樣不能做太耗時(shí)的操作。另外在使用多線程時(shí)候要注意同步和死鎖的情況,一旦產(chǎn)生死鎖主線程同樣會(huì)引發(fā)ANR。
轉(zhuǎn)載于:https://juejin.im/post/5d171455f265da1b61500d17
總結(jié)
以上是生活随笔為你收集整理的Android进阶知识:ANR的定位与解决的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第九章 结构体与共用体
- 下一篇: UA池和IP代理池使用