溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android?ANR的原理是什么

發(fā)布時間:2021-11-24 17:43:54 來源:億速云 閱讀:169 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Android ANR的原理是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

    一、ANR說明和原因

    1.1 簡介

    ANR全稱:Application Not Responding,也就是應(yīng)用程序無響應(yīng)。

    1.2 原因

    Android系統(tǒng)中,ActivityManagerService(簡稱AMS)和WindowManagerService(簡稱WMS)會檢測App的響應(yīng)時間,如果App在特定時間無法相應(yīng)屏幕觸摸或鍵盤輸入時間,或者特定事件沒有處理完畢,就會出現(xiàn)ANR。

    以下四個條件都可以造成ANR發(fā)生:

    • InputDispatching Timeout:5秒內(nèi)無法響應(yīng)屏幕觸摸事件或鍵盤輸入事件

    • BroadcastQueue Timeout :在執(zhí)行前臺廣播(BroadcastReceiver)的onReceive()函數(shù)時10秒沒有處理完成,后臺為60秒。

    • Service Timeout :前臺服務(wù)20秒內(nèi),后臺服務(wù)在200秒內(nèi)沒有執(zhí)行完畢。

    • ContentProvider Timeout :ContentProvider的publish在10s內(nèi)沒進行完。

    1.3 避免

    盡量避免在主線程(UI線程)中作耗時操作。

    那么耗時操作就放在子線程中。

    二、ANR分析辦法

    2.1 ANR重現(xiàn)

    這里使用的是號稱Google親兒子的Google Pixel xl(Android 8.0系統(tǒng))做的測試,生成一個按鈕跳轉(zhuǎn)到ANRTestActivity,在后者的onCreate()中主線程休眠20秒:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_anr_test);
        // 這是Android提供線程休眠函數(shù),與Thread.sleep()最大的區(qū)別是
        // 該使用該函數(shù)不會拋出InterruptedException異常。
        SystemClock.sleep(20 * 1000);
    }

    在進入ANRTestActivity后黑屏一段時間,大概有七八秒,終于彈出了ANR異常。

    Android?ANR的原理是什么

    2.2 ANR分析辦法一:Log

    剛才產(chǎn)生ANR后,看下Log:

    Android?ANR的原理是什么

    可以看到logcat清晰地記錄了ANR發(fā)生的時間,以及線程的tid和一句話概括原因:WaitingInMainSignalCatcherLoop,大概意思為主線程等待異常。

    最后一句The application may be doing too much work on its main thread.告知可能在主線程做了太多的工作。

    2.3 ANR分析辦法二:traces.txt

    剛才的log有第二句Wrote stack traces to '/data/anr/traces.txt',說明ANR異常已經(jīng)輸出到traces.txt文件,使用adb命令把這個文件從手機里導(dǎo)出來:

    1.cd到adb.exe所在的目錄,也就是Android SDK的platform-tools目錄,例如:

    cd D:\Android\AndroidSdk\platform-tools

    此外,除了Windows的cmd以外,還可以使用AndroidStudio的Terminal來輸入adb命令。

    2.到指定目錄后執(zhí)行以下adb命令導(dǎo)出traces.txt文件:

    adb pull /data/anr/traces.txt

    traces.txt默認會被導(dǎo)出到Android SDK的\platform-tools目錄。一般來說traces.txt文件記錄的東西會比較多,分析的時候需要有針對性地去找相關(guān)記錄。

    ----- pid 23346 at 2017-11-07 11:33:57 -----  ----> 進程id和ANR產(chǎn)生時間
    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    ----> 內(nèi)存使用情況
    
    ...
    
    "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) ----> 產(chǎn)生ANR的包名以及具體行數(shù)
      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 查找包名可以快速定位相關(guān)代碼。

    • 通過上方log可以看出相關(guān)問題:

    • 進程id和包名:pid 23346 com.sky.myjavatest

    • 造成ANR的原因:Sleeping

    • 造成ANR的具體行數(shù):ANRTestActivity.java:20類的第20行

    特別注意:產(chǎn)生新的ANR,原來的 traces.txt 文件會被覆蓋。

    2.4 ANR分析辦法三:Java線程調(diào)用分析

    通過JDK提供的命令可以幫助分析和調(diào)試Java應(yīng)用,命令為:

    jstack {pid}

    其中pid可以通過jps命令獲得,jps命令會列出當前系統(tǒng)中運行的所有Java虛擬機進程,比如

    7266 Test
    7267 Jps

    2.5 ANR分析辦法四:DDMS分析ANR問題

    • 使用DDMS——Update Threads工具

    • 閱讀Update Threads的輸出

    三、造成ANR的原因及解決辦法

    上面例子只是由于簡單的主線程耗時操作造成的ANR,造成ANR的原因還有很多:

    主線程阻塞或主線程數(shù)據(jù)讀取

    解決辦法:避免死鎖的出現(xiàn),使用子線程來處理耗時操作或阻塞任務(wù)。盡量避免在主線程query provider、不要濫用SharePreferenceS

    CPU滿負荷,I/O阻塞

    解決辦法:文件讀寫或數(shù)據(jù)庫操作放在子線程異步操作。

    內(nèi)存不足

    解決辦法:AndroidManifest.xml文件<applicatiion>中可以設(shè)置 android:largeHeap="true",以此增大App使用內(nèi)存。不過不建議使用此法,從根本上防止內(nèi)存泄漏,優(yōu)化內(nèi)存使用才是正道。

    各大組件ANR

    各大組件生命周期中也應(yīng)避免耗時操作,注意BroadcastReciever的onRecieve()、后臺Service和ContentProvider也不要執(zhí)行太長時間的任務(wù)。

    四、ANR源碼分析

    4.1 Service造成的Service Timeout

    Service Timeout是位于"ActivityManager"線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息時觸發(fā)。

    4.1.1 發(fā)送延時消息

    Service進程attach到system_server進程的過程中會調(diào)用realStartServiceLocked,緊接著mAm.mHandler.sendMessageAtTime()來發(fā)送一個延時消息,延時的時常是定義好的,如前臺Service的20秒。ActivityManager線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息時會觸發(fā)。

    AS.realStartServiceLocked

    ActiveServices.java

    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
        ...
        //發(fā)送delay消息(SERVICE_TIMEOUT_MSG)
        bumpServiceExecutingLocked(r, execInFg, "create");
        try {
            ...
            //最終執(zhí)行服務(wù)的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消息,則執(zhí)行service Timeout流程
        mAm.mHandler.sendMessageAtTime(msg,
            proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
    }

    4.1.2 進入目標進程的主線程創(chuàng)建Service

    經(jīng)過Binder等層層調(diào)用進入目標進程的主線程 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 {
                //創(chuàng)建ContextImpl對象
                ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
                context.setOuterContext(service);
                //創(chuàng)建Application對象
                Application app = packageInfo.makeApplication(false, mInstrumentation);
                service.attach(context, this, data.info.name, data.token, app,
                        ActivityManagerNative.getDefault());
                //調(diào)用服務(wù)onCreate()方法 
                service.onCreate();
    
                //取消AMS.MainHandler的延時消息
                ActivityManagerNative.getDefault().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (Exception e) {
                ...
            }
        }

    這個方法中會創(chuàng)建目標服務(wù)對象,以及回調(diào)常用的Service的onCreate()方法,緊接著通過serviceDoneExecuting()回到system_server執(zhí)行取消AMS.MainHandler的延時消息。

    4.1.3 回到system_server執(zhí)行取消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) {
                    //當前服務(wù)所在進程中沒有正在執(zhí)行的service
                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
            ...
        }
        ...
    }

    此方法中Service邏輯處理完成則移除之前延時的消息SERVICE_TIMEOUT_MSG。如果沒有執(zhí)行完畢不調(diào)用這個方法,則超時后會發(fā)出SERVICE_TIMEOUT_MSG來告知ANR發(fā)生。

    4.2 BroadcastReceiver造成的BroadcastQueue Timeout

    BroadcastReceiver Timeout是位于"ActivityManager"線程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息時觸發(fā)。

    4.2.1 處理廣播函數(shù) processNextBroadcast() 中 broadcastTimeoutLocked(false) 發(fā)送延時消息

    廣播處理順序為先處理并行廣播,再處理當前有序廣播。

    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\. 發(fā)送延時消息,這個函數(shù)處理了很多事情,比如廣播處理超時結(jié)束廣播
                        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;
                //設(shè)置廣播超時
                setBroadcastTimeoutLocked(timeoutTime);
            }
            ...
        }
    }

    上文的step 1. broadcastTimeoutLocked(false)函數(shù):記錄時間信息并調(diào)用函數(shù)設(shè)置發(fā)送延時消息

    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函數(shù): 設(shè)置廣播超時具體操作,同樣是發(fā)送延時消息

    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)函數(shù)的參數(shù)timeoutTime是當前時間加上設(shè)定好的超時時間。

    也就是上文的

    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()過程,執(zhí)行完performReceiveLocked后調(diào)用cancelBroadcastTimeoutLocked

    cancelBroadcastTimeoutLocked :處理廣播消息函數(shù) processNextBroadcast() 中 performReceiveLocked() 處理廣播消息完畢則調(diào)用 cancelBroadcastTimeoutLocked() 取消超時消息。

    final void cancelBroadcastTimeoutLocked() {
        if (mPendingBroadcastTimeoutMessage) {
            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
            mPendingBroadcastTimeoutMessage = false;
        }
    }

    4.3 ContentProvider的ContentProvider Timeout

    ContentProvider Timeout是位于”ActivityManager”線程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息時觸發(fā)。

    五、Android ANR的信息收集

    無論是四大組件或者進程等只要發(fā)生ANR,最終都會調(diào)用AMS.appNotResponding()方法。

    “Android ANR的原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI