本文基于Android 13源码分析
目的
在Android 系统中,屏幕切换,语言切换,昼夜色切换后,默认情况下会触发Activity的重启,这可能会影响用户体验,如果是导航app,则会影响导航的连贯性,如果app依赖其他的模块如3D模型等,这会导致模型黑屏,重新加载缓慢等体验很差的效果.同时为了保存和恢复状态,还不得不重写onSaveInstanceState和onRestoreInstanceState,非常繁琐.
而onConfigurationChanged回调就是为了解决这类问题的,不会导致Activity销毁和重建.通过一个回调接口来刷新UI回调,我们只需要在回调中手动刷新UI和资源,保证应用使用的连贯性,也简化了开发逻辑
怎么使用
onConfigurationChanged使用时和订阅发布模型类似,Activity向系统订阅自己关心的配置变化, 在AndroidManifest.xml中,加入
<activity
android:name=".MainActivity"
android:exported="true"
// 向系统订阅昼夜色主题变化,屏幕旋转,多语言切换
android:configChanges="uiMode|screenSize|orientation|locale"
>没有在configChanges中订阅的参数,当系统变更后Activity则会销毁重建.
在Activity中重写onConfigurationChanged函数
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// newConfig新配置,可以读取屏幕旋转之后的方向,切换之后的语言
}在函数中我们可以处理自己的业务逻辑了:
多语言切换:调用代码重新加载string资源并setText更新UI
屏幕旋转:判断屏幕方向,加载不同的布局,定制化刷新UI
源码关键流程分析
总时序图
我们先整理看下系统配置变更到回调通知的整体流程图,然后分步解析其中的关键源码
第一阶段通知Application config变更
第二阶段通知Activity config变更
Transaction跨进程流程
Android 9(P)之后逐渐加入了Transaction机制
将上面三个流程图合并
配置变更
先看看触发onConfigurationChange的场景,这里例举两种情况
屏幕旋转
屏幕旋转主要类是DisplayRotation,接着通过DisplayContent#sendNewConfiguration产生回调
base/services/core/java/com/android/server/wm/DisplayContent.java
void sendNewConfiguration() {
...
final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
if (configUpdated) {
return;
}
...
}
boolean updateDisplayOverrideConfigurationLocked() {
...
Configuration values = new Configuration();
computeScreenConfiguration(values);
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}updateDisplayOverrideConfigurationLocked中,新建了一个Configuration,然后通过computeScreenConfiguration计算出最新的屏幕配置信息,然后调用updateDisplayOverrideConfigurationLocked
base/services/core/java/com/android/server/wm/DisplayContent.java
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
ActivityRecord starting, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
try {
if (values != null) {
if (mDisplayId == DEFAULT_DISPLAY) {
// 1.更新application全局config
changes = mAtmService.updateGlobalConfigurationLocked(values,
false /* initLocale */, false /* persistent */,
UserHandle.USER_NULL /* userId */);
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
}
if (!deferResume) {
// 2.更新activity config
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
mAtmService.continueWindowLayout();
}
return kept;
}1.这里也调用了ATMS的updateGlobalConfigurationLocked方法,调用流程同时序图中步骤2
2.后调用了ATMS的ensureConfigAndVisibilityAfterUpdate方法,时序同步骤13
昼夜色切换
在SystemUI 下拉栏中切换黑暗模式,调用setNightModeActivated触发configChange
base/services/core/java/com/android/server/UiModeManagerService.java
@Override
public boolean setNightModeActivated(boolean active) {
return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
}
private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
...
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
...
// 更新config对象
updateConfigurationLocked();
// 调用ATMS更新配置
applyConfigurationExternallyLocked();
persistNightMode(mCurrentUser);
return true;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
updateConfigurationLocked函数主要是更新mConfiguration.uiMode字段
applyConfigurationExternallyLocked函数调用ActivityTaskManager.getService().updateConfiguration(mConfiguration)发起跨进程调用,进入ATMS流程,即时序图的步骤1
更新Application全局配置
入口是ATMS的updateGlobalConfigurationLocked,
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
// 1.读取原始Configuration
mTempConfig.setTo(getGlobalConfiguration());
...
// 2.根据pid读取所有正在运行的进程
SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
for (int i = pidMap.size() - 1; i >= 0; i--) {
final int pid = pidMap.keyAt(i);
final WindowProcessController app = pidMap.get(pid);
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
+ "config %s", app.mName, mTempConfig);
// 3
app.onConfigurationChanged(mTempConfig);
}
// 4.广播
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::broadcastGlobalConfigurationChanged,
mAmInternal, changes, initLocale);
mH.sendMessage(msg);
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
return changes;
}这里的
getGlobalConfiguration()获取的是原始Configuration,保存到mTempConfig读取所有正在运行的process, 存在mProcessMap对象里边
遍历所有进程,然后将Configuration交给其WindowProcessController 处理,WindowProcessController 是ConfigurationContainer的子类
发了一个系统广播
com.android.server.am.ActivityManagerService.LocalService
public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
@Override
public void broadcastGlobalConfigurationChanged(int changes, boolean initLocale) {
synchronized (ActivityManagerService.this) {
Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
...
}WindowProcessController中的跨进程调用
通过ApplicationThread 和应用进程交互,接下来就交给App进程处理了
base/services/core/java/com/android/server/wm/WindowProcessController.java
void dispatchConfiguration(Configuration config) {
mHasPendingConfigurationChange = false;
...
try {
config.seq = mAtm.increaseConfigurationSeqLocked();
//时序步骤5
mAtm.getLifecycleManager().scheduleTransaction(mThread,
ConfigurationChangeItem.obtain(config));
setLastReportedConfiguration(config);
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
}
}跨进程对象ApplicationThread
ATMS跨进程调用到app,跨进程传输的binder对象是mThread(接口IApplicationThread),实现类是ActivityThread中的私有类private class ApplicationThread extends IApplicationThread.Stub
这个binder对象也是通过IActivityManager binder接口从ActivityThread传递到ATMS的
base/core/java/android/app/ActivityThread.java
@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
// 将跨进程接口传递到AMS
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
}ConfigurationChangeItem
时序步骤5发起了ConfigurationChangeItem的一个transaction,此时已经是在应用进程执行任务了,ActivityThread中有一个Handler专门用于处理各种事件,handler收到EXECUTE_TRANSACTION消息后开始执行execute函数,对应时序步骤6
base/core/java/android/app/servertransaction/ConfigurationChangeItem.java
public class ConfigurationChangeItem extends ClientTransactionItem {
private Configuration mConfiguration;
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
client.handleConfigurationChanged(mConfiguration);
}
}时序步骤7在ActivityThread中,handleConfigurationChanged直接委托给ConfigurationController处理,实现职责分离
base/core/java/android/app/ActivityThread.java
public void handleConfigurationChanged(Configuration config) {
mConfigurationController.handleConfigurationChanged(config);
}新配置信息分发到Application
ConfigurationController会收集进程中的Application,Provider,Service组件(步骤9),然后触发其onConfigurationChanged回调
void handleConfigurationChanged(@Nullable Configuration config,
@Nullable CompatibilityInfo compat) {
...
// 收集组件
final ArrayList<ComponentCallbacks2> callbacks =
mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {
final int size = callbacks.size();
for (int i = 0; i < size; i++) {
ComponentCallbacks2 cb = callbacks.get(i);
if (!equivalent) {
performConfigurationChanged(cb, config);
}
}
}
}参数false表示不包含Activity, Activity涉及到前后台切换configChanges配置,在第二阶段处理,performConfigurationChanged调用函数cb.onConfigurationChanged(configToReport);
更新Activity配置
第二阶段是处理Activity的变更,ATMS入口函数是ensureConfigAndVisibilityAfterUpdate时序13
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
boolean kept = true;
final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
// mainRootTask is null during startup.
if (mainRootTask != 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 = mainRootTask.topRunningActivity();
}
if (starting != null) {
kept = starting.ensureActivityConfiguration(changes,
false /* preserveWindow */);
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mRootWindowContainer.ensureActivitiesVisible(starting, changes,
!PRESERVE_WINDOWS);
}
}
return kept;
}首先会获取top activity,然后交给ActivityRecord处理ensureActivityConfiguration
判断可见性
在ensureActivityConfiguration中,首先判断了Activity的可见性,以及生命周期的状态,下边这些状态都不会继续分发配置变更,比如Stop,Destroy,Finish,以及不可见(不只是onPaused状态,内部通过TASK_FRAGMENT_VISIBILITY_VISIBLE来判断,比如锁屏遮挡等)
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
...
if (finishing) {
return true;
}
if (isState(DESTROYED)) {
return true;
}
if (!ignoreVisibility && (mState == STOPPING || mState == STOPPED || !shouldBeVisible())) {
return true;
}
...
if (mState == INITIALIZING) {
return true;
}
...
}
决策是否重启
判断完Activity可见性之后,接着判断是否需要重启,函数shouldRelaunchLocked会读取Manifest.xml配置的属性值
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
final Task rootTask = getRootTask();
...
if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
if (mState == 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.
deferRelaunchUntilPaused = true;
preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else {
relaunchActivityLocked(preserveWindow);
}
return false;
}
...
}在重启的场景下,如果是Pausing状态,重启则会延迟,否则的话直接重启,Activity对象销毁重建,生命周期先onDestroy然后onCreate
onConfigurationChange回调
不满足重启条件,则进入scheduleConfigurationChanged函数
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
final Task rootTask = getRootTask();
...
if (displayChanged) {
scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
// 走onConfigurationChanged
scheduleConfigurationChanged(newMergedOverrideConfig);
}
stopFreezingScreenLocked(false);
return true;
}
private void scheduleConfigurationChanged(Configuration config) {
try {
//跨进程进入应用进程
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
ActivityConfigurationChangeItem.obtain(config));
} catch (RemoteException e) {
// If process died, whatever.
}
}进入ActivityThread之后,触发的函数是handleActivityConfigurationChanged, 触发的流程和上面的ConfigurationChangeItem逻辑一样
ActivityThread中Configuration处理
时序图
Activity Configuration transaction
base/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@Override
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
}ClientTransactionHandler 的子类是ActivityThread
handleActivityConfigurationChanged实现如下
base/core/java/android/app/ActivityThread.java
@Override
public void handleActivityConfigurationChanged(ActivityClientRecord r,
@NonNull Configuration overrideConfig, int displayId) {
...
// Perform updates.
r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
// 1 通知activity config changed
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mConfigurationController.getCompatConfiguration(),
movedToDifferentDisplay ? displayId : r.activity.getDisplayId());
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
if (viewRoot != null) {
if (movedToDifferentDisplay) {
viewRoot.onMovedToDisplay(displayId, reportedConfig);
}
// 2.通知View config changed
viewRoot.updateConfiguration(displayId);
}
mSomeActivitiesChanged = true;
}新的config会更新到 r.overrideConfig变量,接下来分别通知activity和view更新config
通知Activity config变更
base/core/java/android/app/ActivityThread.java
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig, int displayId) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
r.tmpConfig, r.overrideConfig, displayId);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}在performConfigurationChangedForActivity函数里调用了performActivityConfigurationChanged
base/core/java/android/app/ActivityThread.java
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId) {
...
final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
// Apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable
// in many places.
// 1.onConfigurationChanged参数传入的Configuration
final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
contextThemeWrapperOverrideConfig);
if (movedToDifferentDisplay) {
activity.dispatchMovedToDisplay(displayId, configToReport);
}
if (shouldReportChange) {
activity.mCalled = false;
activity.onConfigurationChanged(configToReport);
// 2
if (!activity.mCalled) {
throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
" did not call through to super.onConfigurationChanged()");
}
}
return configToReport;
}这里可以看到传入onConfigurationChanged的config参数的创建
重写的onConfigurationChanged必须调用super函数,否则会抛异常,Activity中会将将标记置为
mCalled = true;
通知Fragment config变更
在Activity中将config 变化通知到所有fragment
base/core/java/android/app/Activity.java
public void onConfigurationChanged(@NonNull Configuration newConfig) {
mCalled = true;
mFragments.dispatchConfigurationChanged(newConfig);
}通知View config变更
在ActivityThread 中通知Activity配置改变之后立即调用 viewRoot.updateConfiguration(displayId);通知View配置变更
base/core/java/android/view/ViewRootImpl.java
public void updateConfiguration(int newDisplayId) {
if (mView == null) {
return;
}
// Handle configuration change.
if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
...
//1
mView.dispatchConfigurationChanged(config);
mForceNextWindowRelayout = true;
requestLayout();
}
updateForceDarkMode();
}分发到View#onConfigurationChanged函数,所以自定义函数中也可以监听config变化来刷新UI
后台切前台
根据时序13的条件,后台Activity不会继续分发configuration,也不会重启,只有当Activity切换到前台时才会触发配置更新的回调
时序图
TaskFragment中的resumeTopActivity
当Activity回到前台的时候Task会触发TaskFragment的resumeTopActivity函数,触发onConfigurationChanged的时机是在TopResumedActivityChangeItem之后,ResumeActivityItem之前
base/services/core/java/com/android/server/wm/TaskFragment.java
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
boolean deferPause) {
...
next.setState(RESUMED, "resumeTopActivity");
if (shouldBeVisible(next)) {
notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
true /* markFrozenIfConfigChanged */, false /* deferResume */);
}
...
try {
final ClientTransaction transaction =
ClientTransaction.obtain(next.app.getThread(), next.token);
// Deliver all pending results.
ArrayList<ResultInfo> a = next.results;
next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
next.abortAndClearOptionsAnimation();
transaction.setLifecycleStateRequest(
ResumeActivityItem.obtain(next.app.getReportedProcState(),
dc.isNextTransitionForward()));
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
} catch (Exception e) {
}
}分发到DisplayContent
TaskFragment调用ensureVisibilityAndConfig交给RootWindowContainer处理
boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,
boolean markFrozenIfConfigChanged, boolean deferResume) {
...
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
final DisplayContent displayContent = getDisplayContent(displayId);
...
if (displayContent != null) {
// Update the configuration of the activities on the display.
return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
deferResume, null /* result */);
} else {
return true;
}在RootWindowContainer中调用displayContent的updateDisplayOverrideConfigurationLocked函数处理config变更,这个函数的调用iu成在上面屏幕旋转的逻辑中分析过了,接下去的流程就会走 总时序图中的时序2,之后的逻辑就是重复上面分析过的ATMS处理流程了