談談應用 ANR 之 Service 超時

1. 核心源碼

關鍵類 路徑(/frameworks/base/)
ActiveServices.java services/core/java/com/android/server/am/ActiveServices.java
ActivityManagerService.java services/core/java/com/android/server/am/ActivityManagerService.java
AppErrors.java services/core/java/com/android/server/am/AppErrors.java

2. ANR 基礎認知

2.1 ANR 是什麼?

ANR(Application Not Responding),應用程序無響應,簡單一個定義,卻涵蓋了很多 Android 系統的設計思想。

首先,ANR 屬於應用程序的範疇,這不同於 SNR(System Not Respoding),SNR 反映的問題是系統進程(system_server)失去了響應能力,而 ANR 明確將問題圈定在應用程序。SNR 由 Watchdog 機制保證,ANR 由消息處理機制保證,Android 在系統層實現了一套精密的機制來發現 ANR,核心原理是消息調度超時處理

其次,ANR 機制主體實現在系統層。所有與 ANR 相關的消息,都會經過系統進程(system_server)調度,然後派發到應用進程完成對消息的實際處理,同時,系統進程設計了不同的超時限制來跟蹤消息的處理。一旦應用程序處理消息不當,超時限制就起作用了,它收集一些系統狀態,例如:CPU/IO使用情況、進程函數調用棧,並且報告用戶有進程無響應了(ANR 對話框)

然後,ANR 問題本質是一個性能問題。ANR 機制實際上對應用程序主線程的限制,要求主線程在限定的時間內處理完一些最常見的操作(啓動服務、處理廣播、處理輸入),如果處理超時,則認爲主線程已經失去了響應其他操作的能力。主線程中的耗時操作,例如:密集CPU運算、大量IO、複雜界面佈局等,都會降低應用程序的響應能力。

最後,部分 ANR 問題是很難分析的,有時候由於系統底層的一些影響,導致消息調度失敗,出現問題的場景又難以復現。這類 ANR 問題往往需要花費大量的時間去了解系統的一些行爲,超出了 ANR 機制本身的範疇。

2.2 ANR 機制

分析一些初級的 ANR 問題,只需要簡單理解最終輸出的日誌即可,但對於一些由系統問題(例如:CPU 負載過高、進程卡死)引發的 ANR,就需要對整個 ANR 機制有所瞭解,才能定位出問題的原因。

ANR 機制可以分爲兩部分:

      ✎  ANR 的監測:Android 對於不同的 ANR 類型(Broadcast,Service,InputEvent)都有一套監測機制。

      ✎  ANR 的報告:在監測到 ANR 以後,需要顯示 ANR 對話框、輸出日誌(發生 ANR 時的進程函數調用棧、CPU 使用情況等)。

2.3 ANR 的觸發原因

前面我們說過,出現 ANR 之後一個直觀現象就是系統會展示出一個 ANR 對話框。

谷歌文檔中對 ANR 產生的原因是這麼描述的:

Android 系統中的應用被 ActivityManagerServiceWindowManagerService 兩個系統服務監控着,系統會在如下兩種情況展示出 ANR 的對話框!

      ✎  KeyDispatchTimeout ( 5 seconds ) :按鍵或觸摸事件在特定時間內無響應
      ✎  BroadcastTimeout ( 10 seconds ):BroadcastReceiver 在特定時間內無法處理完成
      ✎  ServiceTimeout ( 20 seconds ) :Service 在特定的時間內無法處理完成

3. Service 超時監測機制

Service 運行在應用程序的主線程,如果 Service 的執行時間超過 20 秒,則會引發 ANR。

當發生 Service ANR 時,一般可以先排查一下在 Service 的生命週期函數中有沒有做耗時的操作,例如複雜的運算、IO 操作等。如果應用程序的代碼邏輯查不出問題,就需要深入檢查當前系統的狀態:CPU 的使用情況、系統服務的狀態等,判斷當時發生 ANR 進程是否受到系統運行異常的影響。

那麼,系統是如何檢測 Service 超時的呢?Android 是通過設置定時消息實現的。定時消息是由 AMS 的消息隊列處理的,AMS 有 Service 運行的上下文信息,所以在 AMS 中設置一套超時檢測機制也是合情合理的。

Service ANR 機制相對最爲簡單,主體實現在ActiveServices中。

在 Service 的啓動流程中,Service 進程 attach 到 system_server 進程後會調用 realStartServiceLocked() 方法。

3.1 realStartServiceLocked

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

public final class ActiveServices {

    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {

        // 發送 delay 消息(SERVICE_TIMEOUT_MSG)
        bumpServiceExecutingLocked(r, execInFg, "create");

        boolean created = false;
        try {
            
            // 最終執行服務的 onCreate() 方法
            app.thread.scheduleCreateService(r, r.serviceInfo, mAm.
                compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);

            ... ...
        }
    }
    
}

3.2 bumpServiceExecutingLocked

private final void bumpServiceExecutingLocked(...) {

    scheduleServiceTimeoutLocked(r.app);
    
}

3.3 scheduleServiceTimeoutLocked

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    // 當超時後仍沒有 remove 該 SERVICE_TIMEOUT_MSG 消息,
    // 通過 AMS.MainHandler 拋出一個定時消息。
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

上述方法通過 AMS.MainHandler 拋出一個定時消息 SERVICE_TIMEOUT_MSG

3.4 serviceDoneExecutingLocked

前臺進程中執行 Service,超時時間是 SERVICE_TIMEOUT(20 秒)

    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;

後臺進程中執行 Service,超時時間是 SERVICE_BACKGROUND_TIMEOUT(200 秒)

    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

當 Service 的生命週期結束時(不會 ANR),會調用 serviceDoneExecutingLocked() 方法,之前拋出的 SERVICE_TIMEOUT_MSG 消息在這個方法中會被清除。

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
    boolean inDestroying = mDestroyingServices.contains(r);
    if (r != null) {
        ... ...        
        serviceDoneExecutingLocked(r, inDestroying, inDestroying);
    }
}

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
          boolean finishing) {    
    ... ...
    if (r.executeNesting <= 0) {
        if (r.app != null) {    
            ... ...
            // 當前服務所在進程中沒有正在執行的service,清除 SERVICE_TIMEOUT_MSG 消息
            if (r.app.executingServices.size() == 0) {
                mAm.mHandler.removeMessages(
                             ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);            
                ... ...
            }
    ... ...
}

3.5 handleMessage

如果沒有 Remove 掉 SERVICE_TIMEOUT_MSG 呢?接下來我們看看對於 ANR 的處理邏輯。

在 system_server 進程中有一個 Handler 線程,名叫 ActivityManager

如果在超時時間內,SERVICE_TIMEOUT_MSG 沒有被清除,便會向該 Handler 線程發送一條信息 SERVICE_TIMEOUT_MSG

final class MainHandler extends Handler {
    ... ...

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ... ...
            
            case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
        ... ...
    }
}

3.6 serviceTimeout

void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;

    synchronized(mAm) {
        ... ...
        
        long nextTime = 0;

        // 尋找運行超時的 Service
        for (int i = proc.executingServices.size() - 1; i >= 0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }

        // 判斷執行 Service 超時的進程是否在最近運行進程列表,如果不在,則忽略這個 ANR
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, "    ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, 
                                           LAST_ANR_LIFETIME_DURATION_MSECS);
            anrMessage = "executing service " + timeout.shortName;
        ... ...
    }

    if (anrMessage != null) {
        // 當存在 timeout 的 service,則執行 appNotResponding
        mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
    }
}

上述方法會找到當前進程已經超時的 Service,經過一些判定後,決定要報告 ANR,最終調用 AMS.appNotResponding() 方法。

走到這一步,ANR 機制已經完成了監測報告任務,剩下的任務就是 ANR 結果的輸出,我們稱之爲 ANR 的報告機制。ANR 的報告機制是通過 AMS.appNotResponding() 完成的,Broadcast 和 InputEvent 類型的 ANR 最終也都會調用這個方法。

4. ANR 信息收集過程

接下來我們看看 Android ANR 的信息收集過程!

4.1 appNotResponding

// frameworks/base/services/core/java/com/android/server/am/AppErrors.java

class AppErrors {

    final void appNotResponding(ProcessRecord app, ActivityRecord activity,
            ActivityRecord parent, boolean aboveSystem, final String annotation) {
        ... ...

        long anrTime = SystemClock.uptimeMillis();
        if (ActivityManagerService.MONITOR_CPU_USAGE) {
            mService.updateCpuStatsNow();   // 更新 cpu 統計信息
        }

        boolean showBackground = Settings.Secure.
                getInt(mContext.getContentResolver(),
                           Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;

        boolean isSilentANR;

        synchronized (mService) {
            if (mService.mShuttingDown) {
                return;
            } else if (app.notResponding) {
                return;
            } else if (app.crashing) {
                return;
            } else if (app.killedByAm) {
                return;
            } else if (app.killed) {
                return;
            }

            // In case we come through here for the same app before completing
            // this one, mark as anring now so we will bail out.
            app.notResponding = true;

            // 記錄 ANR 到 EventLog
            EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
                    app.processName, app.info.flags, annotation);

            // 將當前進程添加到 firstPids
            firstPids.add(app.pid);

            // Don't dump other PIDs if it's a background ANR
            isSilentANR = !showBackground 
                                  && !isInterestingForBackgroundTraces(app);
            if (!isSilentANR) {
                int parentPid = app.pid;
                if (parent != null && parent.app != null && parent.app.pid > 0) {
                    parentPid = parent.app.pid;
                }
                if (parentPid != app.pid) firstPids.add(parentPid);

                // 將 system_server 進程添加到 firstPids
                if (MY_PID != app.pid 
                                && MY_PID != parentPid) firstPids.add(MY_PID);

                for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
                    ProcessRecord r = mService.mLruProcesses.get(i);
                    if (r != null && r.thread != null) {
                        int pid = r.pid;
                        if (pid > 0 && pid != app.pid 
                                       && pid != parentPid && pid != MY_PID) {
                            if (r.persistent) {
                                // 將 persistent 進程添加到 firstPids
                                firstPids.add(pid);
                            } else if (r.treatLikeActivity) {
                                firstPids.add(pid);
                            } else {
                                // 其他進程添加到 lastPids
                                lastPids.put(pid, Boolean.TRUE);
                            }
                        }
                    }
                }
            }
        }

        // 記錄 ANR 輸出到 main log
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(app.processName);
        if (activity != null && activity.shortComponentName != null) {
            info.append(" (").append(activity.shortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(app.pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parent != null && parent != activity) {
            info.append("Parent: ").append(parent.shortComponentName).append("\n");
        }

        // 創建 CPU tracker 對象 
        ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);

        ... ...

        // 輸出 traces 信息
        File tracesFile = ActivityManagerService.dumpStackTraces(
                true, firstPids,
                (isSilentANR) ? null : processCpuTracker,
                (isSilentANR) ? null : lastPids,
                nativePids);

        String cpuInfo = null;
        if (ActivityManagerService.MONITOR_CPU_USAGE) {
            mService.updateCpuStatsNow();
            synchronized (mService.mProcessCpuTracker) {
                cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
            }
            // 記錄當前 CPU 負載情況
            info.append(processCpuTracker.printCurrentLoad());
            info.append(cpuInfo);
        }

        // 記錄從 anr 時間開始的 Cpu 使用情況
        info.append(processCpuTracker.printCurrentState(anrTime));

        // 輸出當前 ANR 的 reason,以及 CPU 使用率、負載信息
        Slog.e(TAG, info.toString());
        if (tracesFile == null) {
            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
        }
        ... ...
                        
        // 將 traces 文件和 CPU 使用率信息保存到 dropbox,即 data/system/dropbox 目錄
        mService.addErrorToDropBox("anr", app, app.processName,
                          activity, parent, annotation, cpuInfo, tracesFile, null);
        ... ...

        synchronized (mService) {
            mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);

            // 後臺 ANR 的情況, 直接殺掉
            if (isSilentANR) {
                app.kill("bg anr", true);
                return;
            }

            // 設置 app 的 ANR 狀態,病查詢錯誤報告 receiver
            makeAppNotRespondingLocked(app,
                    activity != null ? activity.shortComponentName : null,
                    annotation != null ? "ANR " + annotation : "ANR",
                    info.toString());

            // 彈出 ANR 對話框
            Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);

            // 向 ui 線程發送,內容爲 SHOW_NOT_RESPONDING_MSG 的消息
            mService.mUiHandler.sendMessage(msg);
        }
    }
    
}
當發生 ANR 時, 會按順序依次執行:

       ✒ 1、輸出 ANR Reason 信息到 EventLog,也就是說 ANR 觸發的時間點最接近的就是 EventLog 中輸出的 am_anr 信息;
       ✒ 2、收集並輸出重要進程列表中的各個線程的 traces 信息,該方法較耗時;
       ✒ 3、輸出當前各個進程的 CPU 使用情況以及 CPU 負載情況
       ✒ 4、將 traces 文件CPU 使用情況信息保存到 dropbox,即 data/system/dropbox 目錄;
       ✒ 5、根據進程類型,來決定直接後臺殺掉,還是彈框告知用戶

ANR輸出重要進程的traces信息,這些進程包含:

       ✒ 1、firstPids 隊列:第一個是 ANR 進程,第二個是 system_server,剩餘是所有 persistent 進程;
       ✒ 2、Native 隊列:是指 /system/bin/ 目錄的 mediaserversdcard 以及 surfaceflinger 進程;
       ✒ 3、lastPids 隊列: 是指 mLruProcesses 中的不屬於 firstPids 的所有進程。

4.2 dumpStackTraces

繼續看看 dump 出 trace 信息的流程:

// ActivityManagerService.java

    public static File dumpStackTraces(boolean clearTraces, ... ,nativePids) {
        ... ...

        if (tracesDirProp.isEmpty()) {
            // 默認爲 data/anr/traces.txt
            String globalTracesPath = 
                          SystemProperties.get("dalvik.vm.stack-trace-file", null);

            tracesFile = new File(globalTracesPath);
            try {
                if (clearTraces && tracesFile.exists()) {
                    tracesFile.delete();      // 刪除已存在的 traces 文件
                }

                // 這裏會保證 data/anr/traces.txt 文件內容是全新的方式,而非追加
                tracesFile.createNewFile();   // 創建 traces 文件
                FileUtils.setPermissions(globalTracesPath, 0666, -1, -1);
            } catch (IOException e) {
                Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesFile, e);
                return null;
            }
        } else {
        }

        // 輸出 trace 內容
        dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids,
                                         extraPids, useTombstonedForJavaTraces);
        return tracesFile;
    }

4.3 dumpStackTraces

// ActivityManagerService.java

    private static void dumpStackTraces(String tracesFile, ...) {

        final DumpStackFileObserver observer;
        if (useTombstonedForJavaTraces) {
            observer = null;
        } else {
            observer = new DumpStackFileObserver(tracesFile);
        }

        // We must complete all stack dumps within 20 seconds.
        long remainingTime = 20 * 1000;
        try {
            if (observer != null) {
                observer.startWatching();
            }

            // 首先,獲取 firstPids 進程的 stacks
            if (firstPids != null) {
                int num = firstPids.size();
                for (int i = 0; i < num; i++) {
                    final long timeTaken;
                    if (useTombstonedForJavaTraces) {
                        timeTaken = dumpJavaTracesTombstoned(firstPids.get(i),
                                                   tracesFile, remainingTime);
                    } else {
                        timeTaken = observer.dumpWithTimeout(firstPids.get(i),
                                                               remainingTime);
                    }
                    ... ...    
                }
            }

            // 下一步,獲取 native 進程的 stacks
            if (nativePids != null) {
                for (int pid : nativePids) {
                    ... ...
                    
                    // 輸出 native 進程的 trace
                    Debug.dumpNativeBacktraceToFileTimeout(
                            pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
                            
                    final long timeTaken = SystemClock.elapsedRealtime() - start;
                    ... ...
                }
            }

            // Lastly, dump stacks for all extra PIDs from the CPU tracker.
            if (extraPids != null) {
                ... ...
                }
            }
        } finally {
            if (observer != null) {
                observer.stopWatching();
            }
        }
    }

4.4 小結

觸發 ANR 時系統會輸出關鍵信息:

       ✒ 1、將 am_anr 信息,輸出到 EventLog
       ✒ 2、獲取重要進程 trace 信息,保存到 /data/anr/traces.txt
       ✒ 3、ANR reason 以及 CPU 使用情況信息,輸出到 main log;
       ✒ 4、再將 CPU使用情況 和進程 trace 文件信息,再保存到 /data/system/dropbox

5. 總結

當 Service 出現 ANR 時,最終調用到 AMS.appNotResponding()方法。

       ✒ 1、對於前臺服務,則超時爲 SERVICE_TIMEOUT = 20s

       ✒ 2、對於後臺服務,則超時爲 SERVICE_BACKGROUND_TIMEOUT = 200s

       ✒ 3、Service 超時檢測機制:超過一定時間沒有執行完相應操作來觸發延時消息,則會觸發 ANR;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章