android:configChange

本文详细解析了Android系统中配置变更的处理流程,包括AMS如何响应屏幕旋转等配置变化,并根据Activity的配置变更属性决定是否重启Activity。

1. 当转屏时,系统会调用ams.setRequestedOrientation

    public void setRequestedOrientation(IBinder token,
            int requestedOrientation) {
        synchronized (this) {
            ActivityRecord r = mMainStack.isInStackLocked(token);
            if (r == null) {
                return;
            }
            ......
            mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
            Configuration config = mWindowManager.updateOrientationFromAppTokens(
                    mConfiguration,
                    r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
            if (config != null) {
                r.frozenBeforeDestroy = true;
                if (!updateConfigurationLocked(config, r, false, false)) {
                    mMainStack.resumeTopActivityLocked(null);
                }
            }
            ......
        }
    }
   
2. 调用ams.updateConfigurationLocked

 boolean updateConfigurationLocked(Configuration values,
            ......       
        boolean kept = true;
       
        if (values != null) {
            Configuration newConfig = new Configuration(mConfiguration);
            changes = newConfig.updateFrom(values);
            if (changes != 0) {
                ......
                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    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) {
                    }
                }
                ......
        }
       
        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 = mMainStack.topRunningActivityLocked(null);
        }
       
        if (starting != null) {
            kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
            // And we need to make sure at this point that all other activities
            // are made visible with the correct configuration.
            mMainStack.ensureActivitiesVisibleLocked(starting, changes);
        }
       
        if (values != null && mWindowManager != null) {
            mWindowManager.setNewConfiguration(mConfiguration);
        }
       
        return kept;
    }

   会调用activitythread.scheduleConfigurationChanged,在这个方法中会判断是否由Activity自己处理config change,如果是在Activity的confiChange中声明的则由Activity的onConfigChanged方法处理
   接下来调用activitythread.scheduleConfigurationChanged,如果返回false则要销毁当前的activity,调用activitystack.relaunchActivityLocked销毁当前的activity,然后重新创建,;如果返回true则不会销毁当前的activity,进而就不会调用Activity的生命周期回调方法,当前发生改变的config是在Activity的configChange属性中声明的则说明由Activity的onConfigChanged来处理,不销毁当前activity
  
final boolean ensureActivityConfigurationLocked(ActivityRecord r,
            int globalChanges) {
        ......
        if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
            // 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,
                        "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 {
                //销毁重建
                relaunchActivityLocked(r, r.configChangeFlags, false);
                r.configChangeFlags = 0;
            }

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

        return true;
    }
  

3. 在ams.updateConfigurationLocked返回false的情况下(即创建新的Activity),调用activitystack.resumeTopActivityLocked显示新创建的Activity

<think> 嗯,用户的问题集中在Android开发中的一个具体痛点:明明按照文档设置了configChanges属性并重写了onConfigurationChanged方法,但Activity仍然意外重启。这确实是个值得深入探讨的问题。 用户提供的引用材料很全面,涵盖了configChanges属性的官方说明和常规用法[^1][^2][^3][^4][^5],特别是注意到引用[4]详细列举了所有可配置项的含义。不过用户显然已经跳过了基础认知阶段,直接进入故障排查环节——这说明ta可能是个有经验的开发者,正在调试实际项目。 从技术角度看,这个问题需要分层排查。首先想到四个关键点:Android版本差异(尤其是4.0前后对screenSize的处理差异)、配置项冲突(比如fontScale和uiMode的组合影响)、系统资源重载机制(如WebView等特殊组件会强制重启Activity)、以及代码实现细节(是否所有配置项都正确处理了)。 用户特别强调组合使用了screenSize|keyboardHidden|fontScale|uiMode|locale这些属性,这提示需要检查两个冷门配置项:fontScale在系统字体大小改变时可能触发资源重载,而uiMode在深色/浅色模式切换时存在已知问题。引用[4]提到过这些参数,但没说明组合使用时的优先级问题。 突然想到还有个隐藏陷阱:如果Activity使用了透明主题或分屏模式,系统可能忽略configChanges设置。这点在官方文档角落里提到过,但用户提供的引用里没体现。需要提醒用户检查manifest里的activity主题配置。 解决方案设计上,应该优先推荐用户开启ViewModels配合SavedStateHandle来保存状态,这是现代Android开发的最佳实践。同时要警告直接重写onSaveInstanceState的风险——就像引用[5]暗示的那样,某些情况下系统可能绕过这个回调。 最后生成的三个问题都聚焦在进阶场景:冷启动兼容性考虑(针对Android 12+的SplashScreen API)、多窗口模式适配(现在越来越普遍)、以及如何模拟配置变更进行测试(开发者容易忽略的环节)。特别是第二个关于深色模式的问题,结合了用户设置的uiMode配置项,应该能切中ta的实际需求。 </think> 在Android中配置`android:configChanges`后Activity仍意外重启,通常由以下原因及解决方案导致: ### 根本原因分析 1. **Android版本兼容性问题** - Android 4.0(API 14)以下需用 `keyboardHidden` 替代 `screenSize`(引用[1][4]) - 若未适配旧版本: ```xml <!-- 旧设备兼容方案 --> android:configChanges="orientation|keyboardHidden|screenSize|fontScale|uiMode|locale" ``` 2. **配置项遗漏或冲突** - 必须包含所有可能触发重启的配置(引用[4]): ```xml <!-- 终极防护方案 --> android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation |screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize" ``` 3. **系统资源重载** - `uiMode`(深色/浅色模式切换)或 `fontScale` 变更时会强制重载资源(引用[4]) - **解决方案**:在`onConfigurationChanged()`中手动更新资源: ```java @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 手动重载UI元素 setContentView(R.layout.activity_main); recreateUIComponents(); // 自定义UI重建方法 } ``` 4. **特殊组件触发重启** - WebView、SurfaceView等组件会强制Activity重建(即使配置了configChanges) - **规避方案**:在`onSaveInstanceState()`中保存组件状态,通过`onRestoreInstanceState()`恢复 ### 高级解决方案 ```java // 终极防护代码示例 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("WEBVIEW_STATE", webView.saveState().toString()); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); webView.restoreState(savedInstanceState.getBundle("WEBVIEW_STATE")); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 关键:手动处理所有配置变更 if ((newConfig.uiMode ^ getResources().getConfiguration().uiMode) == Configuration.UI_MODE_NIGHT_MASK) { applyDarkMode(); // 处理深色模式切换 } if (newConfig.fontScale != getResources().getConfiguration().fontScale) { adjustFontScaling(); // 调整字体缩放 } } ``` ### 验证步骤 1. 在`AndroidManifest`中声明所有配置项(如前文终极方案) 2. 重写`onConfigurationChanged()`并添加日志: ```java Log.d("ConfigChange", "Triggered: " + newConfig.toString()); ``` 3. 检查logcat确认方法是否被调用 4. 若仍重启,检查是否包含`Fragment`(需在Fragment中同样处理配置变更) > **经验提示**:Android 10+ 对多窗口模式(分屏/自由窗口)的适配需额外关注`screenLayout|screenSize`变更,此时建议配合`onMultiWindowModeChanged()`处理(引用[4])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值