上篇博客我們分析了WMS中的updateRotationUnchecked函數,當旋轉角度有變化時會調用sendNewConfiguration函數。這篇博客我們就來分析下這個函數。
- 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);
- }
AMS的updateConfiguration函數
這個函數最後是調用了AMS的updateConfiguration函數。
- void sendNewConfiguration() {
- try {
- mActivityManager.updateConfiguration(null);
- } catch (RemoteException e) {
- }
- }
我們再來看AMS的updateConfiguration函數,這個時候參數values是空,所以會調用WMS的computeNewConfiguration來獲取一些配置信息。然後會調用updateConfigurationLocked函數。
- public void updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
-
- synchronized(this) {
- if (values == null && mWindowManager != null) {
- // sentinel: fetch the current configuration from the window manager
- values = mWindowManager.computeNewConfiguration();
- }
-
- if (mWindowManager != null) {
- mProcessList.applyDisplaySize(mWindowManager);
- }
-
- final long origId = Binder.clearCallingIdentity();
- if (values != null) {
- Settings.System.clearConfiguration(values);
- }
- updateConfigurationLocked(values, null, false, false);
- Binder.restoreCallingIdentity(origId);
- }
- }
我們再來看updateConfigurationLocked函數,先把Configuration數據保存在mConfiguration,然後會調用ActivityThread的scheduleConfigurationChanged。緊接着會發送ACTION_CONFIGURATION_CHANGED廣播,然後獲取當前最上面活動的Activity,調用ActivityStack的ensureActivityConfigurationLocked函數和ActivityStackSupervisor的ensureActivitiesVisibleLocked函數。
- boolean updateConfigurationLocked(Configuration values,
- ActivityRecord starting, boolean persistent, boolean initLocale) {
- int changes = 0;
-
- if (values != null) {
- Configuration newConfig = new Configuration(mConfiguration);
- changes = newConfig.updateFrom(values);
- if (changes != 0) {
- if (!initLocale && values.locale != null && values.userSetLocale) {
- final String languageTag = values.locale.toLanguageTag();
- SystemProperties.set("persist.sys.locale", languageTag);
- mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
- values.locale));
- }
-
- mConfigurationSeq++;
- if (mConfigurationSeq <= 0) {
- mConfigurationSeq = 1;
- }
- newConfig.seq = mConfigurationSeq;
- mConfiguration = newConfig;//保存數據
- Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
- mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
- //mUsageStatsService.noteStartConfig(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);
- }
-
- mSystemThread.applyConfigurationToResources(configCopy);
-
- if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
- Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
- msg.obj = new Configuration(configCopy);
- mHandler.sendMessage(msg);
- }
-
- for (int i=mLruProcesses.size()-1; i>=0; i--) {
- ProcessRecord app = mLruProcesses.get(i);
- try {
- if (app.thread != null) {
- //調用ActivityThread的scheduleConfigurationChanged函數
- 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,
- null, AppOpsManager.OP_NONE, null, 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);
- if (!mProcessesReady) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- }
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
- }
- }
- }
-
- boolean kept = true;
- final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
- // mainStack is null during startup.
- if (mainStack != null) {
- if (changes != 0 && starting == null) {
- // 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);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
- }
- }
-
- if (values != null && mWindowManager != null) {
- mWindowManager.setNewConfiguration(mConfiguration);
- }
-
- return kept;
- }
我們先來看看WMS的setNewConfiguration函數,主要是調用了performLayoutAndPlaceSurfacesLocked,這個函數主要是刷新系統UI和計算窗口等,我們在後續分析WMS的時候會詳細介紹。這裏就不分析了
- @Override
- public void setNewConfiguration(Configuration config) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setNewConfiguration()")) {
- throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
- }
-
- synchronized(mWindowMap) {
- mCurConfiguration = new Configuration(config);
- if (mWaitingForConfig) {
- mWaitingForConfig = false;
- mLastFinishedFreezeSource = "new-config";
- }
- performLayoutAndPlaceSurfacesLocked();
- }
- }
ActivityThread的handleConfigurationChanged
ActivityThead最後會調用到handleConfigurationChanged函數,然後會調用collectComponentCallbacks函數來統計回調,後面就調用performConfigurationChanged函數。
- 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;
- }
-
- mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
-
- if (mConfiguration == null) {
- mConfiguration = new Configuration();
- }
- if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
- return;
- }
-
- configDiff = mConfiguration.updateFrom(config);
- config = applyCompatConfiguration(mCurDefaultDisplayDpi);
- }
-
- ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
-
- freeTextLayoutCachesIfNeeded(configDiff);
-
- if (callbacks != null) {
- final int N = callbacks.size();
- for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
- }
- }
- }
最後在performConfigurationChanged中會調用回調的onConfigurationChanged函數。最後也會調用到應用的Activity的onConfigurationChanged函數中。
- private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
- // Only for Activity objects, check that they actually call up to their
- // superclass implementation. ComponentCallbacks2 is an interface, so
- // we check the runtime type and act accordingly.
- Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
- if (activity != null) {
- activity.mCalled = false;
- }
-
- boolean shouldChangeConfig = false;
- if ((activity == null) || (activity.mCurrentConfig == null)) {
- shouldChangeConfig = true;
- } else {
-
- // If the new config is the same as the config this Activity
- // is already running with then don't bother calling
- // onConfigurationChanged
- int diff = activity.mCurrentConfig.diff(config);
- if (diff != 0) {
- // If this activity doesn't handle any of the config changes
- // then don't bother calling onConfigurationChanged as we're
- // going to destroy it.
- if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
- shouldChangeConfig = true;
- }
- }
- }
- if (shouldChangeConfig) {
- cb.onConfigurationChanged(config);
-
- if (activity != null) {
- if (!activity.mCalled) {
- throw new SuperNotCalledException(
- "Activity " + activity.getLocalClassName() +
- " did not call through to super.onConfigurationChanged()");
- }
- activity.mConfigChangeFlags = 0;
- activity.mCurrentConfig = new Configuration(config);
- }
- }
- }
ActivityStack的ensureActivityConfigurationLocked函數
我們繼續回到AMS的updateConfigurationLocked函數,看如下代碼,先獲取最上面的ActivityRecord,然後調用ActivityStack的ensureActivityConfigurationLocked函數。
- if (mainStack != null) {
- if (changes != 0 && starting == null) {
- // 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);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
- }
- }
ensureActivityConfigurationLocked函數如下,先要滿足一定條件(需要在AndroidManifest.xml中配置下config change相關的配置,我們可以看下之前的博客http://blog.csdn.net/kc58236582/article/details/53665682)根據當前Activity狀態來調用relaunchActivityLocked(這個函數又會調用ActivityThread的scheduleRelaunchActivity函數重新啓動Activity)或者destroyActivityLocked函數。隨後調用ActivityThread的scheduleActivityConfigurationChanged。
- final boolean ensureActivityConfigurationLocked(ActivityRecord r,
- int globalChanges) {
- ......
-
- if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {//從這句判斷知道需要在AndroidManifest中配置的
- // Aha, the activity isn't handling the change, so DIE DIE DIE.
- 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_CONFIGURATION,
- "Config is destroying non-running " + r);
- destroyActivityLocked(r, true, "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_CONFIGURATION,
- "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_CONFIGURATION,
- "Config is relaunching resumed " + r);
- relaunchActivityLocked(r, r.configChangeFlags, true);
- r.configChangeFlags = 0;
- } else {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "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: We only forward the stack override configuration as the system level configuration
- // changes is always sent to all processes when they happen so it can just use whatever
- // system level configuration it last got.
- if (r.app != null && r.app.thread != null) {
- try {
- if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending new config to " + r);
- r.app.thread.scheduleActivityConfigurationChanged(
- r.appToken, new Configuration(mOverrideConfig));
- } catch (RemoteException e) {
- // If process died, whatever.
- }
- }
- r.stopFreezingScreenLocked(false);
-
- return true;
- }
這樣應用就會重新啓動等(完成隨系統旋轉)。