概念
ANR(Application Not responding),是指應(yīng)用程序未響應(yīng)馏艾,Android系統(tǒng)對于一些事件需要在一定的時間范圍內(nèi)完
成厕诡,如果超過預(yù)定時間能未能得到有效響應(yīng)或者響應(yīng)時間過長榨崩,都會造成ANR
在 Android 里,應(yīng)用程序的響應(yīng)性是由 Activity Manager 和 WindowManager 系統(tǒng)服務(wù)監(jiān)視的蜓耻。當(dāng)它監(jiān)測到以下
情況中的一個時,Android 就會針對特定的應(yīng)用程序顯示 ANR
場景
- Service Timeout
- BroadcastQueue Timeout
- ContentProvider Timeout
- InputDispatching Timeout
Timeout時長
- 對于前臺服務(wù)械巡,則超時為SERVICE_TIMEOUT = 20s刹淌;
- 對于后臺服務(wù),則超時為SERVICE_BACKGROUND_TIMEOUT = 200s
- 對于前臺廣播讥耗,則超時為BROADCAST_FG_TIMEOUT = 10s有勾;
- 對于后臺廣播,則超時為BROADCAST_BG_TIMEOUT = 60s;
ContentProvider超時為CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s; - InputDispatching Timeout: 輸入事件分發(fā)超時5s古程,包括按鍵和觸摸事件蔼卡。
注意事項: Input的超時機(jī)制與其他的不同,對于input來說即便某次事件執(zhí)行時間超過timeout時長挣磨,只要用
戶后續(xù)在沒有再生成輸入事件雇逞,則不會觸發(fā)ANR
如何避免 ANR?
考慮上面的 ANR 定義茁裙,讓我們來研究一下為什么它會在 Android 應(yīng)用程序里發(fā)生和如何最佳 構(gòu)建應(yīng)用程序來避免ANR塘砸。
Android 應(yīng)用程序通常是運行在一個單獨的線程(例如,main)里呜达。這意味著你的應(yīng)用程序所做的事情如果在主線程里占用了太長的時間的話谣蠢,就會引發(fā) ANR 對話框,因為你的應(yīng)用程序并沒有給自己機(jī)會來處理輸入事件或者 Intent 廣播查近。
因此眉踱,運行在主線程里的任何方法都盡可能少做事情。特別是霜威,Activity 應(yīng)該在它的關(guān)鍵生命周期方法(如 onCreate()和 onResume())里盡可能少的去做創(chuàng)建操作谈喳。潛在的耗時操作,例如網(wǎng)絡(luò)或數(shù)據(jù)庫操作戈泼,或者高耗時的計算如改變位圖尺寸婿禽,應(yīng)該在子線程里(或者以數(shù)據(jù) 庫操作為例,通過異步請求的方式)來完成大猛。然而扭倾,不是說你的主線程阻塞在那里等待子線程的完成——也不是調(diào)用 Thread.wait()或是 Thread.sleep()。替代的方法是挽绩,主線程應(yīng)該為子線程提供一個 Handler膛壹,以便完成時能夠提交給主線程。以這種方式設(shè)計你的應(yīng)用程序,將 能保證你的主線程保持對輸入的響應(yīng)性并能避免由于 5 秒輸入事件的超時引發(fā)的 ANR 對話框模聋。這種做法應(yīng)該在其它顯示 UI 的線程里效仿肩民,因為它們都受相同的超 時影響。
IntentReceiver 執(zhí)行時間的特殊限制意味著它應(yīng)該做:在后臺里做小的链方、瑣碎的工作如保存設(shè)定或者注冊一個 Notification持痰。和在主線 程里調(diào)用的其它方法一樣,應(yīng)用程序應(yīng)該避免在 BroadcastReceiver 里做耗時的操作或計算祟蚀。但不再是在子線程里這些任務(wù)(因為BroadcastReceiver 的生命周期短)工窍,替代的是,如果響應(yīng) Intent 廣播需要執(zhí)行一個耗時的動作的話暂题,應(yīng)用程序應(yīng)該啟動一個 Service移剪。順便提及一句,你也應(yīng)該避免在IntentReceiver 里啟動一個Activity薪者,因為它會創(chuàng)建一個新的畫面纵苛,并從當(dāng)前用戶正在運行的程序上搶奪焦點。如果你的應(yīng)用程序在響應(yīng)Intent 廣播時需要向用戶展示什么言津,你應(yīng)該使用 Notification Manager 來實現(xiàn)攻人。
增強響應(yīng)靈敏性一般來說,在應(yīng)用程序里悬槽,100 到 200ms 是用戶能感知阻滯的時間閾值怀吻。因此,這里有一些額外的技巧來避免 ANR初婆,并有助于讓你的應(yīng)用程序看起來有響應(yīng)性蓬坡。如果你的應(yīng)用程序為響應(yīng)用戶輸入正在后臺工作的話,可以顯示工作的進(jìn)度(ProgressBar和 ProgressDialog 對這種情況來說很有用)磅叛。特別是游戲屑咳,在子線程里做移動的計算。
如果你的應(yīng)用程序有一個耗時的初始化過程的話弊琴,考慮可以顯示一個 SplashScreen 或者快 速顯示主畫面并異步來填充這些信息兆龙。在這兩種情況下,你都應(yīng)該顯示正在進(jìn)行的進(jìn)度敲董,以 免用戶認(rèn)為應(yīng)用程序被凍結(jié)了紫皇。
ANR分析
前臺ANR發(fā)生后,系統(tǒng)會馬上去抓取現(xiàn)場的信息腋寨,用于調(diào)試分析聪铺,收集的信息 保存在data/anr/traces.txt文件
如何避免ANR發(fā)生
- 主線程盡量只做UI相關(guān)的操作,避免耗時操作,比如過度復(fù)雜的UI繪制萄窜,網(wǎng)絡(luò)操作计寇,文件IO操作;
- 避免主線程跟工作線程發(fā)生鎖的競爭脂倦,減少系統(tǒng)耗時binder的調(diào)用番宁,謹(jǐn)慎使用sharePreference,注意主線程執(zhí)行provider query操作
ANR監(jiān)控方案
- watchdog
import android.os.FileObserver;
import android.util.Log;
import androidx.annotation.Nullable;
public class ANRFileObserver extends FileObserver {
public ANRFileObserver(String path) {//data/anr/
super(path);
}
public ANRFileObserver(String path, int mask) {
super(path, mask);
}
@Override
public void onEvent(int event, @Nullable String path) {
switch (event)
{
case FileObserver.ACCESS://文件被訪問
Log.i("Zero", "ACCESS: " + path);
break;
case FileObserver.ATTRIB://文件屬性被修改赖阻,如 chmod蝶押、chown、touch 等
Log.i("Zero", "ATTRIB: " + path);
break;
case FileObserver.CLOSE_NOWRITE://不可寫文件被 close
Log.i("Zero", "CLOSE_NOWRITE: " + path);
break;
case FileObserver.CLOSE_WRITE://可寫文件被 close
Log.i("Zero", "CLOSE_WRITE: " + path);
break;
case FileObserver.CREATE://創(chuàng)建新文件
Log.i("Zero", "CREATE: " + path);
break;
case FileObserver.DELETE:// 文件被刪除火欧,如 rm
Log.i("Zero", "DELETE: " + path);
break;
case FileObserver.DELETE_SELF:// 自刪除棋电,即一個可執(zhí)行文件在執(zhí)行時刪除自己
Log.i("Zero", "DELETE_SELF: " + path);
break;
case FileObserver.MODIFY://文件被修改
Log.i("Zero", "MODIFY: " + path);
break;
case FileObserver.MOVE_SELF://自移動,即一個可執(zhí)行文件在執(zhí)行時移動自己
Log.i("Zero", "MOVE_SELF: " + path);
break;
case FileObserver.MOVED_FROM://文件被移走苇侵,如 mv
Log.i("Zero", "MOVED_FROM: " + path);
break;
case FileObserver.MOVED_TO://文件被移來赶盔,如 mv、cp
Log.i("Zero", "MOVED_TO: " + path);
break;
case FileObserver.OPEN://文件被 open
Log.i("Zero", "OPEN: " + path);
break;
default:
//CLOSE : 文件被關(guān)閉榆浓,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
//ALL_EVENTS : 包括上面的所有事件
Log.i("Zero", "DEFAULT(" + event + "): " + path);
break;
}
}
}
- fileobserver
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
public class ANRWatchDog extends Thread {
private static final String TAG = "ANR";
private int timeout = 5000;
private boolean ignoreDebugger = true;
static ANRWatchDog sWatchdog;
private Handler mainHandler = new Handler(Looper.getMainLooper());
private class ANRChecker implements Runnable {
private boolean mCompleted;
private long mStartTime;
private long executeTime = SystemClock.uptimeMillis();
@Override
public void run() {
synchronized (ANRWatchDog.this) {
mCompleted = true;
executeTime = SystemClock.uptimeMillis();
}
}
void schedule() {
mCompleted = false;
mStartTime = SystemClock.uptimeMillis();
mainHandler.postAtFrontOfQueue(this);
}
boolean isBlocked() {
return !mCompleted || executeTime - mStartTime >= 5000;
}
}
public interface ANRListener {
void onAnrHappened(String stackTraceInfo);
}
private ANRChecker anrChecker = new ANRChecker();
private ANRListener anrListener;
public void addANRListener(ANRListener listener){
this.anrListener = listener;
}
public static ANRWatchDog getInstance(){
if(sWatchdog == null){
sWatchdog = new ANRWatchDog();
}
return sWatchdog;
}
private ANRWatchDog(){
super("ANR-WatchDog-Thread");
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 設(shè)置為后臺線程
while(true){
while (!isInterrupted()) {
synchronized (this) {
anrChecker.schedule();
long waitTime = timeout;
long start = SystemClock.uptimeMillis();
while (waitTime > 0) {
try {
wait(waitTime);
} catch (InterruptedException e) {
Log.w(TAG, e.toString());
}
waitTime = timeout - (SystemClock.uptimeMillis() - start);
}
if (!anrChecker.isBlocked()) {
continue;
}
}
if (!ignoreDebugger && Debug.isDebuggerConnected()) {
continue;
}
String stackTraceInfo = getStackTraceInfo();
if (anrListener != null) {
anrListener.onAnrHappened(stackTraceInfo);
}
}
anrListener = null;
}
}
private String getStackTraceInfo() {
StringBuilder stringBuilder = new StringBuilder();
for (StackTraceElement stackTraceElement : Looper.getMainLooper().getThread().getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append("\r\n");
}
return stringBuilder.toString();
}
}