我们知道在启动一个新Activity(B)的时候,会先执行旧的Activity(A)的onPause,之后才会启动新的Activity;我们都知道的是如果在A的onPause中执行耗时操作,那么肯定会影响到B的启动,如果此时A的onPasuse一直执行耗时操作,那么B还能启动吗?
答案是B也能启动,但是会延时至少500ms。让我们通过源码来看下AMS在启动B之前,暂定A的时候做了什么。
//Andrdoid2.3.7 ActivityStack.java
private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
if (mPausingActivity != null) {
RuntimeException e = new RuntimeException();
Slog.e(TAG, "Trying to pause when pause is already pending for "
+ mPausingActivity, e);
}
ActivityRecord prev = mResumedActivity;
if (prev == null) {
RuntimeException e = new RuntimeException();
Slog.e(TAG, "Trying to pause when nothing is resumed", e);
resumeTopActivityLocked(null);
return;
}
if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
mResumedActivity = null;
mPausingActivity = prev;
mLastPausedActivity = prev;
prev.state = ActivityState.PAUSING;
prev.task.touchActiveTime();
mService.updateCpuStats();
if (prev.app != null && prev.app.thread != null) {
if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev);
try {
EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
System.identityHashCode(prev),
prev.shortComponentName);
prev.app.thread.schedulePauseActivity(prev, prev.finishing, userLeaving,
prev.configChangeFlags);
if (mMainStack) {
mService.updateUsageStats(prev, false);
}
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
mPausingActivity = null;
mLastPausedActivity = null;
}
} else {
mPausingActivity = null;
mLastPausedActivity = null;
}
// If we are not going to sleep, we want to ensure the device is
// awake until the next activity is started.
if (!mService.mSleeping && !mService.mShuttingDown) {
mLaunchingActivity.acquire();
if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) {
// To be safe, don't allow the wake lock to be held for too long.
Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG);
mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT);
}
}
if (mPausingActivity != null) {
// Have the window manager pause its key dispatching until the new
// activity has started. If we're pausing the activity just because
// the screen is being turned off and the UI is sleeping, don't interrupt
// key dispatch; the same activity will pick it up again on wakeup.
if (!uiSleeping) {
prev.pauseKeyDispatchingLocked();
} else {
if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off");
}
// Schedule a pause timeout in case the app doesn't respond.
// We don't give it much time because this directly impacts the
// responsiveness seen by the user.
Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);
msg.obj = prev;
mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete...");
} else {
// This activity failed to schedule the
// pause, so just treat it as being paused now.
if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next.");
resumeTopActivityLocked(null);
}
}
在调用schedulePauseActivity之后,还发送了一个延时消息PAUSE_TIMEOUT_MSG,该消息对应的处理代码如下:
//Andrdoid2.3.7 ActivityStack.java
final Handler mHandler = new Handler() {
//public Handler() {
// if (localLOGV) Slog.v(TAG, "Handler started!");
//}
public void handleMessage(Message msg) {
switch (msg.what) {
case PAUSE_TIMEOUT_MSG: {
IBinder token = (IBinder)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 " + token);
activityPaused(token, null, true);
} break;
case IDLE_TIMEOUT_MSG: {
if (mService.mDidDexOpt) {
mService.mDidDexOpt = false;
Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);
nmsg.obj = msg.obj;
mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT);
return;
}
// We don't at this point know if the activity is fullscreen,
// so we need to be conservative and assume it isn't.
IBinder token = (IBinder)msg.obj;
Slog.w(TAG, "Activity idle timeout for " + token);
activityIdleInternal(token, true, null);
} break;
case DESTROY_TIMEOUT_MSG: {
IBinder token = (IBinder)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 " + token);
activityDestroyed(token);
} break;
case IDLE_NOW_MSG: {
IBinder token = (IBinder)msg.obj;
activityIdleInternal(token, false, null);
} break;
case LAUNCH_TIMEOUT_MSG: {
if (mService.mDidDexOpt) {
mService.mDidDexOpt = false;
Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG);
mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT);
return;
}
synchronized (mService) {
if (mLaunchingActivity.isHeld()) {
Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
mLaunchingActivity.release();
}
}
} break;
case RESUME_TOP_ACTIVITY_MSG: {
synchronized (mService) {
resumeTopActivityLocked(null);
}
} break;
}
}
};
仔细看相关分支,调用了 activityPaused(token, null, true),第三个参数表示是否超时,这里为true,表示超时了;而通过正常暂停回调到AMS的代码如下:
//Android2.3.7 ActivityManagerService.java
public final void activityPaused(IBinder token, Bundle icicle) {
// Refuse possible leaked file descriptors
if (icicle != null && icicle.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
final long origId = Binder.clearCallingIdentity();
mMainStack.activityPaused(token, icicle, false);
Binder.restoreCallingIdentity(origId);
}
看到没,activityPaused的第三个参数为false,表示未超时;
最后为了流程的完整性,接续看下activityPause做了啥,代码如下:
//Android2.3.7 ActivityStack.java
final void activityPaused(IBinder token, Bundle icicle, boolean timeout) {
if (DEBUG_PAUSE) Slog.v(
TAG, "Activity paused: token=" + token + ", icicle=" + icicle
+ ", timeout=" + timeout);
ActivityRecord r = null;
synchronized (mService) {
int index = indexOfTokenLocked(token);
if (index >= 0) {
r = (ActivityRecord)mHistory.get(index);
if (!timeout) {
r.icicle = icicle;
r.haveState = true;
}
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
r.state = ActivityState.PAUSED;
completePauseLocked();
} else {
EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE,
System.identityHashCode(r), r.shortComponentName,
mPausingActivity != null
? mPausingActivity.shortComponentName : "(none)");
}
}
}
}
拿到栈顶的ActivityRecord对象r,与mPausingActivity做对比,由于mPausingActivity是之前保存起来的,与就是同一个对象,因此执行completePauseLocked继续启动B。
好了到此over,我们只是关注onPause会不会无限的影响之后Activity的启动,到这里已经从源码中找到答案。
注意:由于onPause是在主线程中执行的,所以虽然能正常启动新Activity,但是毕竟主线程已经在旧的Activity中阻塞,所以新Activity啥也做不了,直接黑屏卡死。