android:configChanges
- 如果要在android源码确认是否是configChanges导致了Activity重启,建议把ActivityThread.DEBUG_CONFIGURATION改为true。
- Activity无法内部消化此次配置改变时,会调用relaunchActivityLocked方法。不同的是,如果此Activity正在前台,那立即调用;在后台的,则等切到前台后再调用
一、configChanges作用
Android程序在运行时,一些设备的配置可能会改变,如:横竖屏的切换、软键盘的弹出等。这些事件一旦发生,当前活动的Activity会重新启动,其中的过程是:在销毁之前会先调用onSaveInstanceState()方法去保存你应用中的一些数据,然后调用onDestroy()方法,最后调用onCreate()、onStart()、onResume()等方法启动一个新的Activity。
如果想让某些配置在发生改变的时候不重启Activity,需要为Activity添加android:configChanges属性,该属性可以设置多个值,用"|"隔开,例如:“locale|navigation|orientation。设置了android:configChanges属性后,当指定的属性发生变化时,不会去重新启动Activity,而是通知程序去调用Activity的onConfigurationChanged()方法。例如:在进行横竖屏的切换时,会重新启动Activity,而定义了android:configChanges=“orientation|keyboardHidden”,就不会重新启动Activity了,而是去调用onConfigurationChanged()方法。
简言之,在Activity中添加了android:configChanges属性,目的是当android:configChanges所设置的属性值对应的配置属性发生改变时,通知程序调用 onConfigurationChanged()函数,而不会重启Activity。
二、android:configChanges属性可以指定的值
- mcc(0x0001):国际移动用户识别码所属国家代号改变了-----sim被侦测到了,去更新mcc,mcc是移动用户所属国家代号
- mnc(0x0002):国际移动用户识别码的移动网号码改变了------sim被侦测到了,去更新mnc,MNC是移动网号码,最多由两位数字组成,用于识别移动用户所归属的移动通信网
- locale(0x0004):语言改变了-----用户选择了一个新的语言,一般和layoutDirection同时发生
- touchscreen(0x0008):触摸屏改变了------通常是不会发生的
- keyboard(0x0010):键盘发生了改变----例如用户用了外部的键盘
- keyboardHidden(0x0020):键盘的可用性发生了改变
- navigation(0x0040):导航发生了变化-----通常也不会发生
- orientation(0x0080):屏幕方向改变了
- screenLayout(0x0100):屏幕的显示发生了变化------不同的显示被激活
- uiMode(0x0200): 用户的模式发生了变化
- screenSize(0x0400):屏幕大小改变了
- smallestScreenSize(0x0800):屏幕的物理大小改变了,如:连接到一个外部的屏幕上
- layoutDirection(0x2000):文字书写朝向变化----一般和locale同时发生
- fontScale(0x40000000):字体比例发生了变化----选择了不同的全局字体
三、(android-12)frameworks处理流程
<aosp>/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
------
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
......
// changes类型是int,存储着此次发生的配置改变。举个例子:修改语言后,changes是0x2004。
// shouldRelaunchLocked用于判断Activify是否可以内部消化掉changes指示的改变
if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
// 重启分支。Activify无法消化此次改变,或强制要求重启(forceNewConfig==true)
....
if (mState == PAUSING) {
...
// 如果Activity此时为Pause状态,先设置一个标志位
deferRelaunchUntilPaused = true;
preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else {
...
relaunchActivityLocked(preserveWindow);
}
// All done... tell the caller we weren't able to keep this activity around.
return false;
}
...
}
shouldRelaunchLocked用于判断app是否可以内部消化掉changes指定的改变
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
int configChanged = info.getRealConfigChanged();
...
// changes存储着此次发生的配置改变。
// configChanged存储着此个Activity在android:configChanges写的可消化改变。
return (changes&(~configChanged)) != 0;
}
}
假设用户在“设置”修改了语言,像从英文改为中文,这时android会依次调用各个正运行着的Activity的ensureActivityConfiguration。一个ActivityRecord对象对应一个Activity,成员变量packageName存储着此Activity归属app的Application ID,像com.kos.launcher,成员函数ensureActivityConfiguration也就对应一个Activity的处理过程。
在ensureActivityConfiguration,首先得到此次发生的配置改变值0x2004,值存储在变量changes,然后调用shouldRelaunchLocked,判断是否能内部消化掉这次改变。shouldRelaunchLocked逻辑分两步,第一步调用info.getRealConfigChanged()读出android:configChanges写的那改变值0x00C4,值存储在configChanged,第二步用“(changes&(~configChanged)) != 0”判断此次发生的改变(changes)是否有的不在configChanged。如果有,像示例的0x2004和0x00C4,shouldRelaunchLocked返回true,表示内部没法消化掉此次改变,否则返回false。
如果内部没法消化掉,或调用者要求一定要重启(forceNewConfig==true),那进入重启分支。在这分支,如果此app的生命周期正处于暂停状态,像运行在后台,则把deferRelaunchUntilPaused设为true。否则立即调用relaunchActivityLocked。
<aosp>/frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java
------
void completePause(boolean resumeNext, ActivityRecord resuming) {
...
if (prev != null) {
...
if (prev.finishing) {
...
} else if (prev.hasProcess()) {
...
if (prev.deferRelaunchUntilPaused) {
...
// 这里是关键,如果deferRelaunchUntilPaused为true,调用重启
prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
}
...
}
...
}
...
}
当Activity从Pause到Resume状态过渡时,像从后台切到前台,一旦发现deferRelaunchUntilPaused是true,会调用relaunchActivityLocked。综上所述:Activity无法内部消化此次配置改变时,会调用relaunchActivityLocked方法。不同的是,如果此Activity正在前台,那立即调用;在后台的,则等切到前台后再调用。
relaunchActivityLocked执行着重启流程。内中如何转的就不分析了,最终会调用handleRelaunchActivityInner。
<aosp>/frameworks/base/core/java/android/app/ActivityThread.java
------
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
...
// 销毁此个Activity
handleDestroyActivity(r, false, configChanges, true, reason);
...
// 启动此个Activity
handleLaunchActivity(r, pendingActions, customIntent);
}
四、确保配置改变不会导致重启Activity
按上面规则,只要在android:configChanges写上所有可能的配置改变,那无论配置怎么改变都不会导致该Activity重启。那这里为什么还要提这个问题呢?——我不知道如何写android:configChanges才能包含所有配置改变。
<aosp>/frameworks/base/core/java/android/content/pm/ActivityInfo.java ------ public static final int CONFIG_ASSETS_PATHS = 0x80000000; public static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000;
在Android-12,不知道如何写android:configChanges才能包含以上这两个配置。
<aosp>/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
------
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
...
if (packageName != null && packageName.equals("com.kos.launcher")) {
return false;
}
return (changes&(~configChanged)) != 0;
}
如果android:configChanges无法包含所有改变,但又要确保配置改变不会导致重启Activity,可以修改shouldRelaunchLocked。发现是白名单中的app时,强制返回false,即认为内部能消化掉,不必重启此Activity。