pausetimeout問題流程分析及解決方案
這次出現問題的堆疊是處理activity的pausetimeout的堆疊,在分析問題之前我們先了解下pausetimeout。下其大致流程如下(Android P程式碼)
- 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方法中移除。
- 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也移除。
再貼程式碼的話這個就太長了,此處就先省略這兩個方法實現的具體程式碼,如果後續再遇到其他問題,在詳細瞭解。
- 問題分析及解決思路
本體出現問題的堆疊資訊列印如下
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方法。