1. 程式人生 > >pausetimeout問題流程分析及解決方案

pausetimeout問題流程分析及解決方案

這次出現問題的堆疊是處理activity的pausetimeout的堆疊,在分析問題之前我們先了解下pausetimeout。下其大致流程如下(Android P程式碼)

  1. pausetimeout觸發機制

在我們需要對當前resumed的activity做pause操作的時候,我們會呼叫startPausingLocked(ActivityStack.java),部分程式碼邏輯如下

    final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,

            ActivityRecord resuming, boolean pauseImmediately) {

        //1.首先完成正在pausing的activity的pause操作,做到有序

        if (mPausingActivity != null) {

            Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity

                    + " state=" + mPausingActivity.getState());

            if (!shouldSleepActivities()) {

                // Avoid recursion among check for sleep and complete pause during sleeping.

                // Because activity will be paused immediately after resume, just let pause

                // be completed by the order of activity paused from clients.

                completePauseLocked(false, resuming);

            }

            }

       //2. 將當前ResumedActivity設定為mPausingActivity

ActivityRecord prev = mResumedActivity;

…

mPausingActivity = prev;

mLastPausedActivity = prev;

…

  if (mPausingActivity != null) {

….

if (pauseImmediately) {

       // If the caller said they don't want to wait for the pause, then complete

        // the pause now.

        completePauseLocked(false, resuming); //呼叫completePauseLocked立刻對activity做Pause操作

        return false;

    } else {

         schedulePauseTimeout(prev);//否則,呼叫schedulePauseTimeout

         eturn true;

      }

  …..

}

  如上程式碼可見,startPausingLocked方法首先對當前Stack中的mPausingActivity進行pause操作,然後將當前狀態為resumed的activity的狀態置為pausing並用此activity替換mPausingActivity變數,然後根據引數pauseImmediately的真假,呼叫completePauseLocked對mPausingActivity立刻做Pause操作或呼叫schedulePauseTimeout在500ms內完成Pause操作。

schedulePauseTimeout(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); //500ms

        if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");

}

可以看到schedulePauseTimeout會發送一個延遲訊息PAUSE_TIMEOUT_MSG給mHandler(ActivityStackHandler型別) 進行處理,延遲時間為PAUSE_TIMEOUT(500ms),此訊息在relaunchActivityLocked,activityPausedLocked或removeTimeoutsForActivityLocked方法中移除。

  1. pausetimeout流程

在mHandler接收到PAUSE_TIMEOUT_MSG訊息後會呼叫activityPausedLocked(r.appToken, true)對此activity進行處理,activityPausedLocked(ActivityStack.java)程式碼部分程式碼實現如下:

1)activityPausedLocked方法實現

final void activityPausedLocked(IBinder token, boolean timeout) {

        final ActivityRecord r = isInStackLocked(token);

        if (r != null) {

            mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);

            if (mPausingActivity == r) {

                mService.mWindowManager.deferSurfaceLayout();

                try {

                    completePauseLocked(true /* resumeNext */, null /* resumingActivity */);

                } finally {

                    mService.mWindowManager.continueSurfaceLayout();

                }

                return;

            }else {

                if (r.isState(PAUSING)) {

                    r.setState(PAUSED, "activityPausedLocked");

                    if (r.finishing) {

                        finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false,"activityPausedLocked");

                    }}}}

        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);

}

activityPausedLocked方法首先將超時訊息PAUSE_TIMEOUT_MSG移除,如果此時活動仍為mPausingActivity,則呼叫completePauseLocked(true,null)立刻對其進行pause操作,否則將活動的狀態置為PAUSED,如果活動狀態已經為finishing還需要呼叫finishCurrentActivityLocked方法來對其進行finish操作。最終都會呼叫ensureActivitiesVisibleLocked校正所有Task中的Activity的狀態。

2)completePauseLocked( ActivityStack.java)方法實現

  private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {

        ActivityRecord prev = mPausingActivity;

        if (prev != null) {

         ……

            if (prev.finishing) {

                prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false,

                        "completedPausedLocked");//狀態已經為finish,完成finish操作

            } else if (prev.app != null) {

                if (mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(prev)) {//不再等待顯示

                }

                if (prev.deferRelaunchUntilPaused) {

                    // Complete the deferred relaunch that was waiting for pause to complete.如註釋

                    prev.relaunchActivityLocked(false /* andResume */,prev.preserveWindowOnDeferredRelaunch);

                }

             ……

}

……

if (resumeNext) {

 …….

mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);//有兩種不同的方式呼叫此方法,此處先不做關心

……

}

}

completePauseLocked方法顧名思義是用來完成對活動的pause操作和下個活動的resume操作。首先對mPausingActivity進行pause操作,將此活動的狀態置為PAUSED,然後根據活動的finishing已經為true,則呼叫finishCurrentActivityLocked用來完成finish操作。然後,呼叫resumeFocusedStackTopActivityLocked函式來resume下一個activity。最後還是呼叫ensureActivitiesVisibleLocked校正所有Task中的Activity的狀態。

3)finishCurrentActivityLocked(ActivityStack.java)方法實現

 final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj,String reason) {

        final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(

                true /* considerKeyguardState */);

        if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)

                && next != null && !next.nowVisible) {

            if (!mStackSupervisor.mStoppingActivities.contains(r)) {

                addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);

            }

            r.setState(STOPPING, "finishCurrentActivityLocked");

            if (oomAdj) {

                mService.updateOomAdjLocked();

            }

            return r;

        }

        // make sure the record is cleaned out of other places.將此活動從個數據結構中移除

        mStackSupervisor.mStoppingActivities.remove(r);

        mStackSupervisor.mGoingToSleepActivities.remove(r);

        mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(r);

        final ActivityState prevState = r.getState();

……

        if (mode == FINISH_IMMEDIATELY || (prevState == PAUSED && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))

                || finishingActivityInNonFocusedStack || prevState == STOPPING || prevState == STOPPED

                || prevState == ActivityState.INITIALIZING) {

            r.makeFinishingLocked();

            boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason);

            if (finishingActivityInNonFocusedStack) {

                if(next != null){

                    mStackSupervisor.ensureVisibilityAndConfig(next, next.getDisplayId(),//mDisplayId,

                            false /* markFrozenIfConfigChanged */, true /* deferResume */);

                }

            }

            if (activityRemoved) {

                mStackSupervisor.resumeFocusedStackTopActivityLocked();

            }

……

        mStackSupervisor.resumeFocusedStackTopActivityLocked();

        return r;

    }

 finishCurrentActivityLocked方法用來finish當前活動。首先要確保下一個要resumed的活動是否已經是可見的,如果不是,則需要推遲finishing當前活動。如果確認此時就要finish則呼叫destroyActivityLocked將當前活動remove掉;需要注意的是,如果Finnish的activity不在當前fouced的stack中,我們需要ensureVisibilityAndConfig方法來設定顯示相關的引數。成功remove掉此活動後需要呼叫resumeFocusedStackTopActivityLocked來resume下一個活動。

4)destroyActivityLocked(ActivityStack.java)方法實現

cleanUpActivityLocked,此方法關鍵是呼叫removeActivityFromHistoryLocked方法來將activity從history中移除,有兩種方式,一種是立即移除,一種是傳送延時訊息,在1s內移除。

   5)removeActivityFromHistoryLocked方法實現

      此方法用來將其從task中移除,設定acitivityrecord中的各個屬性值,移除activity的windowcontainer等操作。需要注意的是如果最後一個活動的話,還需要將其所在的task也移除。

   再貼程式碼的話這個就太長了,此處就先省略這兩個方法實現的具體程式碼,如果後續再遇到其他問題,在詳細瞭解。

  1. 問題分析及解決思路

本體出現問題的堆疊資訊列印如下

java.lang.IllegalArgumentException: No display found with id: -1

at com.android.server.am.ActivityStackSupervisor.getDisplayOverrideConfiguration(ActivityStackSupervisor.java:470)

at com.android.server.am.ActivityStackSupervisor.ensureVisibilityAndConfig(ActivityStackSupervisor.java:1650)有改動

at com.android.server.am.ActivityStack.finishCurrentActivityLocked(ActivityStack.java:3877) //

at com.android.server.am.ActivityStack.completePauseLocked(ActivityStack.java:1568)

at com.android.server.am.ActivityStack.activityPausedLocked(ActivityStack.java:1534)

at com.android.server.am.ActivityStack$ActivityStackHandler.handleMessage(ActivityStack.java:398)

at android.os.Handler.dispatchMessage(Handler.java:106)

at android.os.Looper.loop(Looper.java:193)

at android.os.HandlerThread.run(HandlerThread.java:65)

    at com.android.server.ServiceThread.run(ServiceThread.java:44)

No display found with id: -1此異常為系統主動丟擲的異常,對應的程式碼為

      Configuration getDisplayOverrideConfiguration(int displayId) {

        final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);

        if (activityDisplay == null) {

            throw new IllegalArgumentException("No display found with id: " + displayId);

        }

        return activityDisplay.getOverrideConfiguration();

}

  此函式用來查詢引數displayId對應的Display相關引數資訊,getActivityDisplayOrCreateLocked方法用來獲取對應的ActivityDisplay例項。根據異常列印的資訊可以看到此時的要查詢的displayId的值為-1。在Display類中,定義為-1的值為INVALID_DISPLAY,即非法的DISPLAY,故此時通過getActivityDisplayOrCreateLocked函式無法查詢到對應的ActivityDisplay例項,導致此異常的丟擲。

那麼此處我們需要找到將displayid置為-1的位置,在我們上述分析的pausetimeout流程中,呼叫finishCurrentActivityLocked函式來finish當前活動並resume下一個活動。而此異常出現的地方在ensureVisibilityAndConfig,在呼叫此方法之前我們先呼叫了destroyActivityLocked方法,具體程式碼如下

  final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj, String reason) {

            ……….

            boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason);

            if (finishingActivityInNonFocusedStack) {

                // Finishing activity that was in paused state and it was in not currently focused

                // stack, need to make something visible in its place.

                mStackSupervisor.ensureVisibilityAndConfig(next, mDisplayId,

                        false /* markFrozenIfConfigChanged */, true /* deferResume */);

            }

            if (activityRemoved) {

                mStackSupervisor.resumeFocusedStackTopActivityLocked();

            }

……

}
在destroyActivityLocked方法中,通過removeActivityFromHistoryLocked-> removeTask-> remove-> removeFromDisplay等一系列呼叫,將displayid置為INVALID_DISPLAY,如下

    private void removeFromDisplay() {

        final ActivityDisplay display = getDisplay();

        if (display != null) { display.removeChild(this);}

        mDisplayId = INVALID_DISPLAY;

    }

具體有以下log證明此流程是這樣走的

09-18 05:57:38.779   912   929 W ActivityManager: Activity pause timeout for ActivityRecord{31811a3 u0 caller.id.phone.number.block/com.android.blue.DialtactsActivity t2261 f}

09-18 05:57:38.866   912   929 I am_destroy_activity: [0,51909027,2261,caller.id.phone.number.block/com.android.blue.DialtactsActivity,finish-imm:completedPausedLocked] //對應destroyActivityLocked方法的呼叫

09-1805:57:38.880    912   929 V WindowManager: Removing focused app token:AppWindowToken{27d6059 token=Token{db15ca0 ActivityRecord

{31811a3 u0 caller.id.phone.number.block/com.android.blue.DialtactsActivity t2261}}}

09-18 05:57:38.899   391   997 W SurfaceFlinger: Attempting to destroy on removed layer: AppWindowToken{27d6059 token=Token{db15ca0 ActivityRecord                           {31811a3 u0 caller.id.phone.number.block/com.android.blue.DialtactsActivity t2261}}}#0 

09-18 05:57:38.903   912   929 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: ActivityManager //此異常發生

相應的activity及其所在的task的流程

09-18 05:57:34.615   912  5435 I am_create_activity: [0,51909027,2261,caller.id.phone.number.block/com.android.blue.DialtactsActivity,android.intent.action.MAIN,NULL,NULL,270532608]

09-18 05:57:34.632   912  5435 I wm_task_moved: [2261,0,2147483647]

09-18 05:57:34.948   912   924 I am_restart_activity: [0,51909027,2261,caller.id.phone.number.block/com.android.blue.DialtactsActivity]

09-18 05:57:38.271   912  1648 I am_finish_activity: [0,51909027,2261,caller.id.phone.number.block/com.android.blue.DialtactsActivity,force-crash]

09-18 05:57:38.866   912   929 I am_destroy_activity: [0,51909027,2261,caller.id.phone.number.block/com.android.blue.DialtactsActivity,finish-imm:completedPausedLocked]

09-18 05:57:38.880   912   929 I wm_task_removed: [2261,removeAppToken: last token]

09-18 05:57:38.882   912   929 I am_remove_task: [2261,2252]

09-18 05:57:38.883   912   929 I wm_task_removed: [2261,removeTask]

如何修改此題

ensureVisibilityAndConfig方法的具體實現為

  boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,

            boolean markFrozenIfConfigChanged, boolean deferResume) {

          ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,

              false /* preserveWindows */, false /* notifyClients */);

          final Configuration config = mWindowManager.updateOrientationFromAppTokens(

                getDisplayOverrideConfiguration(displayId),

                starting != null && starting.mayFreezeScreenLocked(starting.app)

                        ? starting.appToken : null,

                displayId, true /* forceUpdate */);

        if (starting != null && markFrozenIfConfigChanged && config != null) {

            starting.frozenBeforeDestroy = true;

        }

        // Update the configuration of the activities on the display.

        return mService.updateDisplayOverrideConfigurationLocked(config, starting, deferResume,

                displayId);

    }

此方法為P上新增的方法,作用是通過ensureActivitiesVisibleLocked方法確保activity可見,呼叫updateDisplayOverrideConfigurationLocked來設定activity的顯示相關引數。O上原先在此處只調用了ensureActivitiesVisibleLocked方法。實際上我們看問題發生時的呼叫

             if (finishingActivityInNonFocusedStack) {//此stack已經不是當前的焦點所在了,DisplayID為INVALID_DISPLAY也是合理的

   mStackSupervisor.ensureVisibilityAndConfig(next, mDisplayId,

                        false /* markFrozenIfConfigChanged */, true /* deferResume */);

            }

  從ensureActivitiesVisibleLocked方法的作用來看,此處傳入的第二個引數displayId應該為第一個引數ActivityRecord所在的stack的displayId,即next所在的stack的引數,不應該為當前stack的,故此處考慮將其改為

               if (finishingActivityInNonFocusedStack) {

                   if(next != null){

                       mStackSupervisor.ensureVisibilityAndConfig(next , next. getDisplayId(),

                            false /* markFrozenIfConfigChanged */, true /* deferResume */);

}

              }

如果next為null這種情況出現也不要緊,因為緊跟在後面會呼叫mStackSupervisor.resumeFocusedStackTopActivityLocked();方法,此方法中會再次呼叫ensureActivitiesVisibleLocked方法。