您好,登錄后才能下訂單哦!
本篇內容介紹了“Android ANR的原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
ANR全稱:Application Not Responding,也就是應用程序無響應。
Android系統中,ActivityManagerService(簡稱AMS)和WindowManagerService(簡稱WMS)會檢測App的響應時間,如果App在特定時間無法相應屏幕觸摸或鍵盤輸入時間,或者特定事件沒有處理完畢,就會出現ANR。
以下四個條件都可以造成ANR發生:
InputDispatching Timeout:5秒內無法響應屏幕觸摸事件或鍵盤輸入事件
BroadcastQueue Timeout :在執行前臺廣播(BroadcastReceiver)的onReceive()函數時10秒沒有處理完成,后臺為60秒。
Service Timeout :前臺服務20秒內,后臺服務在200秒內沒有執行完畢。
ContentProvider Timeout :ContentProvider的publish在10s內沒進行完。
盡量避免在主線程(UI線程)中作耗時操作。
那么耗時操作就放在子線程中。
這里使用的是號稱Google親兒子的Google Pixel xl(Android 8.0系統)做的測試,生成一個按鈕跳轉到ANRTestActivity
,在后者的onCreate()
中主線程休眠20秒:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_anr_test); // 這是Android提供線程休眠函數,與Thread.sleep()最大的區別是 // 該使用該函數不會拋出InterruptedException異常。 SystemClock.sleep(20 * 1000); }
在進入ANRTestActivity
后黑屏一段時間,大概有七八秒,終于彈出了ANR異常。
剛才產生ANR后,看下Log:
可以看到logcat清晰地記錄了ANR發生的時間,以及線程的tid和一句話概括原因:WaitingInMainSignalCatcherLoop
,大概意思為主線程等待異常。
最后一句The application may be doing too much work on its main thread.
告知可能在主線程做了太多的工作。
剛才的log有第二句Wrote stack traces to '/data/anr/traces.txt'
,說明ANR異常已經輸出到traces.txt
文件,使用adb命令把這個文件從手機里導出來:
1.cd到adb.exe
所在的目錄,也就是Android SDK的platform-tools
目錄,例如:
cd D:\Android\AndroidSdk\platform-tools
此外,除了Windows的cmd以外,還可以使用AndroidStudio的Terminal來輸入adb命令。
2.到指定目錄后執行以下adb命令導出traces.txt
文件:
adb pull /data/anr/traces.txt
traces.txt
默認會被導出到Android SDK的\platform-tools目錄。一般來說traces.txt
文件記錄的東西會比較多,分析的時候需要有針對性地去找相關記錄。
----- pid 23346 at 2017-11-07 11:33:57 ----- ----> 進程id和ANR產生時間 Cmd line: com.sky.myjavatest Build fingerprint: 'google/marlin/marlin:8.0.0/OPR3.170623.007/4286350:user/release-keys' ABI: 'arm64' Build type: optimized Zygote loaded classes=4681 post zygote classes=106 Intern table: 42675 strong; 137 weak JNI: CheckJNI is on; globals=526 (plus 22 weak) Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so /system/lib64/libjavacrypto.so /system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libsoundpool.so /system/lib64/libwebviewchromium_loader.so libjavacore.so libopenjdk.so (9) Heap: 22% free, 1478KB/1896KB; 21881 objects ----> 內存使用情況 ... "main" prio=5 tid=1 Sleeping ----> 原因為Sleeping | group="main" sCount=1 dsCount=0 flags=1 obj=0x733d0670 self=0x74a4abea00 | sysTid=23346 nice=-10 cgrp=default sched=0/0 handle=0x74a91ab9b0 | state=S schedstat=( 391462128 82838177 354 ) utm=33 stm=4 core=3 HZ=100 | stack=0x7fe6fac000-0x7fe6fae000 stackSize=8MB | held mutexes= at java.lang.Thread.sleep(Native method) - sleeping on <0x053fd2c2> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:373) - locked <0x053fd2c2> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:314) at android.os.SystemClock.sleep(SystemClock.java:122) at com.sky.myjavatest.ANRTestActivity.onCreate(ANRTestActivity.java:20) ----> 產生ANR的包名以及具體行數 at android.app.Activity.performCreate(Activity.java:6975) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) at android.app.ActivityThread.-wrap11(ActivityThread.java:-1) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
在文件中使用 ctrl + F 查找包名可以快速定位相關代碼。
通過上方log可以看出相關問題:
進程id和包名:pid 23346 com.sky.myjavatest
造成ANR的原因:Sleeping
造成ANR的具體行數:ANRTestActivity.java:
20類的第20行
特別注意:產生新的ANR,原來的 traces.txt 文件會被覆蓋。
通過JDK提供的命令可以幫助分析和調試Java應用,命令為:
jstack {pid}
其中pid可以通過jps命令獲得,jps命令會列出當前系統中運行的所有Java虛擬機進程,比如
7266 Test 7267 Jps
使用DDMS——Update Threads工具
閱讀Update Threads的輸出
上面例子只是由于簡單的主線程耗時操作造成的ANR,造成ANR的原因還有很多:
主線程阻塞或主線程數據讀取
解決辦法:避免死鎖的出現,使用子線程來處理耗時操作或阻塞任務。盡量避免在主線程query provider、不要濫用SharePreferenceS
CPU滿負荷,I/O阻塞
解決辦法:文件讀寫或數據庫操作放在子線程異步操作。
內存不足
解決辦法:AndroidManifest.xml
文件<applicatiion>中可以設置 android:largeHeap="true"
,以此增大App使用內存。不過不建議使用此法,從根本上防止內存泄漏,優化內存使用才是正道。
各大組件ANR
各大組件生命周期中也應避免耗時操作,注意BroadcastReciever的onRecieve()、后臺Service和ContentProvider也不要執行太長時間的任務。
Service Timeout是位于"ActivityManager"線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG
消息時觸發。
4.1.1 發送延時消息
Service進程attach到system_server進程的過程中會調用realStartServiceLocked
,緊接著mAm.mHandler.sendMessageAtTime()
來發送一個延時消息,延時的時常是定義好的,如前臺Service的20秒。ActivityManager線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG
消息時會觸發。
AS.realStartServiceLocked
ActiveServices.java
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... //發送delay消息(SERVICE_TIMEOUT_MSG) bumpServiceExecutingLocked(r, execInFg, "create"); try { ... //最終執行服務的onCreate()方法 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { ... } }
AS.bumpServiceExecutingLocked
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { ... scheduleServiceTimeoutLocked(r.app); } void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } long now = SystemClock.uptimeMillis(); Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; //當超時后仍沒有remove該SERVICE_TIMEOUT_MSG消息,則執行service Timeout流程 mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT)); }
4.1.2 進入目標進程的主線程創建Service
經過Binder等層層調用進入目標進程的主線程 handleCreateService(CreateServiceData data)。
ActivityThread.java
private void handleCreateService(CreateServiceData data) { ... java.lang.ClassLoader cl = packageInfo.getClassLoader(); Service service = (Service) cl.loadClass(data.info.name).newInstance(); ... try { //創建ContextImpl對象 ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); //創建Application對象 Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); //調用服務onCreate()方法 service.onCreate(); //取消AMS.MainHandler的延時消息 ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (Exception e) { ... } }
這個方法中會創建目標服務對象,以及回調常用的Service的onCreate()
方法,緊接著通過serviceDoneExecuting()
回到system_server執行取消AMS.MainHandler的延時消息。
4.1.3 回到system_server執行取消AMS.MainHandler的延時消息
AS.serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) { ... if (r.executeNesting <= 0) { if (r.app != null) { r.app.execServicesFg = false; r.app.executingServices.remove(r); if (r.app.executingServices.size() == 0) { //當前服務所在進程中沒有正在執行的service mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); ... } ... }
此方法中Service邏輯處理完成則移除之前延時的消息SERVICE_TIMEOUT_MSG
。如果沒有執行完畢不調用這個方法,則超時后會發出SERVICE_TIMEOUT_MSG
來告知ANR發生。
BroadcastReceiver Timeout是位于"ActivityManager"線程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG
消息時觸發。
4.2.1 處理廣播函數 processNextBroadcast() 中 broadcastTimeoutLocked(false) 發送延時消息
廣播處理順序為先處理并行廣播,再處理當前有序廣播。
final void processNextBroadcast(boolean fromMsg) { synchronized(mService) { ... // 處理當前有序廣播 do { r = mOrderedBroadcasts.get(0); //獲取所有該廣播所有的接收者 int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; if (mService.mProcessesReady && r.dispatchTime > 0) { long now = SystemClock.uptimeMillis(); if ((numReceivers > 0) && (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { //step 1\. 發送延時消息,這個函數處理了很多事情,比如廣播處理超時結束廣播 broadcastTimeoutLocked(false); ... } } if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { if (r.resultTo != null) { //2\. 處理廣播消息消息 performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId); r.resultTo = null; } //3\. 取消廣播超時ANR消息 cancelBroadcastTimeoutLocked(); } } while (r == null); ... // 獲取下條有序廣播 r.receiverTime = SystemClock.uptimeMillis(); if (!mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mTimeoutPeriod; //設置廣播超時 setBroadcastTimeoutLocked(timeoutTime); } ... } }
上文的step 1. broadcastTimeoutLocked(false)函數:記錄時間信息并調用函數設置發送延時消息
final void broadcastTimeoutLocked(boolean fromMsg) { ... long now = SystemClock.uptimeMillis(); if (fromMsg) { if (mService.mDidDexOpt) { // Delay timeouts until dexopt finishes. mService.mDidDexOpt = false; long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod; setBroadcastTimeoutLocked(timeoutTime); return; } if (!mService.mProcessesReady) { return; } long timeoutTime = r.receiverTime + mTimeoutPeriod; if (timeoutTime > now) { // step 2 setBroadcastTimeoutLocked(timeoutTime); return; } }
上文的step 2.setBroadcastTimeoutLocked函數: 設置廣播超時具體操作,同樣是發送延時消息
final void setBroadcastTimeoutLocked(long timeoutTime) { if (! mPendingBroadcastTimeoutMessage) { Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this); mHandler.sendMessageAtTime(msg, timeoutTime); mPendingBroadcastTimeoutMessage = true; } }
4.2.2 setBroadcastTimeoutLocked(long timeoutTime)函數的參數timeoutTime是當前時間加上設定好的超時時間。
也就是上文的
long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
mTimeoutPeriod 也就是前臺隊列的10s和后臺隊列的60s。
public ActivityManagerService(Context systemContext) { ... static final int BROADCAST_FG_TIMEOUT = 10 * 1000; static final int BROADCAST_BG_TIMEOUT = 60 * 1000; ... mFgBroadcastQueue = new BroadcastQueue(this, mHandler, "foreground", BROADCAST_FG_TIMEOUT, false); mBgBroadcastQueue = new BroadcastQueue(this, mHandler, "background", BROADCAST_BG_TIMEOUT, true); ... }
4.2.3 在processNextBroadcast()過程,執行完performReceiveLocked后調用cancelBroadcastTimeoutLocked
cancelBroadcastTimeoutLocked :處理廣播消息函數 processNextBroadcast() 中 performReceiveLocked() 處理廣播消息完畢則調用 cancelBroadcastTimeoutLocked() 取消超時消息。
final void cancelBroadcastTimeoutLocked() { if (mPendingBroadcastTimeoutMessage) { mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this); mPendingBroadcastTimeoutMessage = false; } }
ContentProvider Timeout是位于”ActivityManager”線程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息時觸發。
無論是四大組件或者進程等只要發生ANR,最終都會調用AMS.appNotResponding()方法。
“Android ANR的原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。