Android性能調(diào)優(yōu)之ANR

對一款A(yù)ndroid應(yīng)用來說,用戶體驗是至高無上的原則腥泥。如果應(yīng)用上手的體驗特別差,點點這點點那就出現(xiàn)各種問題讳嘱,用戶就會執(zhí)行最簡單的一個操作——卸載你的應(yīng)用栋烤。用戶體驗是一個非常大的概念,其中最直接粗暴的表現(xiàn)有兩個:崩潰和ANR股淡。這兩個問題是開發(fā)人員最不想看到的兩個現(xiàn)象身隐。對于崩潰來說,一般原因比較明顯唯灵,Android會在logcat中以紅色的字體打印出具體導致崩潰的代碼贾铝。對于線上應(yīng)用, 也有各種性能監(jiān)控工具可以幫我們分析埠帕。但是ANR的原因就比較難以定位忌傻。
ANR(Application Not Responding),即應(yīng)用程序無響應(yīng)搞监,相信很多安卓用戶都碰到過那個然并卵的對話框:


ANR.png

出現(xiàn)這個對話框,會讓用戶覺得很沮喪镰矿,輕則關(guān)閉琐驴,重則卸載。用戶就是這么一點一點流失的啊秤标。

產(chǎn)生ANR的原因

那么怎么避免出現(xiàn)ANR呢绝淡?首先要知道ANR產(chǎn)生的原因。
Android系統(tǒng)中產(chǎn)生ANR的原因大致分為三類:

  • Service ANR苍姜,前臺進程中的Service生命周期超過20秒牢酵,后臺進程中的Service生命周期超過200秒時;
  • Broadcast ANR衙猪,前臺的串行廣播消息中onReceive()方法執(zhí)行超過10秒馍乙,后臺的串行廣播消息中onReceive()方法執(zhí)行超過60秒時;
  • Input ANR垫释,輸入時間在5秒內(nèi)無法響應(yīng)時丝格;

Android系統(tǒng)采用不同的監(jiān)測機制來監(jiān)測ANR,Service和Broadcast都是由AMS調(diào)度棵譬,利用Handler和Looper显蝌,設(shè)計了一個TIMEOUT消息交由AMS線程來處理,整個超時機制的實現(xiàn)都是在Java層订咸; InputEvent由InputDispatcher調(diào)度曼尊,待處理的輸入事件都會進入隊列中等待,設(shè)計了一個等待超時的判斷脏嚷,超時機制的實現(xiàn)在Native層骆撇。在這篇文章中做了很詳細的介紹。
具體到代碼層面父叙,是什么操作導致了以上情況的產(chǎn)生呢艾船?最重要的原因就是在主線程里執(zhí)行了太多的阻塞耗時操作葵腹。那么要避免ANR也就需要堅持一個原則:不要在主線程里做繁重的耗時操作。悲傷的是屿岂,我們的主工程里大量的數(shù)據(jù)庫操作都執(zhí)行在主線程里践宴,這是接下來(不是本文)需要深入探討的一個問題。

哪些操作時在主線程執(zhí)行的爷怀?

要做到不在主線程里執(zhí)行耗時操作阻肩,首先要知道哪些地方執(zhí)行的代碼是在主線程執(zhí)行的。

  • Activity和Fragment里的所有生命周期回調(diào)都是執(zhí)行在主線程运授;
  • Service默認是執(zhí)行在主線程的烤惊;
  • BroadcastReceiver的onReceive()方法是執(zhí)行在主線程的;
  • 沒有使用子線程looper的handler的handleMessage()方法和post(runnable)是執(zhí)行在主線程的吁朦;
  • View的post(Runnbale)是執(zhí)行在主線程的柒室;
    因此,在這些地方執(zhí)行操作時逗宜,要格外小心雄右,最保險的方法就是開啟一個子線程,把耗時操作都扔到子線程里做纺讲,具體創(chuàng)建一個異步操作擂仍,方法五花八門,這里不展開講了熬甚。

怎么分析ANR逢渔?

即使寫代碼的時候特別小心,也無法保證你的應(yīng)用不發(fā)生ANR乡括,因為不同的設(shè)備配置和性能上的差異肃廓,使得在A機器上4秒鐘執(zhí)行完的操作,在B機器上可能會花費超過4秒的時間诲泌,甚至是由于手機上的其他進程占用cpu導致cpu無法響應(yīng)當前應(yīng)用亿昏,種種原因都會導致ANR的出現(xiàn)。所以我們必須分析其產(chǎn)生的原因档礁,才能及時發(fā)現(xiàn)問題角钩,解決問題。
當ANR發(fā)生時呻澜,我們通常采用Logcat和traces.txt文件的相關(guān)信息去定位問題递礼。主要包含以下幾個方面:

  • 基本信息,進程名羹幸、進程號脊髓、包名、系統(tǒng)build號栅受、ANR類型等将硝;
  • cpu使用信息恭朗,活躍進程的cpu平均占用率、IO情況等依疼;
  • 堆棧信息痰腮;
    可以通過adb命令將traces.txt文件拉到本地:
    $adb pull data/anr/traces.txt .
    以下是病歷夾某一次ANR的日志:
----- pid 6603 at 2016-10-14 09:57:24 -----
Cmd line: com.apricotforest.dossier

JNI: CheckJNI is off; workarounds are off; pins=0; globals=412 (plus 20 weak)

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

"main" prio=5 tid=1 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 obj=0x41744e58 self=0x41664910
  | sysTid=6603 nice=-6 sched=0/0 cgrp=apps handle=1074172244
  | state=S schedstat=( 28338231507 276030844958 49092 ) utm=2490 stm=343 core=3
  at java.lang.VMThread.sleep(Native Method)
  at java.lang.Thread.sleep(Thread.java:1013)
  at java.lang.Thread.sleep(Thread.java:995)
  at com.apricotforest.dossier.persistentconnection.PersistentConnectionService$3.onClose(PersistentConnectionService.java:162)
  at com.xingshulin.persistentconnection.WebSocketManager$1.onClose(WebSocketManager.java:31)
  at de.tavendo.autobahn.WebSocketConnection.failConnection(WebSocketConnection.java:198)
  at de.tavendo.autobahn.WebSocketConnection.access$7(WebSocketConnection.java:156)
  at de.tavendo.autobahn.WebSocketConnection$1.handleMessage(WebSocketConnection.java:390)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:136)
  at android.app.ActivityThread.main(ActivityThread.java:5047)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)
  at dalvik.system.NativeStart.main(Native Method)
...

從文中可以看到以上描述的一些信息,很明顯問題發(fā)生在長連接服務(wù)的onClose()方法中律罢,該方法中執(zhí)行的操作耗時導致發(fā)生了Service的ANR膀值。
上面這個例子比較簡單,日志里已經(jīng)很明白的告訴你出錯的位置了误辑,還有一些ANR問題發(fā)生的原因比較隱蔽沧踏,需要認真的分析,比如:

Process:com.apricotforest.dossier
...
CPU usage from 3330ms to 814ms ago:
6% 178/system_server: 3.5% user + 1.4% kernel / faults: 86 minor 20 major
4.6% 2976/com.apricotforest.dossier: 0.7% user + 3.7% kernel /faults: 52 minor 19 major
0.9% 252/com.android.systemui: 0.9% user + 0% kernel
...

100%TOTAL: 5.9% user + 4.1% kernel + 89% iowait

最后一句說明CPU占用100%巾钉,已經(jīng)滿負荷翘狱,其中大部分被iowait即I/O操作占用。這時候需要仔細的分析logcat和trace.txt,同時結(jié)合google砰苍,才有可能定位到問題原因潦匈。
p.p1 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px 'Heiti SC Light'; color: #586e75}p.p2 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #586e75; min-height: 19.0px}p.p3 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #859900}p.p4 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #859900; min-height: 19.0px}span.s1 {font: 16.0px Courier}

內(nèi)存原因也可能導致ANR,例如用一張大圖片作為activity的背景师骗,trace信息可能是這樣的:

Cmdline: android.process.acore

DALVIK THREADS:
"main"prio=5 tid=3 VMWAIT
|group="main" sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8
| sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376
atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod)
atandroid.graphics.Bitmap.nativeCreate(Native Method)
atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468)
atandroid.view.View.buildDrawingCache(View.java:6324)
atandroid.view.View.getDrawingCache(View.java:6178)

...

MEMINFO in pid 1360 [android.process.acore] **
native dalvik other total
size: 17036 23111 N/A 40147
allocated: 16484 20675 N/A 37159
free: 296 2436 N/A 2732

free的內(nèi)存已經(jīng)所剩無幾。當然讨惩,這時候發(fā)生OOM的幾率也變的很大了辟癌。
病歷夾的現(xiàn)狀:ANR比較頻繁,需要重點解決荐捻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黍少,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子处面,更是在濱河造成了極大的恐慌厂置,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魂角,死亡現(xiàn)場離奇詭異昵济,居然都是意外死亡,警方通過查閱死者的電腦和手機野揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門访忿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斯稳,你說我怎么就攤上這事海铆。” “怎么了挣惰?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵卧斟,是天一觀的道長殴边。 經(jīng)常有香客問我,道長珍语,這世上最難降的妖魔是什么锤岸? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮廊酣,結(jié)果婚禮上能耻,老公的妹妹穿的比我還像新娘。我一直安慰自己亡驰,他們只是感情好晓猛,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凡辱,像睡著了一般戒职。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上透乾,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天洪燥,我揣著相機與錄音,去河邊找鬼乳乌。 笑死捧韵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的汉操。 我是一名探鬼主播再来,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磷瘤!你這毒婦竟也來了芒篷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤采缚,失蹤者是張志新(化名)和其女友劉穎针炉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扳抽,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡篡帕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贸呢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赂苗。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贮尉,靈堂內(nèi)的尸體忽然破棺而出拌滋,到底是詐尸還是另有隱情,我是刑警寧澤猜谚,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布败砂,位于F島的核電站赌渣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昌犹。R本人自食惡果不足惜坚芜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斜姥。 院中可真熱鬧鸿竖,春花似錦、人聲如沸铸敏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杈笔。三九已至闪水,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒙具,已是汗流浹背球榆。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禁筏,地道東北人持钉。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像篱昔,于是被迫代替她去往敵國和親每强。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容