迁移到Androidx后,更改语言环境不起作用

时间:2019-03-20 16:34:52

标签: android android-activity android-support-library androidx

我有一个支持多种语言的旧项目。我想升级支持库和目标平台,在迁移到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!

14 个答案:

答案 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 开始,enter image description here覆盖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 commentfirst 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