我有一个支持多种语言的旧项目。我想升级支持库和目标平台,在迁移到Androidx之前,一切正常,但现在更改语言不起作用!
我使用此代码更改App的默认语言环境
private static Context updateResources(Context context, String language)
{
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
并通过覆盖attachBaseContext
在每个活动上调用此方法,如下所示:
@Override
protected void attachBaseContext(Context newBase)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String language = preferences.getString(SELECTED_LANGUAGE, "fa");
super.attachBaseContext(updateResources(newBase, language));
}
我尝试其他方法来获取字符串,但我注意到 getActivity().getBaseContext().getString
有效,而getActivity().getString
不起作用。即使以下代码也不起作用,并且始终在默认资源string.xml中显示app_name
vlaue。
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"/>
我在https://github.com/Freydoonk/LanguageTest中共享示例代码
getActivity()..getResources().getIdentifier
也不起作用,总是返回0!
答案 0 :(得分:46)
基本上,在后台发生的是,在attachBaseContext
中正确设置了配置后,AppCompatDelegateImpl
然后将配置覆盖为没有语言环境的完全新鲜的配置< / em>:
final Configuration conf = new Configuration();
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
In an unreleased commit by Chris Banes实际上已得到解决:新配置是基本上下文配置的深层副本。
final Configuration conf = new Configuration(baseConfiguration);
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
在此发布之前,可以手动执行完全相同的操作。要继续使用1.1.0版,请将其添加到attachBaseContext
下:
科特林溶液
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
Java解决方案
@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
此代码的作用与Configuration(baseConfiguration)
完全相同,但是由于我们在 之后进行操作,AppCompatDelegate
已经设置了正确的uiMode
,我们必须确保修复后覆盖了uiMode
的位置,以免丢失暗/亮模式设置。
请注意 ,仅当您未在清单中指定configChanges="uiMode"
时,此方法才有效。如果这样做,那么还会有另一个错误:onConfigurationChanged
的{{1}}不会在newConfig.uiMode
内部设置AppCompatDelegateImpl
。如果您将用于计算当前夜间模式的所有代码onConfigurationChanged
复制到基本活动代码,然后在AppCompatDelegateImpl
调用之前覆盖它,也可以解决此问题。在Kotlin中,它看起来像这样:
super.onConfigurationChanged
12月16日更新
The recent version of AppCompatDelegateImpl.java
自从最初撰写此帖子以来,已经获得了更多更改。我上面提到的Chris Banes的修复显然不足以完全摆脱与private var activityHandlesUiMode = false
private var activityHandlesUiModeChecked = false
private val isActivityManifestHandlingUiMode: Boolean
get() {
if (!activityHandlesUiModeChecked) {
val pm = packageManager ?: return false
activityHandlesUiMode = try {
val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
activityHandlesUiModeChecked = true
return activityHandlesUiMode
}
override fun onConfigurationChanged(newConfig: Configuration) {
if (isActivityManifestHandlingUiMode) {
val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED)
delegate.localNightMode
else
AppCompatDelegate.getDefaultNightMode()
val configNightMode = when (nightMode) {
AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
}
newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
super.onConfigurationChanged(newConfig)
}
相关的所有错误(您可以在上方查看configChanges
的解决方法,并且一位用户在此处的评论中指出(在他的情况下,configChanges="uiMode"
的值在orientation
中没有变化)。相反,最新的实现依赖于称为onConfigurationChanged
的新方法来创建正确的配置实例,因此不建议使用其他所有处理夜间模式的方法。
我个人使用我的解决方案已有数月之久,没有任何问题或任何用户抱怨,但您仍应注意generateConfigDelta
仍在开发中,因此请您自行决定使用我的解决方案。还要确保检查出其他建议,建议将其降级到早期库版本, 可能 更适合您的情况。
答案 1 :(得分:6)
我正在使用“ androidx.appcompat:appcompat:1.3.0-alpha01”,但我想它也可以在Version 1.2.0上使用。
以下代码基于Android Code Search。
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import java.util.*
open class MyBaseActivity :AppCompatActivity(){
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
val config = Configuration()
applyOverrideConfiguration(config)
}
override fun applyOverrideConfiguration(newConfig: Configuration) {
super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig))
}
open fun updateConfigurationIfSupported(config: Configuration): Configuration? {
// Configuration.getLocales is added after 24 and Configuration.locale is deprecated in 24
if (Build.VERSION.SDK_INT >= 24) {
if (!config.locales.isEmpty) {
return config
}
} else {
if (config.locale != null) {
return config
}
}
// Please Get your language code from some storage like shared preferences
val languageCode = "fa"
val locale = Locale(languageCode)
if (locale != null) {
// Configuration.setLocale is added after 17 and Configuration.locale is deprecated
// after 24
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale)
} else {
config.locale = locale
}
}
return config
}
}
答案 2 :(得分:5)
最后,我在应用程序中发现了问题。当将项目迁移到我的项目的Androidx依赖项时,更改如下:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-alpha04'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02'
}
可以看到,当我将androidx.appcompat:appcompat
的版本更改为最新的稳定版本1.1.0-alpha03
时,1.0.2
的版本为dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
,我的问题已解决并且更改语言正常工作。
我在Maven Repository中找到了appcompat库的最新稳定版本。我还将其他库更改为最新的稳定版本。
现在我的应用程序依赖项如下:
if ($row = mysqli_num_rows($selectProducts) > 0)
答案 3 :(得分:5)
只需在androidx.appcompat:appcompat:1.1.0
中调用getResources()
即可解决Activity.applyOverrideConfiguration()
错误
@Override public void
applyOverrideConfiguration(Configuration cfgOverride)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// add this to fix androidx.appcompat:appcompat 1.1.0 bug
// which happens on Android 6.x ~ 7.x
getResources();
}
super.applyOverrideConfiguration(cfgOverride);
}
答案 4 :(得分:4)
最新答案,但我认为可能会有所帮助。从 androidx.appcompat:appcompat:1.2.0-beta01 开始,覆盖applyOverrideConfiguration
的解决方案不再适用于我。相反,在随后覆盖的attacheBaseContext
中,您必须调用applyOverrideConfiguration()
而不覆盖它。
override fun attachBaseContext(newBase: Context) {
val newContext = LocaleHelper.getUpdatedContext(newBase)
super.attachBaseContext(newContext)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1){
applyOverrideConfiguration(newContext.resources.configuration)
}
}
可惜的是,他的解决方案只能在1.1.0上运行。根据我的研究,这应该已经正式修复。这个错误仍然存在很奇怪。我知道我使用Beta,但对于想使用最新版本的人来说,这种解决方案对我来说是有效的。 在仿真器API级别21-25上进行了测试。在该API级别之上,您不必担心。
答案 5 :(得分:3)
新的应用兼容库中存在与夜间模式相关的问题,该问题导致覆盖android 21至25上的配置。 可以通过在调用此公共函数时应用配置来解决此问题:
public void applyOverrideConfiguration(配置overlayConfiguration
对我来说,此小技巧通过将设置从覆盖的配置复制到我的配置中而起作用,但是您可以执行任何所需的操作。最好将您的语言逻辑重新应用于新配置,以最大程度地减少错误
@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (Build.VERSION.SDK_INT >= 21&& Build.VERSION.SDK_INT <= 25) {
//Use you logic to update overrideConfiguration locale
Locale locale = getLocale()//your own implementation here;
overrideConfiguration.setLocale(locale);
}
super.applyOverrideConfiguration(overrideConfiguration);
}
答案 6 :(得分:3)
尝试这样的事情:
public class MyActivity extends AppCompatActivity {
public static final float CUSTOM_FONT_SCALE = 4.24f;
public static final Locale CUSTOM_LOCALE = Locale.CANADA_FRENCH; // or whatever
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(useCustomConfig(newBase));
}
private Context useCustomConfig(Context context) {
Locale.setDefault(CUSTOM_LOCALE);
if (Build.VERSION.SDK_INT >= 17) {
Configuration config = new Configuration();
config.fontScale = CUSTOM_FONT_SCALE;
config.setLocale(CUSTOM_LOCALE);
return context.createConfigurationContext(config);
} else {
Resources res = context.getResources();
Configuration config = new Configuration(res.getConfiguration());
config.fontScale = CUSTOM_FONT_SCALE;
config.locale = CUSTOM_LOCALE;
res.updateConfiguration(config, res.getDisplayMetrics());
return context;
}
}
}
来源:issuetracker comment和first sample linked from the issuetracker comment。
尽管上面的方法对我来说很好,但是second sample linked from the issuetracker comment中的另一种选择如下(我个人还没有尝试过):
@RequiresApi(17)
public class MyActivity extends AppCompatActivity {
public static final float CUSTOM_FONT_SCALE = 4.24f;
public static final Locale CUSTOM_LOCALE = Locale.CANADA_FRENCH; // or whatever
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
Configuration config = new Configuration();
config.fontScale = CUSTOM_FONT_SCALE;
applyOverrideConfiguration(config);
}
@Override
public void applyOverrideConfiguration(Configuration newConfig) {
super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig));
}
private Configuration updateConfigurationIfSupported(Configuration config) {
if (Build.VERSION.SDK_INT >= 24) {
if (!config.getLocales().isEmpty()) {
return config;
}
} else {
if (config.locale != null) {
return config;
}
}
Locale locale = CUSTOM_LOCALE;
if (locale != null) {
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
return config;
}
}
答案 7 :(得分:3)
现在这些库的语言不会改变: androidx.appcompat:appcompat:1.1.0, androidx.appcompat:appcompat:1.2.0
问题只在这个库中解决: androidx.appcompat:appcompat:1.3.0-rc01
答案 8 :(得分:0)
1。您可以在 attachBaseContext()
中使用的方法private void setLanguage(Context mContext, String localeName) {
Locale myLocale = new Locale(localeName);
Resources res = mContext.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = myLocale;
res.updateConfiguration(conf, dm);
}
2。覆盖活动
@Override
protected void attachBaseContext(Context newBase) {
setLanguage(newBase, "your language");
super.attachBaseContext(newBase);
}
注意::重新创建活动后,这对我来说效果很好
答案 9 :(得分:0)
现在有一个更新的版本也可以使用:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
正如@Fred提到的appcompat:1.1.0-alpha03
有一个小故障,尽管他们的release versions log中没有提及
答案 10 :(得分:0)
最后,我找到了定位的解决方案,对我而言,实际上是bundle apk
的问题,因为它分割了定位文件。默认情况下,将在bundle apk
中生成所有拆分。但是在build.gradle
文件的android块中,您可以声明将生成哪些拆分。
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
将此代码添加到{strong> build.gradle
个文件的Android块后,我的问题得到解决。
答案 11 :(得分:0)
在androidx.appcompat:appcompat:1.1.0
上有相同的错误。切换到androidx.appcompat:appcompat:1.1.0-rc01
,现在Android 5-6.
上的语言改变了
答案 12 :(得分:0)
@ 0101100101的答案对我有用。
只有我用过
@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration)
{
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
所以只有getResources()
而不是getBaseContext().getResources()
。
对于我来说,我已经使用重写的getResources()扩展了ContextWrapper。 但是在调用applyOverrideConfiguration之后,我无法访问我的自定义getResources。我得到标准的。
如果我使用上面的代码,一切正常。
答案 13 :(得分:0)
解决方案:
在 App 级 Gradle 中,我在 android 部门中包含了以下代码,
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
https://medium.com/dwarsoft/how-to-provide-languages-dynamically-using-app-bundle-567d2ec32be6