您的位置:首页 > 移动开发

APP横屏机制

2016-11-08 19:23 337 查看
启动横屏应用时的整个逻辑:首先会从WindowManagerService那边获取屏幕的方向,然后再设置到ActivityManagerService中来,最后再启动Window的显示逻辑。
这三个步骤分别对应下面这三个函数(横屏最重要的三个调用函数):

(1). WindowManagerService.updateRotationUncheckedLocked()
(2). ActivityManagerService.updateConfigurationLocked(config, r, false, false)
(3). WindowManagerService.setNewConfiguration(mConfiguration)
这三个函数是配套使用的。对于转屏应用,首先是

我们找一个具体的转屏场景来分析,启动横屏activity。先给出时序图:



上图的调用也验证了前面所说的转屏会走的三个步骤。注意一下调用updateOrientationFromAppTokens()函数时,会先调用r.mayFreezeScreenLocked(r.app)函数,这个函数判断该activity是否已经跟目标进程关联,并且关联的进程正常运行,如果满足,那么就将ActivityRecord.appToken作为参数。
step1、WMS.updateOrientationFromAppTokens()

[html]
view plain
copy





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()

[html]
view plain
copy





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)

[html]
view plain
copy





/*
* 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()函数:

[html]
view plain
copy





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的需求?看看这个函数中调用mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation);就清楚了,mForcedAppOrientation在前面重新赋值过了。哈哈,现在整个逻辑就清楚了,如果应用需要update
new orientation,但是此刻WMS侧又无法update orientation成功,那么会在其他逻辑处理完成后调用updateRotationUnchecked()继续把update orientation工作做完,updateRotationUnchecked()函数调用updateRotationUncheckedLocked()更新orientation,如果确实更新了orientation,文章最开始就说了三个步骤缺一不可,第二个跟第三个步骤在哪调用呢?哈哈,就在sendNewConfiguration()中

[html]
view plain
copy





void sendNewConfiguration() {
try {
mActivityManager.updateConfiguration(null);
} catch (RemoteException e) {
}
}

主动调用AMS的updateConfiguration()函数,updateConfiguration()函数中必然调用了第三个步骤:WindowManagerService.setNewConfiguration(mConfiguration)。

step4、updateRotationUncheckedLocked()
更新 new orientation 到WMS中来,如果更新失败,那么返回false,更新成功则返回true。如果更新失败,那么在其他逻辑完成后调用 updateRotationUnchecked()时会重新update orientation到WMS中来,正如step3中所说的mForcedAppOrientation是个关键变量,保存着request orientation。

[html]
view plain
copy





// TODO(multidisplay): Rotate any display?
/**
* Updates the current rotation.
*
* Returns true if the rotation has been changed. In this case YOU
* MUST CALL sendNewConfiguration() TO UNFREEZE THE SCREEN.
*/
public boolean updateRotationUncheckedLocked(boolean inTransaction) {
if (mDeferredRotationPauseCount > 0) { //①如果调用了pauseRotationLocked()来pauses rotation changes,那么mDeferredRotationPauseCount值会加1,此时便不能change rotation;待调用resumeRotationLocked()将mDeferredRotationPauseCount值减为0,便会调用updateRotationUncheckedLocked()再次change rotation;
// Rotation updates have been paused temporarily. Defer the update until
// updates have been resumed.
if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, rotation is paused.");
return false;
}

ScreenRotationAnimation screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { //②如果此刻正在做屏幕旋转动画,也是不能change rotation的;
// Rotation updates cannot be performed while the previous rotation change
// animation is still in progress. Skip this update. We will try updating
// again after the animation is finished and the display is unfrozen.
if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, animation in progress.");
return false;
}

if (!mDisplayEnabled) { //③灭屏时也是不能change rotation的;
// No point choosing a rotation if the display is not enabled.
if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, display is not enabled.");
return false;
}

// TODO: Implement forced rotation changes.
// Set mAltOrientation to indicate that the application is receiving
// an orientation that has different metrics than it expected.
// eg. Portrait instead of Landscape.

int rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation); //④根据mForcedAppOrientation和mRotation、传感器方向等值来综合考虑出orientation。mForcedAppOrientation保存着request orientation,mRotation是当前正在使用的屏幕方向。
boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
mForcedAppOrientation, rotation);

if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Application requested orientation "
+ mForcedAppOrientation + ", got rotation " + rotation
+ " which has " + (altOrientation ? "incompatible" : "compatible")
+ " metrics");
}

if (mRotation == rotation && mAltOrientation == altOrientation) { //⑤如果综合出来的orientation与last orientation相同,便无需update orientation;
// No change.
return false;
}

if (DEBUG_ORIENTATION) {
Slog.v(TAG,
"Rotation changed to " + rotation + (altOrientation ? " (alt)" : "")
+ " from " + mRotation + (mAltOrientation ? " (alt)" : "")
+ ", forceApp=" + mForcedAppOrientation);
}
//⑥以下逻辑是需要update orientation;
mRotation = rotation;
mAltOrientation = altOrientation;
mPolicy.setRotationLw(mRotation); //⑦将mRotation设置到PhoneWindowManager中;

mWindowsFreezingScreen = true; //⑧准备冻结屏幕;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
displayContent.layoutNeeded = true; //⑨layoutNeeded为true;这样needsLayout()函数就可以返回true;
final int[] anim = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
} else {
mPolicy.selectRotationAnimationLw(anim); //⑨PhoneWindowManager选择进入和退出转屏动画;
}
startFreezingDisplayLocked(inTransaction, anim[0], anim[1]); //⑨调用startFreezingDisplayLocked()冻结屏幕,参数中传入了进入和退出动画,这个函数在下面将详细分析;
// startFreezingDisplayLocked can reset the ScreenRotationAnimation.
screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); //⑨获取DisplayContentsAnimator类对象,这个类中包含一个ScreenRotationAnimation类对象;

// We need to update our screen size information to match the new
// rotation. Note that this is redundant with the later call to
// sendNewConfiguration() that must be called after this function
// returns... however we need to do the screen size part of that
// before then so we have the correct size to use when initializing
// the rotation animation for the new rotation.
computeScreenConfigurationLocked(null); //⑨更新DisplayInfo信息;

final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
if (SHOW_TRANSACTIONS) {
Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked");
}
SurfaceControl.openTransaction();
}
try {
// NOTE: We disable the rotation in the emulator because
// it doesn't support hardware OpenGL emulation yet.
if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
&& screenRotationAnimation.hasScreenshot()) { //⑨create Rotation Matrix and set Matrix to mSurfaceControl(截图surface)
if (screenRotationAnimation.setRotationInTransaction(
rotation, mFxSession,
MAX_ANIMATION_DURATION, mTransitionAnimationScale,
displayInfo.logicalWidth, displayInfo.logicalHeight)) {
scheduleAnimationLocked();
}
}

mDisplayManagerService.performTraversalInTransactionFromWindowManager(); //⑨这个是干啥的??
} finally {
if (!inTransaction) {
SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
}
}

final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) { //⑨对于未销毁surface的window,将WindowState.mOrientationChanging设为true;
WindowState w = windows.get(i);
if (w.mHasSurface
// FLYME_BEGIN
// FUNCTION:optimize the efficiency of rotating screen . added by fujinzhi. transplanted by duzhenhui.
/*&& !w.prohibitRotation()*/
// FLYME_END 2014.04.17
) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w);
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
}
w.mLastFreezeDuration = 0;
}

for (int i=mRotationWatchers.size()-1; i>=0; i--) { //⑨将rotation发布到PhoneWindow、KeyguardFaceUnlockView、LegacySensorManager中去;重点是PhoneWindow,这个将在下面详细分析;
try {
mRotationWatchers.get(i).watcher.onRotationChanged(rotation);
} catch (RemoteException e) {
}
}

//TODO (multidisplay): Magnification is supported only for the default display.
if (mDisplayMagnifier != null
&& displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) { //⑨如果打开手势放大,那么回调mDisplayMagnifier.onRotationChangedLocked()函数;
mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation);
}

return true;
}

这个函数是WMS侧的屏幕旋转逻辑主要处理函数,WMS侧几乎所有的屏幕旋转操作都在此函数中完成。AMS侧自然是updateConfigurationLocked()函数。

1)、PhoneWindowManager.setRotationLw(int rotation)将rotation传到PhoneWindowManager中去

[html]
view plain
copy





@Override
public void setRotationLw(int rotation) {
mOrientationListener.setCurrentRotation(rotation);
}

mOrientationListener是一个MyOrientationListener extends WindowOrientationListener类对象,rotation保存在mCurrentRotation中:

[html]
view plain
copy





/**
* Sets the current rotation.
*
* @param rotation The current rotation.
*/
public void setCurrentRotation(int rotation) {
synchronized (mLock) {
mCurrentRotation = rotation;
}
}

2)、startFreezingDisplayLocked(inTransaction, anim[0], anim[1])函数,现在来详细研究下这个函数。

[html]
view plain
copy





private void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim) {
if (mDisplayFrozen) { //①mDisplayFrozen变量为true,表示正在调用startFreezingDisplayLocked()冻结屏幕,并且还未调用stopFreezingDisplayLocked()进行解冻;
return;
}

if (!mDisplayReady || !mPolicy.isScreenOnFully()) { //②系统还未ready或灭屏状态(PhoneWindowManager.mScreenOnFully==false);
// No need to freeze the screen before the system is ready or if
// the screen is off.
return;
}

mScreenFrozenLock.acquire(); //③获得一把唤醒锁,防止freeze screen期间系统灭屏并锁屏,锁屏会更改orientation;

mDisplayFrozen = true;
mDisplayFreezeTime = SystemClock.elapsedRealtime();
mLastFinishedFreezeSource = null;

mInputMonitor.freezeInputDispatchingLw(); //④freeze Input Dispatcher,也就是禁止触摸时间上报;

// Clear the last input window -- that is just used for
// clean transitions between IMEs, and if we are freezing
// the screen then the whole world is changing behind the scenes.
mPolicy.setLastInputMethodWindowLw(null, null);

if (mAppTransition.isTransitionSet()) { //④如果设置了activity切换动画,那么调用mAppTransition.freeze()设置mAppTransitionState=APP_STATE_READY;
mAppTransition.freeze();
}

if (PROFILE_ORIENTATION) {
File file = new File("/data/system/frozen");
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}

if (CUSTOM_SCREEN_ROTATION) {
mExitAnimId = exitAnim;
mEnterAnimId = enterAnim;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final int displayId = displayContent.getDisplayId();
ResSchedulerManager mResSchedulerManager;
ScreenRotationAnimation screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(displayId);
if (screenRotationAnimation != null) { //⑤如果上次转屏动画还在播放,那么kill掉;
screenRotationAnimation.kill();
}
mResSchedulerManager = (ResSchedulerManager) mContext
.getSystemService(Context.RES_SCHEDULER_SERVICE);
mResSchedulerManager.dispatchSwitchPerfModeCMD(mResSchedulerManager.APP_ROTATE);

// TODO(multidisplay): rotation on main screen only.
screenRotationAnimation = new ScreenRotationAnimation(this,mContext, displayContent,
mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(),mDisableScreenRotationForHdmi);
mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); //⑥设置一个ScreenRotationAnimation对象到DisplayContentsAnimator.mScreenRotationAnimation中;
}
}

总结起来这个函数就是静止灭屏、冻结触摸事件、创建ScreenRotationAnimation对象。难道这就是冻结屏幕???

3)、computeScreenConfigurationLocked(),来分析下。

[html]
view plain
copy





boolean computeScreenConfigurationLocked(Configuration config) {
if (!mDisplayReady) {
return false;
}

// TODO(multidisplay): For now, apply Configuration to main screen only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();

// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
|| mRotation == Surface.ROTATION_270); //①根据mRotation值,计算出屏幕宽度和高度;mRotation值在是在调用该函数之前已经更新;
final int realdw = rotated ?
displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
final int realdh = rotated ?
displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
int dw = realdw;
int dh = realdh;

if (mAltOrientation) {
if (realdw > realdh) {
// Turn landscape into portrait.
int maxw = (int)(realdh/1.3f);
if (maxw < realdw) {
dw = maxw;
}
} else {
// Turn portrait into landscape.
int maxh = (int)(realdw/1.3f);
if (maxh < realdh) {
dh = maxh;
}
}
}

if (config != null) {
config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
Configuration.ORIENTATION_LANDSCAPE;
}

// Update application display metrics.
final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
synchronized(displayContent.mDisplaySizeLock) {
displayInfo.rotation = mRotation;
displayInfo.logicalWidth = dw;
displayInfo.logicalHeight = dh;
displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
displayInfo.appWidth = appWidth;
displayInfo.appHeight = appHeight;
displayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
displayInfo.getAppMetrics(mDisplayMetrics);
mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
}
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}

final DisplayMetrics dm = mDisplayMetrics;
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm,
mCompatDisplayMetrics);

if (config != null) {
config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation)
/ dm.density);
config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation)
/ dm.density);
computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, dm.density, config);

config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh);
config.densityDpi = displayContent.mBaseDisplayDensity;

// Update the configuration based on available input devices, lid switch,
// and platform configuration.
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.navigation = Configuration.NAVIGATION_NONAV;

int keyboardPresence = 0;
int navigationPresence = 0;
final InputDevice[] devices = mInputManager.getInputDevices();
final int len = devices.length;
for (int i = 0; i < len; i++) {
InputDevice device = devices[i];
if (!device.isVirtual()) {
final int sources = device.getSources();
final int presenceFlag = device.isExternal() ?
WindowManagerPolicy.PRESENCE_EXTERNAL :
WindowManagerPolicy.PRESENCE_INTERNAL;

if (mIsTouchDevice) {
if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
InputDevice.SOURCE_TOUCHSCREEN) {
config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
}
} else {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
}

if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
config.navigation = Configuration.NAVIGATION_TRACKBALL;
navigationPresence |= presenceFlag;
} else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
&& config.navigation == Configuration.NAVIGATION_NONAV) {
config.navigation = Configuration.NAVIGATION_DPAD;
navigationPresence |= presenceFlag;
}

if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
config.keyboard = Configuration.KEYBOARD_QWERTY;
keyboardPresence |= presenceFlag;
}
}
}

// Determine whether a hard keyboard is available and enabled.
boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
if (hardKeyboardAvailable != mHardKeyboardAvailable) {
mHardKeyboardAvailable = hardKeyboardAvailable;
mHardKeyboardEnabled = hardKeyboardAvailable;
mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
}
if (!mHardKeyboardEnabled) {
config.keyboard = Configuration.KEYBOARD_NOKEYS;
}

// Let the policy update hidden states.
config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}

return true;
}

总结:这个函数其实就是根据mRotation值更新DisplayInfo、config.orientation值。

step5、AMS.updateConfigurationLocked()

[html]
view plain
copy





/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
* configuration. Returns true if the activity has been left running, or
* false if <var>starting</var> is being destroyed to match the new
* configuration.
* @param persistent TODO
*/
boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent, boolean initLocale) {
// do nothing if we are headless
if (mHeadless) return true;

int changes = 0;

if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
Slog.i(TAG, "Updating configuration to: " + values);
}

EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);

if (values.locale != null && !initLocale) {
saveLocaleLocked(values.locale,
!values.locale.equals(mConfiguration.locale),
values.userSetLocale);
}

mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig; //①将mConfiguration更新到最新config;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);

final Configuration configCopy = new Configuration(mConfiguration);

// TODO: If our config changes, should we auto dismiss any currently
// showing dialogs?
mShowDialogs = shouldShowDialogs(newConfig);

AttributeCache ac = AttributeCache.instance();
if (ac != null) {
ac.updateConfiguration(configCopy);
}

// Make sure all resources in our process are updated
// right now, so that anyone who is going to retrieve
// resource values after we return will be sure to get
// the new ones. This is especially important during
// boot, where the first config change needs to guarantee
// all resources have that config before following boot
// code is executed.
mSystemThread.applyConfigurationToResources(configCopy); //②将config更新到ResourcesManager中去;

if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); //③post一个update消息,该消息处理函数中调用Settings.System.putConfiguration()保存config;
msg.obj = new Configuration(configCopy);
mHandler.sendMessage(msg);
}

for (int i=mLruProcesses.size()-1; i>=0; i--) { //④将横屏config通过调用ActivityThread.scheduleConfigurationChanged()传递每个应用进程中去,这个将在下面进行详细分析;
ProcessRecord app = mLruProcesses.get(i);
try {
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+ app.processName + " new config " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, //⑤发送一个config changed广播;
null, AppOpsManager.OP_NONE, false, false, MY_PID,
Process.SYSTEM_UID, UserHandle.USER_ALL);
if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
}

boolean kept = true;
final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
if (changes != 0 && starting == null) { //⑥如果start 横屏Activity时,参数starting就是正要启动的横屏Activity;如果是旋转屏幕,starting 就为null,此时把top Activity取出来;
// If the configuration changed, and the caller is not already
// in the process of starting an activity, then find the top
// activity to check if its configuration needs to change.
starting = mainStack.topRunningActivityLocked(null);
}

if (starting != null) {
kept = mainStack.ensureActivityConfigurationLocked(starting, changes); //⑦将config设置到starting中去,下面会详细分析这个函数;
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes); //⑧调用ensureActivitiesVisibleLocked()
}

if (values != null && mWindowManager != null) {
mWindowManager.setNewConfiguration(mConfiguration); //⑨调用WindowManagerService.setNewConfiguration()启动performLayoutAndPlaceSurfacesLocked()
}

return kept;
}

先来详细分析下第④点中将config传递到每个应用进程中去ActivityThread.scheduleConfigurationChanged(),这个函数调用ActivityThead.handleConfigurationChanged()进一步处理

[html]
view plain
copy





final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {

int configDiff = 0;

synchronized (mResourcesManager) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
config = mPendingConfiguration;
mCurDefaultDisplayDpi = config.densityDpi;
updateDefaultDensity();
}
mPendingConfiguration = null;
}

if (config == null) {
return;
}

if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);

mResourcesManager.applyConfigurationToResourcesLocked(config, compat); //①将config更新到应用进程的ResourcesManager中去

if (mConfiguration == null) {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
}
configDiff = mConfiguration.diff(config);
mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
}

ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);

// Cleanup hardware accelerated stuff
WindowManagerGlobal.getInstance().trimLocalMemory();

freeTextLayoutCachesIfNeeded(configDiff);

if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config);
}
}
}

再来分析下第⑦点中调用的ActivityStack.ensureActivityConfigurationLocked()函数。

[html]
view plain
copy





/**
* Make sure the given activity matches the current configuration. Returns
* false if the activity had to be destroyed. Returns true if the
* configuration is the same, or the activity will remain running as-is
* for whatever reason. Ensures the HistoryRecord is updated with the
* correct configuration and all other bookkeeping is handled.
*/
final boolean ensureActivityConfigurationLocked(ActivityRecord r,
int globalChanges) {
if (mConfigWillChange) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Skipping config check (will change): " + r);
return true;
}

if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Ensuring correct configuration: " + r);

// Short circuit: if the two configurations are the exact same
// object (the common case), then there is nothing to do.
Configuration newConfig = mService.mConfiguration;
if (r.configuration == newConfig && !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration unchanged in " + r);
return true;
}

// We don't worry about activities that are finishing.
if (r.finishing) { //①正在finish的Activity则无需关心;
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration doesn't matter in finishing " + r);
r.stopFreezingScreenLocked(false);
return true;
}

// Okay we now are going to make this activity have the new config.
// But then we need to figure out how it needs to deal with that.
Configuration oldConfig = r.configuration;
r.configuration = newConfig; //②更新ActivityRecord.configuration值;

// Determine what has changed. May be nothing, if this is a config
// that has come back from the app after going idle. In that case
// we just want to leave the official config object now in the
// activity and do nothing else.
final int changes = oldConfig.diff(newConfig);
if (changes == 0 && !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration no differences in " + r);
return true;
}

// If the activity isn't currently running, just leave the new
// configuration and it will pick that up next time it starts.
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration doesn't matter not running " + r);
r.stopFreezingScreenLocked(false);
r.forceNewConfig = false;
return true;
}

// Figure out how to handle the changes between the configurations.
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x"
+ Integer.toHexString(changes) + ", handles=0x"
+ Integer.toHexString(r.info.getRealConfigChanged())
+ ", newConfig=" + newConfig);
}
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
// Aha,
r.configChangeFlags |= changes;
r.startFreezingScreenLocked(r.app, globalChanges);
r.forceNewConfig = false;
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Config is destroying non-running " + r);
destroyActivityLocked(r, true, false, "config");
} else if (r.state == ActivityState.PAUSING) {
// A little annoying: we are waiting for this activity to
// finish pausing. Let's not do anything now, but just
// flag that it needs to be restarted when done pausing.
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Config is skipping already pausing " + r);
r.configDestroy = true;
return true;
} else if (r.state == ActivityState.RESUMED) {
// Try to optimize this case: the configuration is changing
// and we need to restart the top, resumed activity.
// Instead of doing the normal handshaking, just say
// "restart!".
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Config is relaunching resumed " + r);
relaunchActivityLocked(r, r.configChangeFlags, true);
r.configChangeFlags = 0;
} else {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Config is relaunching non-resumed " + r);
relaunchActivityLocked(r, r.configChangeFlags, false);
r.configChangeFlags = 0;
}

// All done... tell the caller we weren't able to keep this
// activity around.
return false;
}

// Default case: the activity can handle this new configuration, so
// hand it over. Note that we don't need to give it the new
// configuration, since we always send configuration changes to all
// process when they happen so it can just use whatever configuration
// it last got.
if (r.app != null && r.app.thread != null) {
try {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r);
r.app.thread.scheduleActivityConfigurationChanged(r.appToken); //③调用ActivityThread.scheduleActivityConfigurationChanged();
} catch (RemoteException e) {
// If process died, whatever.
}
}
r.stopFreezingScreenLocked(false);

return true;

原文地址
http://blog.csdn.net/guoqifa29/article/details/40504189
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: