背景:
剪映出海,产品需要在不同语言环境下验收UI,手机切换语言效率较低,因此需要在App内支持动态替换语言提高产品/设计同学验收效率,这套方案亦可作为App内设置语言方案。
替换语言意味着什么?
我们知道Context里是能够通过getResources函数获取当前上下文对应的资源,然后就可以通过getString获得对应的文案。
而getString会返回getText(id).toString();
//android.content.res.Resources#getText(int)
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
可以看到getText又是通过getAssets()去拿的资源。而ResourcesImpl的mAssets字段又是在实例化时赋值。
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
public void updateConfiguration(Configuration config, DisplayMetrics metrics,
CompatibilityInfo compat) {
//...
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
//...
}
从上面可以看到,通过Resources去获取对应语系文案的配置应该就是在mConfiguration.getLocales()里配置的了,所以我们如果能修改掉Configuration.mLocaleList字段那应该就可以实现替换语言的功能了。
所以动态替换语言也就意味着动态替换掉context.resources.configuration.mLocaleList的值。
替换语言只需要对与界面相关的Context相关,也就是Activity(ContextThemeWapper)的Context,Fragment用的也是Activity的Context。当然因为程序内部某些地方会用到applicationContext.getResources().getString(),因此applicationContext的Configuration的Locale配置我们也是需要修改的。
PS:一个应用里面有多少个Context?答案是:Num Of Activity + Num Of Service + 1(Application),
四大组件中ContentProvider&BroadcastReceiver并不继承于Context,他们只是使用到了Context来使用上下文环境。
Context相关类
那么我们需要在什么时机去替换Context的内部资源配置?
我们需要Application&Activity在attachBaseContext,还有Fragment在attachActivity时也需要修改Activity的Configuration。
在程序内部的Application/BaseActivity/BaseFragment的attachBaseContext/onAttach执行了以下方法,在运行时语言就会全局替换了。
//com.vega.launcher.ScaffoldApplication
override fun attachBaseContext(base: Context) {
su