Activity啓動流程總結-超時判定機制

前言

在Activity啓動過程中,ActivityManagerService會與APP進程進行交互,調度APP進程上的Activity的生成和初始化,以及生命週期的逐步切換和回調。

在這期間的調度交互中ActivityManagerService需要知道APP進程端的執行結果來進行下一步操作,因此ActivityManagerService需要一個超時判定機制,來應對APP進程端執行過久或無響應時的情況。

例如,當目標Activity所屬進程未啓動時,ActivityManagerService需要先請求Zygote創建進程,然後等待進程創建完畢後繼續啓動目標Activity,那麼AMS如何監聽進程啓動是否太久呢?還有當啓動目標Activity前,會先pause當前正顯示的Activity,之後才顯示目標Activity,那麼AMS是否會無限期等待?

源碼探究

文中源碼基於Android 9.0

ActivityManagerService在調度Activity的各個階段都需要超時處理,這裏按照《Activity啓動流程總結-生命週期》中的過程來分階段看。

Activity pause階段

Activity啓動流程中,首先需要暫停當前正在顯示的Activity。

超時的設置

[ActivityStack.java]

final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
        ActivityRecord resuming, boolean pauseImmediately) {
    // ···
    ActivityRecord prev = mResumedActivity;
    // ···
    mPausingActivity = prev;
    // ···
    if (prev.app != null && prev.app.thread != null) {
        try {
            // 調度APP進程執行對應Activity的pause行爲
            mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,
                    PauseActivityItem.obtain(prev.finishing, userLeaving,
                            prev.configChangeFlags, pauseImmediately));
        } catch (Exception e) {
            // Ignore exception, if process died other code will cleanup.
            Slog.w(TAG, "Exception thrown during pause", e);
            mPausingActivity = null;
            mLastPausedActivity = null;
            mLastNoHistoryActivity = null;
        }
    } else {
        mPausingActivity = null;
        mLastPausedActivity = null;
        mLastNoHistoryActivity = null;
    }
    // ···
    
    // If we are not going to sleep, we want to ensure the device is
    // awake until the next activity is started.
    if (!uiSleeping && !mService.isSleepingOrShuttingDownLocked()) {
        // 獲取Wakelock並設置超時監聽
        mStackSupervisor.acquireLaunchWakelock();
    }

    if (mPausingActivity != null) {
        // ···
        // pauseImmediately默認爲false
        if (pauseImmediately) {
            // If the caller said they don't want to wait for the pause, then complete
            // the pause now.
            completePauseLocked(false, resuming);
            return false;

        } else {
            // 設置超時監聽
            schedulePauseTimeout(prev);
            return true;
        }

    } else {
        // This activity failed to schedule the
        // pause, so just treat it as being paused now.
        if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next.");
        if (resuming == null) {
            mStackSupervisor.resumeFocusedStackTopActivityLocked();
        }
        return false;
    }
}

該方法中會設置兩個超時,在通過scheduleTransaction方法調度Activity進行pause後,就調用acquireLaunchWakelockschedulePauseTimeout方法進行超時設置。

LAUNCH_TIMEOUT_MSG

[ActivityStackSupervisor.java]

void acquireLaunchWakelock() {
    if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
        throw new IllegalStateException("Calling must be system uid");
    }
    mLaunchingActivity.acquire();
    if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) {
        // To be safe, don't allow the wake lock to be held for too long.
        mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT);
    }
}

可以看出這裏是通過mHandler發送延遲消息的方式來進行超時設置,what爲LAUNCH_TIMEOUT_MSG,LAUNCH_TIMEOUT爲10s

PAUSE_TIMEOUT_MSG

[ActivityStack.java]

private void schedulePauseTimeout(ActivityRecord r) {
    final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);
    msg.obj = r;
    r.pauseTime = SystemClock.uptimeMillis();
    mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
    if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
}

通過mHandler發送延遲消息的方式來進行超時設置,Message的what賦值爲PAUSE_TIMEOUT_MSG,obj爲需要pause的ActivityRecord,pauseTime爲系統啓動以來時間。

[ActivityStack.java]

private static final int PAUSE_TIMEOUT = 500;

PAUSE_TIMEOUT表示500ms,留給Activity pause的時間很短,在500ms後就會觸發超時。

mHandler的由來
  • ActivityStack的mHandler成員在ActivityStack的構造函數中實例化:
    [ActivityStack.java]
ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
        int windowingMode, int activityType, boolean onTop) {
    mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
}

mHandler實例的類型爲ActivityStackHandler(繼承自Handler),通過ActivityManagerService的mHandler的成員的Looper創建。

  • ActivityStackSupervisor的mHandler成員在構造函數中創建,其實例爲ActivityStackSupervisorHandler
    [ActivityStackSupervisor.java]
public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
    mService = service;
    mLooper = looper;
    mHandler = new ActivityStackSupervisorHandler(looper);
}
  • ActivityManagerService的mHandler成員在構造函數中實例化,其實例爲創建MainHandler
    構造函數中創建ServiceThread(繼承自HandlerThread)並啓動,之後將Looper設置給mHandler。通過mHandler發送的消息將運行在ServiceThread中。

[ActivityManagerService.java]

public ActivityManagerService(Context systemContext) {
    mHandlerThread = new ServiceThread(TAG,
            THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
    mHandlerThread.start();
    mHandler = new MainHandler(mHandlerThread.getLooper());
}

超時的觸發

LAUNCH_TIMEOUT_MSG

進入ActivityStackSupervisorHandler的handleMessage方法的LAUNCH_TIMEOUT_MSG case:
[ActivityStack.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_TIMEOUT_MSG: {
            synchronized (mService) {
                if (mLaunchingActivity.isHeld()) {
                    Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
                    if (VALIDATE_WAKE_LOCK_CALLER
                            && Binder.getCallingUid() != Process.myUid()) {
                        throw new IllegalStateException("Calling must be system uid");
                    }
                    // 釋放Wakelock
                    mLaunchingActivity.release();
                }
            }
        } break;
        // ···
    }
}
PAUSE_TIMEOUT_MSG

進入ActivityStackHandler的handleMessage方法的PAUSE_TIMEOUT_MSG case:
[ActivityStack.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        case PAUSE_TIMEOUT_MSG: {
            // 獲取pausing的Activity對應的ActivityRecord
            ActivityRecord r = (ActivityRecord)msg.obj;
            // We don't at this point know if the activity is fullscreen,
            // so we need to be conservative and assume it isn't.
            Slog.w(TAG, "Activity pause timeout for " + r);
            synchronized (mService) {
                if (r.app != null) {
                    mService.logAppTooSlow(r.app, r.pauseTime, "pausing " + r);
                }
                // 傳入token和true
                activityPausedLocked(r.appToken, true);
            }
        } break;
        // ···
    }
}

當過了500ms的延遲時間後,若觸發了PAUSE_TIMEOUT_MSG的消息,將執行activityPausedLocked方法,傳入Activity對應的窗口token和true(表示來自超時)。

activityPausedLocked方法在正常的Activity pause後也會調用。

接着進入activityPausedLocked方法:
[ActivityStack.java]

// timeout在超時觸發情況下爲true,正常pause情況下爲false
final void activityPausedLocked(IBinder token, boolean timeout) {
    if (DEBUG_PAUSE) Slog.v(TAG_PAUSE,
        "Activity paused: token=" + token + ", timeout=" + timeout);

    final ActivityRecord r = isInStackLocked(token);
    if (r != null) {
        // 移除PAUSE_TIMEOUT_MSG消息
        mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
        if (mPausingActivity == r) {
            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r
                    + (timeout ? " (due to timeout)" : " (pause complete)"));
            mService.mWindowManager.deferSurfaceLayout();
            try {
                completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
            } finally {
                mService.mWindowManager.continueSurfaceLayout();
            }
            return;
        } else {
            // ···
        }
    }
    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}

可以看出該方法中是否timeout的區別僅在日誌不同上,即使超時也會繼續進行後續的啓動Activity的流程。

超時的取消

當APP進程端完成Activity的pause操作後,會調用ActivityManagerService的activityPaused方法,而在該方法中又會通過token查找對應的ActivityStack,調用其activityPausedLocked方法,此時傳入的timeout爲false,在activityPausedLocked方法中就會移除PAUSE_TIMEOUT_MSG消息。

LAUNCH_TIMEOUT_MSG消息將會在目標Activity啓動完成後取消。

Application launch階段

若待啓動的目標Activity所屬進程未啓動,則AMS會先請求創建進程。

超時的設置

ActivityManagerService中會調用startProcessLocked方法進行請求創建進程:
[ActivityManagerService.java]

private boolean startProcessLocked(String hostingType, String hostingNameStr, String entryPoint,
        ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
        String seInfo, String requiredAbi, String instructionSet, String invokeWith,
        long startTime) {
    // ···
    // 進一步請求創建進程
    final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint,
            app, app.startUid, gids, runtimeFlags, mountExternal, app.seInfo,
            requiredAbi, instructionSet, invokeWith, app.startTime);
    synchronized (ActivityManagerService.this) {
        // 這個方法中進行超時設置
        handleProcessStartedLocked(app, startResult, startSeq);
    }
    // ···
}

handleProcessStartedLocked方法中又會調用另一個重載方法:
[ActivityManagerService.java]

private boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,
        long expectedStartSeq, boolean procAttached) {
    synchronized (mPidsSelfLocked) {
        this.mPidsSelfLocked.put(pid, app);
        // procAttached傳入爲false
        if (!procAttached) {
            // what爲PROC_START_TIMEOUT_MSG
            Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
            // obj爲待啓動進程對應的ProcessRecord
            msg.obj = app;
            // 發送延遲消息
            mHandler.sendMessageDelayed(msg, usingWrapper
                    ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
        }
    }
    checkTime(app.startTime, "startProcess: done updating pids map");
    return true;
}

這裏可以看出是通過ActivityManagerService的mHandler成員發送一個what=PROC_START_TIMEOUT_MSG10s延遲消息。

[ActivityManagerService.java]

// 20min
static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
// 10s
static final int PROC_START_TIMEOUT = 10*1000;

當有對啓動進程過程附加監聽時,將設置延遲時間爲1200s,例如設置了Valgrind內存監測工具。通常情況下延遲時間爲10s。

超時的觸發

ActivityManagerService的mHandler是在ActivityManagerService構造函數中初始化,其實例爲MainHandler,進入它的handleMessage方法:
[ActivityManagerService.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        // ···
        case PROC_START_TIMEOUT_MSG: {
            ProcessRecord app = (ProcessRecord)msg.obj;
            synchronized (ActivityManagerService.this) {
                processStartTimedOutLocked(app);
            }
        } break;
        // ···
    }
}

在PROC_START_TIMEOUT_MSG case中調用了processStartTimedOutLocked方法:
[ActivityManagerService.java]

private final void processStartTimedOutLocked(ProcessRecord app) {
    final int pid = app.pid;
    // 標記是否進行清理工作
    boolean gone = false;
    synchronized (mPidsSelfLocked) {
        // mPidsSelfLocked中保存着所有在運行的APP進程信息
        ProcessRecord knownApp = mPidsSelfLocked.get(pid);
        if (knownApp != null && knownApp.thread == null) {
            // 若存在APP進程信息,但是IApplicationThread爲空,則需要移除,並標記需要清理
            mPidsSelfLocked.remove(pid);
            gone = true;
        }
    }

    if (gone) {
        Slog.w(TAG, "Process " + app + " failed to attach");
        EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, app.userId,
                pid, app.uid, app.processName);
        removeProcessNameLocked(app.processName, app.uid);
        if (mHeavyWeightProcess == app) {
            mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
                    mHeavyWeightProcess.userId, 0));
            mHeavyWeightProcess = null;
        }
        mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
        // Take care of any launching providers waiting for this process.
        // 處理等待該進程啓動的ContentProvider部分
        cleanupAppInLaunchingProvidersLocked(app, true);
        // Take care of any services that are waiting for the process.
        // 處理等待該進程啓動的Service部分
        mServices.processStartTimedOutLocked(app);
        app.kill("start timeout", true);
        if (app.isolated) {
            mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
        }
        removeLruProcessLocked(app);
        if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
            Slog.w(TAG, "Unattached app died before backup, skipping");
            mHandler.post(new Runnable() {
            @Override
                public void run(){
                    try {
                        IBackupManager bm = IBackupManager.Stub.asInterface(
                                ServiceManager.getService(Context.BACKUP_SERVICE));
                        bm.agentDisconnected(app.info.packageName);
                    } catch (RemoteException e) {
                        // Can't happen; the backup manager is local
                    }
                }
            });
        }
        if (isPendingBroadcastProcessLocked(pid)) {
            Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping");
            // 跳過等待該進程啓動的廣播部分
            skipPendingBroadcastLocked(pid);
        }
    } else {
        // 否則僅打印一段日誌
        Slog.w(TAG, "Spurious process start timeout - pid not known for " + app);
    }
}

該方法中會對AMS的mPidsSelfLocked成員中保存的ProcessRecord進行檢查,若存在對應的ProcessRecord卻不存在IApplicationThread,則需要進行清理工作。否則打印一段日誌。
APP進程正常啓動情況下,會將對應的ProcessRecord保存在mPidsSelfLocked中。

超時的取消

APP進程正常啓動後,會通知AMS:
[ActivityThread.java]

private void attach(boolean system, long startSeq) {
    final IActivityManager mgr = ActivityManager.getService();
    try {
        // 通過IActivityManager binder通信接口通知到AMS,最終調用到AMS的attachApplication方法。
        // 這裏將IApplicationThread和啓動序列號傳給AMS,用於AMS向APP進程通信和AMS匹配啓動的ProcessRecord。
        mgr.attachApplication(mAppThread, startSeq);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

在AMS的attachApplication方法中又調用attachApplicationLocked方法:
[ActivityManagerService.java]

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // ···
    mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
    // ···
}

在AMS的attachApplicationLocked方法中將移除PROC_START_TIMEOUT_MSG消息。

Activity launch階段

超時的設置

在ActivityStack的resumeTopActivityInnerLocked方法中,當判斷APP進程存在時,將會調度Activity resume事務,併發送超時消息:

[ActivityStack.java]

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
    // ···
    if (next.app != null && next.app.thread != null) {
        // next爲將要顯示的Activity對應的ActivityRecord
        next.completeResumeLocked();
    } else {
        // ··· 
    }
    // ···
}

在ActivityRecord的completeResumeLocked方法中將調用ActivityStackSupervisor的scheduleIdleTimeoutLocked方法

[ActivityStackSupervisor.java]

void scheduleIdleTimeoutLocked(ActivityRecord next) {
    if (DEBUG_IDLE) Slog.d(TAG_IDLE,
            "scheduleIdleTimeoutLocked: Callers=" + Debug.getCallers(4));
    // 發送IDLE_TIMEOUT_MSG消息
    Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);
    // IDLE_TIMEOUT值爲10s
    mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
}

通過mHandler(爲ActivityStackSupervisorHandler)發送一個10sIDLE_TIMEOUT_MSG消息消息。

超時的觸發

進入ActivityStackSupervisorHandler的handleMessage方法,看對應的IDLE_TIMEOUT_MSG case:
[ActivityStackSupervisor.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        case IDLE_TIMEOUT_MSG: {
            if (DEBUG_IDLE) Slog.d(TAG_IDLE,
                    "handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj);
            // We don't at this point know if the activity is fullscreen,
            // so we need to be conservative and assume it isn't.
            // 調用ActivityStackSupervisorHandler的activityIdleInternal方法
            activityIdleInternal((ActivityRecord) msg.obj,
                    true /* processPausingActivities */);
        } break;
    }
}

最終將調用ActivityStackSupervisor的activityIdleInternalLocked方法。

超時的取消

Activity啓動完成後,將調用AMS的activityIdle方法通知啓動完成,在activityIdle中又會調用ActivityStackSupervisor的activityIdleInternalLocked方法:

[ActivityStackSupervisor.java]

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
    boolean processPausingActivities, Configuration config) {
    ActivityRecord r = ActivityRecord.forTokenLocked(token);
    if (r != null) {
        // ···
        // 移除IDLE_TIMEOUT_MSG消息
        mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
        // ···
    }

    if (allResumedActivitiesIdle()) {
        // ···

        if (mLaunchingActivity.isHeld()) {
            // 移除IDLE_TIMEOUT_MSG消息
            mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
            if (VALIDATE_WAKE_LOCK_CALLER &&
                    Binder.getCallingUid() != Process.myUid()) {
                throw new IllegalStateException("Calling must be system uid");
            }
            // 釋放WakeLock
            mLaunchingActivity.release();
        }
        ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
    }
}

AMS在收到Activity啓動完成通知調用後,就會移除IDLE_TIMEOUT_MSGLAUNCH_TIMEOUT_MSG消息。

Activity stop階段

當目標Activity顯示後,將處理待執行stop的那些Activity。

超時的設置

停止Activity會調用ActivityStack的stopActivityLocked方法:

[ActivityStack.java]

final void stopActivityLocked(ActivityRecord r) {
    // ···
    Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r);
    // STOP_TIMEOUT爲 11 * 1000
    mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
    // ···
}

發送11s延遲的STOP_TIMEOUT_MSG消息。

超時的觸發

進入ActivityStackHandler的handleMessage方法:

[ActivityStack.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        case STOP_TIMEOUT_MSG: {
            ActivityRecord r = (ActivityRecord)msg.obj;
            // We don't at this point know if the activity is fullscreen,
            // so we need to be conservative and assume it isn't.
            Slog.w(TAG, "Activity stop timeout for " + r);
            synchronized (mService) {
                if (r.isInHistory()) {
                    r.activityStoppedLocked(null /* icicle */,
                            null /* persistentState */, null /* description */);
                }
            }
        } break;
    }
}

即使APP進程端stop Activity超時,AMS也會進行後續的操作。

超時的取消

Activity在stop完成後,會調用AMS的activityStopped方法,在該方法中會查找對應ActivityRecord,調用其activityStoppedLocked方法:

[ActivityRecord.java]

final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState,
        CharSequence description) {
    // ···
    stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
    // ···
}

在該方法中移除STOP_TIMEOUT_MSG超時消息。

Activity destroy階段

超時的設置

銷燬APP進程中的Activity時會調用ActivityStack的destroyActivityLocked方法:

[ActivityStack.java]

final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
    r.setState(DESTROYING,
            "destroyActivityLocked. finishing and not skipping destroy");
    Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
    // DESTROY_TIMEOUT爲 10 * 1000
    mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
}

發送10s延遲的DESTROY_TIMEOUT_MSG消息。

超時的觸發

進入ActivityStackHandler的handleMessage方法:
[ActivityStack.java]

public void handleMessage(Message msg) {
    switch (msg.what) {
        case DESTROY_TIMEOUT_MSG: {
            ActivityRecord r = (ActivityRecord)msg.obj;
            // We don't at this point know if the activity is fullscreen,
            // so we need to be conservative and assume it isn't.
            Slog.w(TAG, "Activity destroy timeout for " + r);
            synchronized (mService) {
                // 進行清理和移除相關操作
                activityDestroyedLocked(r != null ? r.appToken : null, "destroyTimeout");
            }
        } break;
    }
}

APP進程端destroy Activity如果超時,AMS會進行清理相關操作。

超時的取消

APP進程端destroy Activity完成後會調用AMS的activityDestroyed方法,該方法中最終會調用ActivityStack的activityDestroyedLocked方法:
[ActivityStack.java]

final void activityDestroyedLocked(ActivityRecord record, String reason) {
    if (record != null) {
        // 移除DESTROY_TIMEOUT_MSG消息
        mHandler.removeMessages(DESTROY_TIMEOUT_MSG, record);
    }
    // 進行清理和移除相關操作 ···
}

AMS收到通知後即會移除DESTROY_TIMEOUT_MSG消息。

總結

AMS在pause Activity時會通過Handler發送LAUNCH_TIMEOUT_MSG(10s)PAUSE_TIMEOUT_MSG(500ms)超時消息;在launch Application時發送**PROC_START_TIMEOUT_MSG(10s)超時消息;launch Activity時發送IDLE_TIMEOUT_MSG(10s)超時消息;stop Activity時發送STOP_TIMEOUT_MSG(11s)超時消息;destory Activity時發送DESTROY_TIMEOUT_MSG(10s)**超時消息。

當觸發超時後,AMS將自動進行後續階段的操作和清理操作。即使APP進程端處理耗時過久或異常或無響應,AMS也能保證調度過程不會因此出現停滯和混亂。

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