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 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker11 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952712 小时前
Andorid Google 登录接入文档
android
黄林晴14 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android