在Activity中定义android:configChanges=”orientation”,这样当横竖屏切换时,可以在onConfigurationChanged监听到改变,同时Activity不会被重启。不知道大家是否想过,为什么如果不定义orientation时,系统需要重启Activity呢?
这还要从资源说起,Android应用程序在资源的定义中,会为不同的屏幕尺寸,语言,横竖屏定义不同的资源文件,存放不同的资源。以保证应用程序可以适配不同的屏幕,语言,横竖屏等等。而Android系统如何从当前应用程序中获取最合适的资源来显示呢?
下面是官网给的Android系统从应用程序中获取资源的流程:官方链接
图中第二步:table中包含MCC, MNC, Language等18个资源获取的维度,table说的是资源配置表,有了这个资源配置表,Android系统就可以从aapt打包生成的resources.arsc中通过上面图标中的步骤,找到最合适的资源。而这个资源配置表,就是通过Configuration来设置的。
再回到系统为什么需要重启Activity的问题上,比如当Configuration中语言(local)发生变化时,也就是说当前获取最佳资源的维度发生了变化,也就是说在Configuration变化之前已经启动的Activity上显示的资源已经不是最合适当前配置的资源了,因此系统需要更新资源配置,清除之前配置缓存的资源,然后重启已经启动的Activity以便显示更新Configuration配置的资源。
接下来就以语言的变化流程,看系统是如何更新配置,清除缓存资源,然后重启已经启动的Activity。
1 Setting应用中更换语言时:调用LocalePicker.updateLocale
public static void updateLocale(Locale locale) {
IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
config.setLocale(locale);
config.userSetLocale = true;
am.updateConfiguration(config);
}
这个函数主要获取AMS代理对象,然后从AMS中获取Configuration对象config,设置config中改变的对象locale也就是语言,并设置userSetLocale为true, 最有通过AMS的代理对象调用AMS的updateConfiguration函数。
2 AMS的updateConfiguration函数主要是调用updateConfigurationLocked()函数。
这个函数比较长,分三个部分来分析:
1 初始化和其他设置阶段:
boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent, boolean initLocale) {
int changes = 0;
if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
if (!initLocale && values.locale != null && values.userSetLocale) {
final String languageTag = values.locale.toLanguageTag();
SystemProperties.set(“persist.sys.locale”, languageTag);
mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, values.locale));
}
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = new Configuration(configCopy);
mHandler.sendMessage(msg);
A 创建newConfig = new Configuration(mConfiguration);
在Configuration构造函数中会调用setTo函数,把mConfiguration的各个成员变量赋值到newConfig中。
在Configuration的成员变量中,包含mcc(移动国家编码), mnc(移动网络编码), locale(语言),keyboardHidden(键盘显隐),等等,每一个都是一个资源获取的维度,每一个都影响当前应用中最合适资源的匹配。
同时在ActivityInfo中还定义了,CONFIG_MCC,CONFIG_MNC,CONFIG_LOCALE等静态整型变量,这些整型变量与Configuration的成员变量一一对应,并最终以位的形式标记,新旧Configuration哪些成员变量发生了变化。
B 调用Configuration. updateFrom通过改变的Configuration对象values更新newConfig,同时返回整数change,change就是以位的形式标记values和newConfig哪些成员变量发生了变化,在语言设置的流程中必然整数change的CONFIG_LOCALE位为”1”,标记当前语言发生了变化。
C 因为Configuration的local成员变量发生了变化,所以change不为0,接下来,
通过发送消息SEND_LOCALE_TO_MOUNT_DAEMON_MSG 设置local到MountService,
设置newConfig 的mConfigurationSeq
提交newConfig 到UsageStatsService(统计服务)
发送消息UPDATE_CONFIGURATION_MSG 把改变的Configuration 保存到设置中。
二部分:通知各个应用进程,Configuration改变:
mSystemThread.applyConfigurationToResources(configCopy);
for (int i=mLruProcesses.size()-1; i>=0; i–) {
ProcessRecord app = mLruProcesses.get(i);
try {
if (app.thread != null) {
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
A 首先调用系统进程(及AMS所在进程)通知configuration改变:mSystemThread就是ActivityThread类对象:
ActivityThread的applyConfigurationToResources函数直接调用了ResourcesManager的函数applyConfigurationToResourcesLocked
final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
}
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
return false;
}
int changes = mResConfiguration.updateFrom(config);
mDisplays.clear();
DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
mResCompatibilityInfo = compat;
changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_SCREEN_SIZE
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
if (config.locale != null) {
Locale.setDefault(config.locale);
}
Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
ApplicationPackageManager.configurationChanged();
Configuration tmpConfig = null;
for (int i = mActiveResources.size() - 1; i >= 0; i--) {
ResourcesKey key = mActiveResources.keyAt(i);
Resources r = mActiveResources.valueAt(i).get();
if (r != null) {
int displayId = key.mDisplayId;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
DisplayMetrics dm = defaultDisplayMetrics;
final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfiguration) {
if (tmpConfig == null) {
tmpConfig = new Configuration();
}
tmpConfig.setTo(config);
if (!isDefaultDisplay) {
dm = getDisplayMetricsLocked(displayId);
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
}
if (hasOverrideConfiguration) {
tmpConfig.updateFrom(key.mOverrideConfiguration);
}
r.updateConfiguration(tmpConfig, dm, compat);
} else {
r.updateConfiguration(config, dm, compat);
}
} else {
mActiveResources.removeAt(i);
}
}
return changes != 0;
}
A1:这个函数前面部分主要是初始化并找出新的Configuration和当前的有哪些改变了并保存在change中,如果语言改变了,设置为新的Configuration的语言。
A2: 调用Resources.updateSystemConfiguration(config, defaultDisplayMetrics,compat);函数更新系统配置在Resources类中有一个Resources的静态成员变量mSystem,主要是指向系统资源的(framework-res.apk)。因此这个函数直接调用mSystem.updateConfiguration()函数:实际上还是调用Resources的updateConfiguration函数:
public void updateConfiguration(Configuration config,
DisplayMetrics metrics, CompatibilityInfo compat) {
synchronized (mAccessLock) {
if (compat != null) {
mCompatibilityInfo = compat;
}
if (metrics != null) {
mMetrics.setTo(metrics);
}
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
final int configChanges = calcConfigChanges(config);
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
mConfiguration.setLayoutDirection(mConfiguration.locale);
}
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
mMetrics.densityDpi = mConfiguration.densityDpi;
mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
String locale = null;
if (mConfiguration.locale != null) {
locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
}
final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
width = mMetrics.heightPixels;
height = mMetrics.widthPixels;
}
final int keyboardHidden;
if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
&& mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
} else {
keyboardHidden = mConfiguration.keyboardHidden;
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
locale, mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
Build.VERSION.RESOURCES_SDK_INT);
mDrawableCache.onConfigurationChange(configChanges);
mColorDrawableCache.onConfigurationChange(configChanges);
mColorStateListCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
flushLayoutCache();
}
Resources成员变量mAssets 是AssetManager类对象主要用于访问资源。
Resources 成员变量mDrawableCache,mColorDrawableCache,mColorStateListCache,mAnimatorCache,mStateListAnimatorCache,主要用于缓存资源的。
这个函数前面主要是初始化获得用于过滤资源访问的资源表,然后调用
A3: mAssets.setConfiguration函数这个函数是一个native函数:
static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
jint mcc, jint mnc, jstring locale, jint orientation, jint touchscreen, jint density,
jint keyboard, jint keyboardHidden, jint navigation, jint screenWidth, jint screenHeight, jint smallestScreenWidthDp, jint screenWidthDp, jint screenHeightDp,
jint screenLayout, jint uiMode, jint sdkVersion){
AssetManager* am = assetManagerForJavaObject(env, clazz);
ResTable_config config;
memset(&config, 0, sizeof(config));
const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
static const jint kScreenLayoutRoundMask = 0x300;
static const jint kScreenLayoutRoundShift = 8;
config.mcc = (uint16_t)mcc;
config.mnc = (uint16_t)mnc;
config.orientation = (uint8_t)orientation;
config.touchscreen = (uint8_t)touchscreen;
config.density = (uint16_t)density;
config.keyboard = (uint8_t)keyboard;
config.inputFlags = (uint8_t)keyboardHidden;
config.navigation = (uint8_t)navigation;
config.screenWidth = (uint16_t)screenWidth;
config.screenHeight = (uint16_t)screenHeight;
config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;
config.screenWidthDp = (uint16_t)screenWidthDp;
config.screenHeightDp = (uint16_t)screenHeightDp;
config.screenLayout = (uint8_t)screenLayout;
config.uiMode = (uint8_t)uiMode;
config.sdkVersion = (uint16_t)sdkVersion;
config.minorVersion = 0;
config.screenLayout2 = (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
am->setConfiguration(config, locale8);
if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
}
A4: 调用缓存资源成员变量的onConfigurationChange函数清除缓存中不合适当前Configuration的资源这里以mDrawableCache为例来分析:
mDrawableCache是DrawableCache类对象,DrawableCache继承自ThemedResourceCache
ThemedResourceCache成员变量:
ArrayMap对象mThemedEntries,主要缓存以Theme为key的资源,
LongSparseArray对象mUnthemedEntries,主要缓存指定不是以Theme来区分的资源,
LongSparseArray对象 mNullThemedEntries,主要缓存以theme为key但是Theme为空的资源。
另外:onConfigurationChange的参数configChanges标记configuration的改变位,是通过前面调用calcConfigChanges得到的,这个函数会先计算出新的Configuration和旧的Configuration的改变位change, 然后调用ActivityInfo.activityInfoConfigToNative将change转换为native的Cofiguration改变为。因为native的改变位和ActivityInfo定义的位不一样。所以需要转换一下,才能匹配后面资源对应的位,检查资源是否会受到configuration的影响。
mDrawableCache.onConfigurationChange,就是调用ThemedResourceCache的onConfigurationChange函数,这个函数直接调用prune(int configChanges)函数:
private boolean prune(int configChanges) {
synchronized (this) {
if (mThemedEntries != null) {
for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
mThemedEntries.removeAt(i);
}
}
}
pruneEntriesLocked(mNullThemedEntries, configChanges);
pruneEntriesLocked(mUnthemedEntries, configChanges);
return mThemedEntries == null && mNullThemedEntries == null
&& mUnthemedEntries == null;
}
}
这个函数主要是遍历mThemedEntries,然后调用pruneEntriesLocked函数:
private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, int configChanges) {
for (int i = entries.size() - 1; i >= 0; i--) {
final WeakReference<T> ref = entries.valueAt(i);
if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
entries.removeAt(i);
}
}
return entries.size() == 0;
}
这个函数主要是遍历entries中每一个资源,如果资源(弱引用)还没有被销毁,然后调用pruneEntryLocked函数检查缓存的资源的敏感配置位和当前configChanges相同。如果相同则表示该资源已经过期,需要重新获取,所以把该资源从entries中移除。
需要解释的是:pruneEntryLocked最终调用的是
public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
}
然后调用Configuration.needNewResources比较新的configChanges和entry. getChangingConfigurations这两个是否有相同的位,如果有表示缓存的资源entry受当前configuration改变影响,所以需要清除。
而entry. getChangingConfigurations其实就是Drawable的成员变量mChangingConfigurations
这个成员变量的初始化,就是在Resources的loadDrawable函数最后一段来设置的
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
A5:回到Resources的updateConfiguration函数中,最后调用flushLayoutCache();函数清除layout文件的缓存。
B 再回到第二部分:
遍历mLruProcesses中的ProcessRecord调用app的成员变量thread的函数app.thread.scheduleConfigurationChanged(configCopy);
成员变量thread,是ActivityThread下的一个本地binder对象ApplicationThread的代理对象。
app.thread.scheduleConfigurationChanged就调用到了各个进程中。
这个函数只是往消息队列里面发送了一条CONFIGURATION_CHANGED的消息。在处理这条消息时就调用到了函数:
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
……
synchronized (mResourcesManager) {
mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
configDiff = mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
}
……
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config);
}
}
}
这个函数主要是调用mResourcesManager.applyConfigurationToResourcesLocked函数:(这个函数比较长但因为有前面的分析,并不难,所以不贴代码了)
B1 在ResourcesManager中有一个成员变量mActiveResources因为一个进程中可能运行多个应用,也就可能包含多个Apk,每一Apk对应一个Resources对象。所以函数主要作用就是,一方面调用Resources.updateSystemConfiguration更新系统Resources的对象(及framework-res.apk)另一方面遍历mActiveResources中每一个Resources对象,便调用Resources.updateConfiguration(前面已近有分析)
B2 调用collectComponentCallbacks函数收集所有实现了ComponentCallbacks2接口的对象callbacks。这个函数也比较简单,ComponentCallbacks2接口就是包含onConfigurationChanged函数,这里收集的实际就是当前进程中运行的所有 Application, Activity,Service,ContentProvider实例。
B3 遍历callbacks调用performConfigurationChanged执行其onConfigurationChanged函数,前提是需要在manifest文件中定义了android:configChanges属性。以语言变化为列,需要定义android:configChanges=”local”
第三部分:重启所有Activity(条件android:configChanges没有包含语言的变化及不包含”local”)
boolean kept = true;
final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
if (mainStack != null) {
if (changes != 0 && starting == null) {
starting = mainStack.topRunningActivityLocked(null);
}
if (starting != null) {
kept = mainStack.ensureActivityConfigurationLocked(starting, changes);
mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
}
}
A 首先调用mainStack.topRunningActivityLocked函数,获得正在启动的starting Activity然后调用mainStack.ensureActivityConfigurationLocked检查是否需要更新新的Configuration。
这个函数比较长,经过缩减代码如下:
final boolean ensureActivityConfigurationLocked(ActivityRecord r,
int globalChanges) {
final Configuration oldConfig = r.configuration;
final Configuration oldStackOverride = r.stackConfigOverride;
r.configuration = newConfig;
r.stackConfigOverride = mOverrideConfig;
……
final int changes = oldConfig.diff(newConfig) | stackChanges;
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
r.configChangeFlags |= changes;
r.startFreezingScreenLocked(r.app, globalChanges);
r.forceNewConfig = false;
if (r.app == null || r.app.thread == null) {
destroyActivityLocked(r, true, "config");
} else if (r.state == ActivityState.PAUSING) {
r.configDestroy = true;
return true;
} else if (r.state == ActivityState.RESUMED) {
relaunchActivityLocked(r, r.configChangeFlags, true);
r.configChangeFlags = 0;
} else {
relaunchActivityLocked(r, r.configChangeFlags, false);
r.configChangeFlags = 0;
}
return false;
}
return true;
}
这个函数缩减部分主要是通过Activity的oldConfig,oldStackOverride,newConfig以及mOverrideConfig合成合适当前Activity的Configuration,最后计算出具体的Configuration的改变位changes。
然后判断if ((changes&(~r.info.getRealConfigChanged())) != 0 ||r.forceNewConfig)如果当前Activity的ActivityInfo中的成员变量configChanges不包含changes,也就是说android:configChanges中不包含”local”或者Activity需要强制更新Configuration。那么接着判断:
(r.app == null || r.app.thread == null)Activity所运行的进程,或者所运行进程中的ApplicationThread(binder代理对象)为空,则说明当前Activity可以销毁了,这调用destroyActivityLocked函数销毁当前Activity。
(r.state == ActivityState.PAUSING)如果当前Activity是暂停中设置r.configDestroy = true;在后面的处理过程中如果configDestroy为true 则会先destoryActivity.
(r.state == ActivityState.RESUMED)如果Activity是ActivityState.RESUMED状态或者其他状态,则都调用relaunchActivityLocked();
relaunchActivityLocked()函数主要就是调用r.app.thread.scheduleRelaunchActivity();
和前面一样就是调用,ApplicationThread的scheduleRelaunchActivity函数来重启这个Activity.具体重启流程大家可以自行查看。
B 调用mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);函数:
void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
final int topStackNdx = stacks.size() - 1;
for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
stack.ensureActivitiesVisibleLocked(starting, configChanges);
}
}
}
mActivityDisplays ArrayList< ActivityDisplay>对象,这是AMS对Activity的管理方式,有时间在专门写一篇Android6.0对Activity的管理以及启动流程的文章,其实这个函数就是遍历每一个ActivityDisplay,然后遍历ActivityDisplay的成员变量mStacks中的每一个ActivityStack,再遍历ActivityStack成员变量mTaskHistory每一个TaskRecord。最后再遍历TaskRecord成员变量mActivities中的每一个ActivityRecord。最后调用ensureActivityConfigurationLocked处理是否重启每一个Activity。
至此,设置中语言发生变化,然后更新Configuration,清空缓存资源,设置AssetManager的资源配置表,回调Activity的onConfigurationChange函数,重启Activity的整个过程分析完成。