android:configChanges分析

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处理流程

java 复制代码
<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指定的改变
    
java 复制代码
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。

java 复制代码
<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。

java 复制代码
<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才能包含以上这两个配置。

java 复制代码
<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。

相关推荐
拭心11 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
行者彡11 小时前
gitee别人仓库再上传自己仓库
gitee
sin220111 小时前
git推送本地仓库到远程(Gitee)
git·gitee
带电的小王13 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡13 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道13 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库14 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道15 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe15 小时前
Android Hook - 动态加载so库
android
居居飒16 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin