Android 性能優(yōu)化13 --- ANR優(yōu)化

概念

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();
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末于未,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陡鹃,更是在濱河造成了極大的恐慌烘浦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萍鲸,死亡現(xiàn)場離奇詭異闷叉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脊阴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門握侧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嘿期,你說我怎么就攤上這事品擎。” “怎么了秽五?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵孽查,是天一觀的道長。 經(jīng)常有香客問我坦喘,道長盲再,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任瓣铣,我火速辦了婚禮答朋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棠笑。我一直安慰自己梦碗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著洪规,像睡著了一般印屁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斩例,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天雄人,我揣著相機(jī)與錄音,去河邊找鬼念赶。 笑死础钠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叉谜。 我是一名探鬼主播旗吁,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼停局!你這毒婦竟也來了很钓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤翻具,失蹤者是張志新(化名)和其女友劉穎履怯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裆泳,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡叹洲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了工禾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片运提。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闻葵,靈堂內(nèi)的尸體忽然破棺而出民泵,到底是詐尸還是另有隱情,我是刑警寧澤槽畔,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布栈妆,位于F島的核電站,受9級特大地震影響厢钧,放射性物質(zhì)發(fā)生泄漏鳞尔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一早直、第九天 我趴在偏房一處隱蔽的房頂上張望寥假。 院中可真熱鬧,春花似錦霞扬、人聲如沸糕韧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萤彩。三九已至粪滤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乒疏,已是汗流浹背额衙。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留怕吴,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓县踢,卻偏偏與公主長得像转绷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子硼啤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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