Не підтримується Android context.getResources.updateConfiguration ()


85

Зовсім недавно context.getResources (). updateConfiguration () застаріло в Android API 25, і радимо використовувати контекст. замість цього створітьConfigurationContext () .

Хто-небудь знає, як createConfigurationContext можна використовувати для заміни локальної системи Android?

перш ніж це буде зроблено:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
                                 context.getResources().getDisplayMetrics());

Як щодо applyOverrideConfiguration (неперевірений)?
user1480019

є також просте рішення тут, дуже схожий на цей один stackoverflow.com/questions/39705739 / ...
Thanasis Saxanidis

[updateConfiguration було застарілим на рівні API 25] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

Відповіді:


122

Натхненний каліграфією , я в підсумку створив обгортку контексту. У моєму випадку мені потрібно переписати системну мову, щоб надати користувачам програм можливість змінити мову програми, але це можна налаштувати відповідно до будь-якої логіки, яку вам потрібно реалізувати.

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.ContextWrapper;
    import android.content.res.Configuration;
    import android.os.Build;
    
    import java.util.Locale;
    
    public class MyContextWrapper extends ContextWrapper {

    public MyContextWrapper(Context base) {
        super(base);
    }

    @SuppressWarnings("deprecation")
    public static ContextWrapper wrap(Context context, String language) {
        Configuration config = context.getResources().getConfiguration();
        Locale sysLocale = null;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
            sysLocale = getSystemLocale(config);
        } else {
            sysLocale = getSystemLocaleLegacy(config);
        }
        if (!language.equals("") && !sysLocale.getLanguage().equals(language)) {
            Locale locale = new Locale(language);
            Locale.setDefault(locale);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                setSystemLocale(config, locale);
            } else {
                setSystemLocaleLegacy(config, locale);
            }
            
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             context = context.createConfigurationContext(config);
        } else {
             context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
            }
        return new MyContextWrapper(context);
    }

    @SuppressWarnings("deprecation")
    public static Locale getSystemLocaleLegacy(Configuration config){
        return config.locale;
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static Locale getSystemLocale(Configuration config){
        return config.getLocales().get(0);
    }

    @SuppressWarnings("deprecation")
    public static void setSystemLocaleLegacy(Configuration config, Locale locale){
        config.locale = locale;
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static void setSystemLocale(Configuration config, Locale locale){
        config.setLocale(locale);
    }
}

і щоб ввести вашу обгортку, у кожну діяльність додайте такий код:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}

ОНОВЛЕННЯ 23/09/2020 У разі перевизначення теми програми, щоб застосувати темний режим, наприклад, ContextThemeWrapper порушить налаштування мови, тому додайте наступний код у свою Діяльність, щоб скинути бажану локаль

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
      Locale locale = new Locale("fr");
      overrideConfiguration.setLocale(locale);
      super.applyOverrideConfiguration(overrideConfiguration);
}

ОНОВЛЕННЯ 19.10.2018 Іноді після зміни орієнтації або призупинення / відновлення діяльності об’єкт «Конфігурація» скидає конфігурацію системи за замовчуванням, і в результаті ми побачимо, що програма відображає англійський текст «en», хоча ми обгорнули контекст французькою мовою «fr» . Тому, як хороша практика, ніколи не зберігайте об’єкт Context / Activity у глобальній змінній у видах діяльності або фрагментах.

крім того, створіть і використовуйте наступне у MyBaseFragment або MyBaseActivity:

public Context getMyContext(){
    return MyContextWrapper.wrap(getContext(),"fr");
}

Ця практика надасть вам 100% рішення без вад.


5
Я маю одне занепокоєння цим підходом ... Наразі це застосовується лише до видів діяльності, а не до всього додатку. Що буде з компонентами додатків, які можуть не розпочинатися з таких видів діяльності, як послуги?
rfgamaral

6
Чому ви розширюєте ContextWrapper? У вас нічого немає, лише статичні методи?
vladimir123

7
Мені довелося вийняти createConfigurationContext / updateConfiguration з гілки if-else і додати під ним, інакше у першій діяльності все було нормально, але коли відкрилося друге, мова змінилася на стандартну для пристрою. Не вдалося знайти причину.
kroky

3
Я додав потрібний рядок і розмістив його як цей суть: gist.github.com/muhammad-naderi/…
Мухаммад Надері

2
@kroky має рацію. Локаль системи змінено правильно, але конфігурація повертається до типових. Як результат, файл ресурсу рядків повертається за замовчуванням. Чи є інший спосіб, крім встановлення конфігурації щоразу під час будь-якої діяльності
Yash Ladia

29

Можливо, так:

Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context  = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();

Бонус: Стаття в блозі, в якій використовується createConfigurationContext ()


Дякуємо, що вказали мені у правильному напрямку, я думаю, врешті-решт потрібно буде створити ContextWrapper і прикріпити його до таких дій, як це робиться в Каліграфії. У будь-якому випадку винагорода є вашою, але не розглядатиме її як остаточну відповідь лише тоді, коли я опублікую правильне кодування обхідного шляху.
Бассель Мурян

58
API 24 + ... Дурний google, чи не можуть вони просто надати нам простий спосіб?
Ab

7
@click_whir Висловлювання "Це просто, якщо ви націлюєте лише ці пристрої" насправді не робить це простим.
Влад

1
@Vlad Існує простий метод, якщо вам не потрібно підтримувати пристрої, створені до 2012 року. Ласкаво просимо до розробки додатків!
click_whir

1
звідки ти LocaleList
взяв

4

Натхненний Каліграфією та Мурджаном та я сам, я створив це.

спочатку потрібно створити підклас програми:

public class MyApplication extends Application {
    private Locale locale = null;

    @Override
    public void onCreate() {
        super.onCreate();

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = preferences.getString(getString(R.string.pref_locale), "en");
        String systemLocale = getSystemLocale(config).getLanguage();
        if (!"".equals(lang) && !systemLocale.equals(lang)) {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            setSystemLocale(config, locale);
            updateConfiguration(config);
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (locale != null) {
            setSystemLocale(newConfig, locale);
            Locale.setDefault(locale);
            updateConfiguration(newConfig);
        }
    }

    @SuppressWarnings("deprecation")
    private static Locale getSystemLocale(Configuration config) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return config.getLocales().get(0);
        } else {
            return config.locale;
        }
    }

    @SuppressWarnings("deprecation")
    private static void setSystemLocale(Configuration config, Locale locale) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            config.setLocale(locale);
        } else {
            config.locale = locale;
        }
    }

    @SuppressWarnings("deprecation")
    private void updateConfiguration(Configuration config) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            getBaseContext().createConfigurationContext(config);
        } else {
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

тоді вам потрібно встановити для цього тег програми AndroidManifest.xml:

<application
    ...
    android:name="path.to.your.package.MyApplication"
    >

і додайте це до свого тегу активності AndroidManifest.xml.

<activity
    ...
    android:configChanges="locale"
    >

Зверніть увагу, що pref_locale - це такий рядок:

<string name="pref_locale">fa</string>

і hardcode "en" є типовою мовою, якщо pref_locale не встановлено


Цього недостатньо, вам також потрібно перевизначити контекст у кожній діяльності. Оскільки ви опинитеся в ситуації, що ваш baseContext має один регіон, а програма - інший. В результаті ви отримаєте змішані мови у вашому інтерфейсі. Перегляньте мою відповідь.
Олександр Альбул

3

Тут немає 100% робочого рішення. Вам потрібно використовувати обидва createConfigurationContextі applyOverrideConfiguration. В іншому випадку, навіть якщо ви заміните baseContextкожну діяльність новою конфігурацією, вона все одно використовуватиметься Resourcesзі ContextThemeWrapperстарою мовою.

Тож ось моє рішення, яке працює до API 29:

Підклас свого MainApplicationкласу з:

abstract class LocalApplication : Application() {

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(
            base.toLangIfDiff(
                PreferenceManager
                    .getDefaultSharedPreferences(base)
                    .getString("langPref", "sys")!!
             )
        )
    }
}

Також кожен Activityіз:

abstract class LocalActivity : AppCompatActivity() {

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(            
            PreferenceManager
                .getDefaultSharedPreferences(base)
                    .getString("langPref", "sys")!!
        )
    }

    override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
        super.applyOverrideConfiguration(baseContext.resources.configuration)
    }
}

Додати LocaleExt.ktз наступними функціями розширення:

const val SYSTEM_LANG = "sys"
const val ZH_LANG = "zh"
const val SIMPLIFIED_CHINESE_SUFFIX = "rCN"


private fun Context.isAppLangDiff(prefLang: String): Boolean {
    val appConfig: Configuration = this.resources.configuration
    val sysConfig: Configuration = Resources.getSystem().configuration

    val appLang: String = appConfig.localeCompat.language
    val sysLang: String = sysConfig.localeCompat.language

    return if (SYSTEM_LANG == prefLang) {
        appLang != sysLang
    } else {
        appLang != prefLang
                || ZH_LANG == prefLang
    }
}

fun Context.toLangIfDiff(lang: String): Context =
    if (this.isAppLangDiff(lang)) {
        this.toLang(lang)
    } else {
        this
    }

@Suppress("DEPRECATION")
fun Context.toLang(toLang: String): Context {
    val config = Configuration()

    val toLocale = langToLocale(toLang)

    Locale.setDefault(toLocale)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        config.setLocale(toLocale)

        val localeList = LocaleList(toLocale)
        LocaleList.setDefault(localeList)
        config.setLocales(localeList)
    } else {
        config.locale = toLocale
    }

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        config.setLayoutDirection(toLocale)
        this.createConfigurationContext(config)
    } else {
        this.resources.updateConfiguration(config, this.resources.displayMetrics)
        this
    }
}

/**
 * @param toLang - two character representation of language, could be "sys" - which represents system's locale
 */
fun langToLocale(toLang: String): Locale =
    when {
        toLang == SYSTEM_LANG ->
            Resources.getSystem().configuration.localeCompat

        toLang.contains(ZH_LANG) -> when {
            toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) ->
                Locale.SIMPLIFIED_CHINESE
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
                Locale(ZH_LANG, "Hant")
            else ->
                Locale.TRADITIONAL_CHINESE
        }

        else -> Locale(toLang)
    }

@Suppress("DEPRECATION")
private val Configuration.localeCompat: Locale
    get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        this.locales.get(0)
    } else {
        this.locale
    }

Додайте до своїх res/values/arrays.xmlпідтримуваних мов у масиві:

<string-array name="lang_values" translatable="false">
    <item>sys</item> <!-- System default -->
    <item>ar</item>
    <item>de</item>
    <item>en</item>
    <item>es</item>
    <item>fa</item>
    ...
    <item>zh</item> <!-- Traditional Chinese -->
    <item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>

Я хочу зазначити:

  • Використовуйте config.setLayoutDirection(toLocale);для зміни напрямку макета, коли ви використовуєте локалі RTL, такі як арабська, перська тощо
  • "sys" у коді є значення, що означає "успадкувати мову за замовчуванням у системі".
  • Тут "langPref" - це ключ до уподобань, де ви вводите поточну мову користувача.
  • Немає необхідності відтворювати контекст, якщо він уже використовує необхідну локаль.
  • Тут немає потреби в ContextWraperтому, що розміщено тут, просто встановіть новий контекст, який повертається з createConfigurationContextяк baseContext
  • Це дуже важливо! Під час дзвінка createConfigurationContextвам слід передати конфігурацію, створену з нуля, і лише з Localeнабором властивостей. Для цієї конфігурації не повинно бути встановлено жодної іншої властивості. Оскільки якщо ми встановимо деякі інші властивості для цієї конфігурації (наприклад, орієнтація ), ми замінюємо цю властивість назавжди, і наш контекст більше не змінює цю властивість орієнтації, навіть якщо ми обертаємо екран.
  • Недостатньо лише recreateактивності, коли користувач вибирає іншу мову, оскільки applicationContext залишиться зі старою мовою, і це може забезпечити несподівану поведінку. Тож прислухайтесь до зміни налаштувань і замість цього перезапустіть ціле завдання програми:

fun Context.recreateTask() {
    this.packageManager
        .getLaunchIntentForPackage(context.packageName)
        ?.let { intent ->
            val restartIntent = Intent.makeRestartActivityTask(intent.component)
            this.startActivity(restartIntent)
            Runtime.getRuntime().exit(0)
         }
}

Це не працює. Також розгляньте можливість створення базової діяльності для всіх видів діяльності, замість того, щоб дублювати код у кожній діяльності. Крім того, recreateTask(Context context)метод не працює належним чином, оскільки я все ще бачу макет без змін.
blueware

@blueware Я оновив зразки. Раніше були деякі помилки. Але наразі це повинно працювати, це код з мого робочого додатка. Забава recreateTask не могла спрацювати, ви можете продемонструвати тост на кшталт "Мова буде змінена після перезапуску" ..
Олександр Альбуль

1

Ось рішення @ bassel-mourjan з трохи котлінської користі :):

import android.annotation.TargetApi
import android.content.ContextWrapper
import android.os.Build
import java.util.*

@Suppress("DEPRECATION")
fun ContextWrapper.wrap(language: String): ContextWrapper {
    val config = baseContext.resources.configuration
    val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        this.getSystemLocale()
    } else {
        this.getSystemLocaleLegacy()
    }

    if (!language.isEmpty() && sysLocale.language != language) {
        val locale = Locale(language)
        Locale.setDefault(locale)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            this.setSystemLocale(locale)
        } else {
            this.setSystemLocaleLegacy(locale)
        }
    }

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        val context = baseContext.createConfigurationContext(config)
        ContextWrapper(context)
    } else {
        baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
        ContextWrapper(baseContext)
    }

}

@Suppress("DEPRECATION")
fun ContextWrapper.getSystemLocaleLegacy(): Locale {
    val config = baseContext.resources.configuration
    return config.locale
}

@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.getSystemLocale(): Locale {
    val config = baseContext.resources.configuration
    return config.locales[0]
}


@Suppress("DEPRECATION")
fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) {
    val config = baseContext.resources.configuration
    config.locale = locale
}

@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.setSystemLocale(locale: Locale) {
    val config = baseContext.resources.configuration
    config.setLocale(locale)
}

І ось як ви ним користуєтесь:

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}

Цей рядок val config = baseContext.resources.configurationдуже дуже неправильний. Через це у вас з’явиться багато помилок. Натомість вам потрібно створити нову конфігурацію. Дивіться мою відповідь.
Олександр Альбул

0

тут є просте рішення з contextWrapper: Android N програмно міняє мову Зверніть увагу на метод recreate ()


Посилання корисне та хороше посилання. Я вважаю, що краще включити сюди фактичну відповідь, а не вимагати додаткового клацання.
БеззубийПовстанець

Ви маєте рацію, я просто новачок у stackoverflow, і я вважав, що неправильно брати кредити за відповідь, тому я публікую посилання оригінального автора
Танасіс Саксанідіс

-1

Спробуйте це:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);

1
Це просто створює діяльність, у якій потрібно змінити контекст на нову
Алі Карача
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.