启动横屏应用时的整个逻辑:首先会从WindowManagerService那边获取屏幕的方向,然后再设置到ActivityManagerService中来,最后再启动Window的显示逻辑。
这三个步骤分别对应下面这三个函数(横屏最重要的三个调用函数):
(1). WindowManagerService.updateRotationUncheckedLocked()
(2). ActivityManagerService.updateConfigurationLocked(config, r, false, false)
(3). WindowManagerService.setNewConfiguration(mConfiguration)
这三个函数是配套使用的。对于转屏应用,首先是
我们找一个具体的转屏场景来分析,启动横屏activity。先给出时序图:
step1、WMS.updateOrientationFromAppTokens()
public Configuration updateOrientationFromAppTokens(
Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"updateOrientationFromAppTokens()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
Configuration config = null;
long ident = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
config = updateOrientationFromAppTokensLocked(currentConfig,
freezeThisOneIfNeeded);
}
Binder.restoreCallingIdentity(ident);
return config;
}
函数简单调用updateOrientationFromAppTokensLocked(),注意传进来的参数,currentConfig是当前的config信息,freezeThisOneIfNeeded就是前面说的ActivityRecord.appToken,继续往下研究。
step2、updateOrientationFromAppTokensLocked()
private Configuration updateOrientationFromAppTokensLocked(
Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
Configuration config = null;
if (updateOrientationFromAppTokensLocked(false)) { //①满足这个条件是很苛刻的:从函数名就可以看出来是从可见的应用窗口获取orientation,并且orientation与last orientation不同,同时还必须将new orientation成功update到WMS中,也就是updateRotationUncheckedLocked()也要返回TRUE;
if (freezeThisOneIfNeeded != null) {
AppWindowToken atoken = findAppWindowToken(freezeThisOneIfNeeded);
if (atoken != null) { //②屏幕旋转,并且freezeThisOneIfNeeded不为null,那么调用startAppFreezingScreenLocked()冻结screen;
startAppFreezingScreenLocked(atoken, ActivityInfo.CONFIG_ORIENTATION);
}
}
config = computeNewConfigurationLocked(); //③调用computeNewConfigurationLocked()计算config;
} else if (currentConfig != null) { //④什么时候会走这个逻辑呢,具体看step3和step4中return false情况,也就是说没有update new orientation到WMS中。包括,1.可见应用窗口orientation与上一次相同;2.orientation与上一次不同,但是前一次转屏动画还在播放;3.屏幕是灭屏状态;4.PhoneWindowManager策略类综合出的orientation跟上一次相同;;
// No obvious action we need to take, but if our current
// state mismatches the activity manager's, update it,
// disregarding font scale, which should remain set to
// the value of the previous configuration.
mTempConfiguration.setToDefaults(); //⑤下面这些逻辑就是即使不从应用窗口更改orientation,还有其他config需要核对差异。
mTempConfiguration.fontScale = currentConfig.fontScale;
//Flyme Theme: save the theme flag.
mTempConfiguration.themeChanged = currentConfig.themeChanged;
//Flyme Theme: save the theme flag.
if (computeScreenConfigurationLocked(mTempConfiguration)) {
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
displayContent.layoutNeeded = true;
int anim[] = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
} else {
mPolicy.selectRotationAnimationLw(anim);
}
startFreezingDisplayLocked(false, anim[0], anim[1]);
config = new Configuration(mTempConfiguration);
}
}
}
return config;
}
step3、updateOrientationFromAppTokensLocked(false)
/*
* Determine the new desired orientation of the display, returning
* a non-null new Configuration if it has changed from the current
* orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
* setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
* SCREEN. This will typically be done for you if you call
* sendNewConfiguration().
*
* The orientation is computed from non-application windows first. If none of
* the non-application windows specify orientation, the orientation is computed from
* application tokens.
* @see android.view.IWindowManager#updateOrientationFromAppTokens(
* android.os.IBinder)
*/
boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
long ident = Binder.clearCallingIdentity();
try {
int req = getOrientationFromWindowsLocked(); //①从非activity窗口中提取orientation;
if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
req = getOrientationFromAppTokensLocked(); //②从activity窗口中提取orientation;
}
if (req != mForcedAppOrientation) { //③窗口设置的orientation与当前orientation不同,即更改orientation;
mForcedAppOrientation = req; //④这个变量值得更改非常重要;
//send a message to Policy indicating orientation change to take
//action like disabling/enabling sensors etc.,
mPolicy.setCurrentOrientationLw(req); //⑤告诉PhoneWindowManager orientation,这样采取关闭或开启sensor;比如打开一个强制横屏的窗口,那么必然要关闭sensor嘛,如何关闭,自然是关闭sensor的listener了!
if (updateRotationUncheckedLocked(inTransaction)) { //⑥调用updateRotationUncheckedLocked()改变WMS侧屏幕方向,如果确实更新了orientation,那么返回TRUE,如果orientation没有更新,那自然返回false;
// changed
return true;
}
}
return false;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
上面的解释的非常清楚这个函数是干嘛的,就是首先从非activity窗口中计算orientation,如果非activity窗口未指定orientation,那么接着从activity窗口中计算orientation。如果计算的orientation跟last不一样,那么首先调用PhoneWindowManager.setCurrentOrientationLw()打开或关闭sensor的listener;接着调用updateRotationUncheckedLocked()做出屏幕转变后WMS侧的处理逻辑。
上面逻辑中第④点中mForcedAppOrientation的赋值非常非常重要,为什么?因为当前启动的应用需要转屏,但是第⑥点中调用updateRotationUncheckedLocked()在很多场景下是无法update orientation的,比如前一个orientation 动画未播完或Rotation被Deferred等,难道就不update orientation了?当然不是,WMS这边在播完orientation动画、resumeRotation、亮屏、等一系列逻辑下会调用updateRotationUnchecked()函数,该函数会完成前面未完成的update orientation工作。看看updateRotationUnchecked()函数:
public void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
if(DEBUG_ORIENTATION) Slog.v(TAG, "updateRotationUnchecked("
+ "alwaysSendConfiguration=" + alwaysSendConfiguration + ")");
long origId = Binder.clearCallingIdentity();
boolean changed;
synchronized(mWindowMap) {
changed = updateRotationUncheckedLocked(false);
if (!changed || forceRelayout) {
getDefaultDisplayContentLocked().layoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
}
}
if (changed || alwaysSendConfiguration) {
sendNewConfiguration();
}
Binder.restoreCallingIdentity(origId);
}
调用updateRotationUncheckedLocked()函数继续完成WMS侧的update orientation工作,那updateRotationUncheckedLocked()怎么知道之前有过update orientation的需求?看看这个