當(dāng)UI線程阻塞時間太長,應(yīng)用無響應(yīng)(ANR)錯誤便會觸發(fā)球榆。如果應(yīng)用位于前臺娃兽,系統(tǒng)還會顯示給用戶一個ANR對話框菇民,讓用戶有機會強制關(guān)閉應(yīng)用。
ANR是一個問題投储,因為應(yīng)用中負(fù)責(zé)更新UI的主線程無法處理用戶輸入事件或者繪制操作第练,從而導(dǎo)致用戶感到沮喪。
一般分三種情況:
- KeyDispatchTimeout(5 seconds):主要情況玛荞。按鍵或者觸摸事件無法在特定時間內(nèi)完成娇掏。
- BroadcastTimeout(10 seconds) :BroadcastReceiver在特定時間內(nèi)無法處理完成
- ServiceTimeout(20 seconds): Service在特定的時間內(nèi)無法處理完成(所以雖然service是后臺執(zhí)行的,但是他是運行在UI線程的勋眯,如果處理一些耗時操作婴梧,會造成ANR)
監(jiān)測和診斷問題
如果應(yīng)用已經(jīng)發(fā)布了下梢,Android vitals
可以向你警告ANR問題的發(fā)生。(PS:前提是要發(fā)布到Google Play Store上志秃,國內(nèi)如果不是面向海外的怔球,可以集成友盟SDK或者騰訊Bugly等)
Android vitals
Google Play Console的Android vitals模塊統(tǒng)計了應(yīng)用的性能相關(guān)情況,包括ANR和Crash等浮还【固常可以根據(jù)上面的統(tǒng)計來分析解決ANR和Crash問題。
診斷ANR
診斷ANR可用的常規(guī)套路:
- 在主線程中執(zhí)行IO操作
- 在主線程執(zhí)行長時間的計算
- 主線程執(zhí)行同步Binder操作訪問另一個進(jìn)程钧舌,該進(jìn)程執(zhí)行很長時間再返回
- 非主線程持有l(wèi)ock担汤,導(dǎo)致主線程等待lock超時
- 主線程和另一個線程發(fā)生死鎖,可以是位于當(dāng)前進(jìn)程或者通過Binder調(diào)用洼冻。
用下面的技巧幫助你分析到底是上面的哪種情況引起了ANR崭歧。
Strict mode
使用StrictMode
幫你找到主線程哪里調(diào)用了IO操作。這個模式打開后撞牢,可以盡可能幫助你找到主線程中的磁盤訪問和網(wǎng)絡(luò)訪問操作率碾,網(wǎng)絡(luò)訪問操作是肯定需要放到子線程中執(zhí)行的。而磁盤操作的話屋彪,通常都會執(zhí)行很快所宰,當(dāng)然能做到子線程中最好。官方文檔也說了畜挥,不要強迫修復(fù)StrictMode
幫你找到的所有內(nèi)容仔粥,特別是,在正常的Activity生命周期中蟹但,許多磁盤訪問操作是需要的躯泰。
But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle
調(diào)試模式下打開,但是發(fā)布狀態(tài)不允許打開华糖。
可以在Activity或者Appliction的onCreat()方法中打開:
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
打開后臺ANR彈框
設(shè)備的開發(fā)者選項中可以打開“顯示所有ANR”麦向。默認(rèn)Android只對于前臺應(yīng)用發(fā)生ANR時彈框,打開后缅阳,后臺應(yīng)用發(fā)生ANR也會彈框磕蛇。
TraceView
使用TraceView分析應(yīng)用運行時你根據(jù)用例情況在卡頓前后捕捉的方法調(diào)用trace文件。trace文件可以通過代碼調(diào)用生成十办,或者通過Android Studio捕捉秀撇。
Debug.startMethodTracing("hellotrace"); //開始 trace,保存文件到 "/sdcard/hellotrace.trace"
// ...
Debug.stopMethodTracing(); //結(jié)束
使用adb命令將trace
文件導(dǎo)出到電腦向族,然后放到DDMS中打開分析
adb pull /sdcard/hellotrace.trace /tmp
拉取traces文件
當(dāng)ANR發(fā)生時呵燕,Android系統(tǒng)會將一些trace信息存儲到設(shè)備的/data/anr/traces.txt文件中。你可以利用adb命令將其拉取出來分析(前提是要root件相?不記得了)再扭。
對于模擬器氧苍,簡單快速查看:
adb root
adb shell
cat /data/anr/traces.txt
還可以用bugreport命令導(dǎo)出。
解決問題
主線程執(zhí)行慢代碼
將耗時操作異步執(zhí)行泛范。
主線程執(zhí)行IO操作
建議將所有IO操作放到子線程執(zhí)行让虐。
鎖爭用
工作線程持有主線程需要獲取某個資源的鎖又不能及時釋放的情況。
通常發(fā)生ANR時罢荡,主線程處于Monitor或者BLOCKED狀態(tài)赡突。例子:
@Override
public void onClick(View v) {
// The worker thread holds a lock on lockedResource
new LockTask().execute(data);
synchronized (lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0]);
}
}
}
上述代碼,主線程需要獲取lockedResource
鎖区赵,而該鎖被LockTask持有惭缰,其sort
方法為耗時操作,導(dǎo)致不能及時釋放鎖笼才,從而引發(fā)主線程阻塞超時漱受,導(dǎo)致ANR。
另外一個例子骡送,主線程等待子線程執(zhí)行結(jié)果超時:
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// Wait for this worker thread’s notification
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// Finished, notify the main thread
notify();
}
}
}
這些情況需要評估鎖耗時昂羡,保證鎖盡可能占有最少的時間∷猓或者移除鎖紧憾。
死鎖
盡可能避免死鎖。
執(zhí)行緩慢的Broadcast Receiver
當(dāng)應(yīng)用花費了太長時間處理廣播消息時候也會導(dǎo)致ANR發(fā)生昌渤。
下面的情況會導(dǎo)致ANR發(fā)生:
- 廣播接收者沒能及時執(zhí)行完成onReceive()方法(通常10s)
- 廣播接收者調(diào)用了
goAsync()
方法,但是沒有調(diào)用PendingResult
對象的finish()
方法憔四。
如果onReceive方法中要執(zhí)行耗時操作膀息,可以將任務(wù)放到IntentService
中執(zhí)行。
@Override
public void onReceive(Context context, Intent intent) {
// The task now runs on a worker thread.
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
或者了赵,調(diào)用BroadcastReceiver告訴系統(tǒng)潜支,我需要更多時間來處理消息。你處理完成之后柿汛,必須調(diào)用PendingResult
對象的finish
方法冗酿。
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// This is a long-running operation
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
但是使用goAsync()
仍然可能導(dǎo)致ANR,你必須在10s之內(nèi)完成操作
參考資料:https://developer.android.com/topic/performance/vitals/anr.html