Изменение локали не работает после перехода на Androidx

У меня есть старый проект, который поддерживает несколько языков. Я хочу обновить библиотеку поддержки и целевую платформу, До перехода на Androidx все работало нормально, но теперь не работает смена языка!

Я использую этот код, чтобы изменить язык приложения по умолчанию.

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 не работает. Даже следующий код не работает и всегда показывает app_name vlaue в ресурсе по умолчанию string.xml.

<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!

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

Ibrahim Ali 20.03.2019 17:45

Вы воссоздаете действие после изменения языка?

mustafiz012 20.03.2019 18:03

@ mustafiz012 mustafiz012 Если я вызову recareate в attachBaseContext, сделайте циклический вызов и ...

Fred 20.03.2019 18:08

@IbrahimAli Я делюсь примером кода в github.com/Freydoonk/LanguageTest

Fred 22.03.2019 15:03

Интересно, что установка android:configChanges = "uiMode" решает проблему, но вместо этого, естественно, мы получаем цветовые артефакты при переключении между светлой и темной темой...

0101100101 19.09.2019 06:51
73
5
17 848
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

1. Метод, который вы можете использовать в прикрепитьBaseContext()

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);
}

Примечание: У меня это работает нормально после воссоздания активности

Где вы вызываете «воссоздать();»

Fred 20.03.2019 18:28

Предположим, вы используете кнопку, чтобы изменить язык. В этом случае, когда вы нажмете кнопку, поместите свой язык в общие настройки и воссоздайте свою активность. Получите языковую строку из общих настроек, когда это необходимо. Если вам нужна дополнительная информация, я поделюсь примером кода.

mustafiz012 20.03.2019 18:42

Я делаю это, я сохраняю выбранный язык в общих настройках и воссоздаю приложение, я получаю выбранный язык из общих настроек и перехожу к setLanguage(newBase, myPpersistedLanguage); в attachBaseContext, но это не работает. Я редактирую полный attachBaseContext в примере кода в своем вопросе, чтобы показать это.

Fred 20.03.2019 18:52

Я думаю, что ваш файл strings.xml настроен не так, как требуется. Скажите, пожалуйста, у вас есть альтернативный файл strings.xml?? Например: strings.xml, strings.xml (fa)

mustafiz012 20.03.2019 19:02

Добавляю скриншот своего ресурса в свой проект

Fred 20.03.2019 19:10

Затем я поделюсь своим рабочим примером кода. Пожалуйста, дайте мне немного времени.

mustafiz012 20.03.2019 19:12

Давайте продолжить обсуждение в чате.

mustafiz012 20.03.2019 19:32
Ответ принят как подходящий

Наконец, я нахожу проблему в своем приложении. При переносе проекта на 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, моя проблема решена, и смена языка работает правильно.

Я нашел последнюю стабильную версию библиотеки appcompat в Репозиторий Maven. Я также меняю другие библиотеки на последнюю стабильную версию.

Теперь раздел зависимостей моего приложения выглядит следующим образом:

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'
}

У меня была аналогичная проблема. У меня уже был стабильный 1.0.2 для appcompat, но моя проблема была в библиотеке поддержки дизайна или com.google.android.material:material:1.1.0-alpha06. Поэтому, когда я перешел на стабильную версию 1.0.0, она была исправлена.

Abir Hasan 15.05.2019 15:53

Теперь есть более новая версия, которая также работает:

implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'

Как упомянул @Fred, у appcompat:1.1.0-alpha03 есть сбой, хотя он не упоминается в их журнал выпусков версий.

Тестирую androidx.appcompat:appcompat:1.1.0-alpha04, у него пока такая проблема.

Fred 04.04.2019 22:28

для моего производственного приложения работает просто отлично, я использую собственную реализацию для ContextWrapper внутри attachBaseContext(Context newBase) для каждого действия

Choletski 05.04.2019 08:58

Наконец-то я получил решение для локации. В моем случае на самом деле проблема была с bundle apk, потому что она разделяла файлы локации. В bundle apk по умолчанию будут генерироваться все сплиты. но в блоке android вашего файла build.gradle вы можете объявить, какие разбиения будут сгенерированы.

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
            }
        }

После добавления этого кода в файл андроид блок build.gradle моя проблема была решена.

У меня была такая же проблема. Когда я проверял перед публикацией в плеймаркете, все работало, а после публикации смена локали не работала. Я применил это решение и теперь работает безупречно. Спасибо

Sunny 28.02.2020 03:25

У меня такая же проблема. Всякий раз, когда я устанавливаю отладочную версию, она работает, но когда я выпускаю пакет, возникает проблема, и я пробовал много решений, но это единственное, что сработало. Спасибо.

Yonatan Dawit 03.05.2020 20:04

Этот! Это имеет большой смысл. Один из тестируемых телефонов никогда не отображал выбранный язык из приложения, и мы задались вопросом, почему это происходит только при загрузке из Google Play. Я проверил его работу, добавив системный язык -> очистить данные -> перезапустив приложение. Спасибо!

Saehun Sean Oh 19.04.2021 23:45

Была такая же ошибка на androidx.appcompat:appcompat:1.1.0. Перешел на androidx.appcompat:appcompat:1.1.0-rc01 и теперь языки меняются на Android 5-6.

В новых библиотеках совместимости приложений есть проблема, связанная с ночным режимом, которая приводит к переопределению конфигурации на Android с 21 по 25. Это можно исправить, применив вашу конфигурацию при вызове этой общедоступной функции:

public void applyOverrideConfiguration(Configuration overrideConfiguration

Для меня этот небольшой трюк сработал, скопировав настройки из переопределенной конфигурации в мою конфигурацию, но вы можете делать все, что хотите. Лучше повторно применить языковую логику к новой конфигурации, чтобы свести к минимуму ошибки.

@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);
}

это изменение кода приводит к сбою в нескольких местах на некоторых устройствах Attempt to invoke virtual method 'java.lang.String java.util.Locale.toString()' on a null object reference android.widget.Editor$TextActionModeCallback.populateMenuWit‌​hItemsExt. Лучше посоветовать перейти на более раннюю версию или следовать ответу @ 0101100101.

sanjeev 08.11.2019 10:11

getLocale и setLocale должны быть на Android N/24 или выше

Rahmat Ihsan 27.05.2020 03:51

ОБНОВЛЕНИЕ 21 августа 2020 г.:

Наконец-то был выпущен AppCompat 1.2.0. Если вы вообще не используете ContextWrapper или ContextThemeWrapper, вам больше нечего делать, и вы сможете удалить все обходные пути, которые были у вас в 1.1.0!

Если вы ДЕЙСТВИТЕЛЬНО используете ContextWrapper или ContextThemeWrapper внутри attachBaseContext, изменения локали сломаются, потому что, когда вы передаете обернутый контекст в super,

  1. 1.2.0 AppCompatActivity выполняет внутренние вызовы, которые заключают ваш ContextWrapper в другой ContextThemeWrapper,
  2. или, если вы используете ContextThemeWrapper, заменяет его конфигурацию пустой, аналогично тому, что произошло в 1.1.0.

Но решение всегда одно. Я пробовал несколько других решений для ситуации 2, но, как указал @Kreiri в комментариях (спасибо за вашу помощь в расследовании!), AppCompatDelegateImpl всегда заканчивался удалением локали. Большим препятствием является то, что, в отличие от 1.1.0, applyOverrideConfiguration вызывается в вашем базовом контексте, а не в вашей активности хоста, поэтому вы не можете просто переопределить этот метод в своей активности и исправить локаль, как вы могли в 1.1.0. Единственное рабочее решение, о котором я знаю, это измените обертку, переопределив getDelegate(), чтобы убедиться, что ваша обертка и / или переопределение локали будут последними. Сначала вы добавляете класс ниже:

Образец Kotlin (обратите внимание, что класс ДОЛЖЕН находиться внутри пакета androidx.appcompat.app, потому что единственный существующий конструктор AppCompatDelegate является частным пакетом)

package androidx.appcompat.app

import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar

class BaseContextWrappingDelegate(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() {

    override fun getSupportActionBar() = superDelegate.supportActionBar

    override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)

    override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater

    override fun onCreate(savedInstanceState: Bundle?) {
        superDelegate.onCreate(savedInstanceState)
        removeActivityDelegate(superDelegate)
        addActiveDelegate(this)
    }

    override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)

    override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)

    override fun onStart() = superDelegate.onStart()

    override fun onStop() = superDelegate.onStop()

    override fun onPostResume() = superDelegate.onPostResume()

    override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)

    override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)

    override fun setContentView(v: View?) = superDelegate.setContentView(v)

    override fun setContentView(resId: Int) = superDelegate.setContentView(resId)

    override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)

    override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)

    override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))

    override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)

    override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()

    override fun onDestroy() {
        superDelegate.onDestroy()
        removeActivityDelegate(this)
    }

    override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate

    override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)

    override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)

    override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)

    override fun installViewFactory() = superDelegate.installViewFactory()

    override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)

    override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
        superDelegate.isHandleNativeActionModesEnabled = enabled
    }

    override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled

    override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)

    override fun applyDayNight() = superDelegate.applyDayNight()

    override fun setLocalNightMode(mode: Int) {
        superDelegate.localNightMode = mode
    }

    override fun getLocalNightMode() = superDelegate.localNightMode

    private fun wrap(context: Context): Context {
        TODO("your wrapping implementation here")
    }
}

Затем внутри нашего базового класса активности вы удаляете все свои обходные пути 1.1.0 и просто добавляете это:

private var baseContextWrappingDelegate: AppCompatDelegate? = null

override fun getDelegate() = baseContextWrappingDelegate ?: BaseContextWrappingDelegate(super.getDelegate()).apply {
    baseContextWrappingDelegate = this
}

В зависимости от используемой вами реализации ContextWrapper изменения конфигурации могут нарушить изменение тем или языковых стандартов. Чтобы исправить это, дополнительно добавьте это:

override fun createConfigurationContext(overrideConfiguration: Configuration) : Context {
    val context = super.createConfigurationContext(overrideConfiguration)
    TODO("your wrapping implementation here")
}

И ты хорош! Вы можете ожидать, что Google снова сломает это в 1.3.0. Я буду там, чтобы исправить это ... До встречи, космический ковбой!

СТАРЫЙ ОТВЕТ И РЕШЕНИЕ ДЛЯ APPCOMPAT 1.1.0:

В основном то, что происходит в фоновом режиме, заключается в том, что, хотя вы правильно установили конфигурацию в attachBaseContext, AppCompatDelegateImpl затем переходит и переопределяет конфигурацию на полностью свежая конфигурация без локали:

 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) {
     ...
 }

В неопубликованном коммите Криса Бейнса это было фактически исправлено: новая конфигурация является глубокой копией конфигурации базового контекста.

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" внутри своего манифеста. Если вы это сделаете, то есть еще одна ошибка: внутри onConfigurationChangednewConfig.uiMode не будет установлен AppCompatDelegateImplonConfigurationChanged. Это также можно исправить, если вы скопируете весь код, который AppCompatDelegateImpl использует для расчета текущего ночного режима, в свой базовый код активности, а затем переопределите его перед вызовом super.onConfigurationChanged. В Котлине это будет выглядеть так:

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)
}

Это сработало идеально для меня. Спасибо! Но я не понял, как переопределение метода «applyOverrideConfiguration» решает проблему. Не могли бы вы объяснить это?

Burak 23.10.2019 23:27

@Burak Если вы проверите исходный код ContextThemeWrapper, вы сможете увидеть, что все, что передается в super.applyOverrideConfiguration(overrideConfiguration), сохраняется и позже используется для извлечения любых ресурсов. Именно так Google реализовал тему день/ночь. Базовая конфигурация ресурса не имеет правильной настройки uiMode, поэтому ContextThemeWrapper переопределяет getResources(), чтобы вернуть экземпляр, в котором он есть в конфигурации.

0101100101 24.10.2019 02:02

Это решение имеет проблемы, если приложение поддерживает альбомную ориентацию.

mdtuyen 28.11.2019 04:36

@phongvan какие проблемы? Я использую это в течение долгого времени, и у меня никогда не было проблем с пейзажем или переключением между портретом и пейзажем. Почти уверен, что если вы столкнетесь с какими-либо сбоями, это связано с другими ошибками в версии 1.1.0, а не с моим решением, потому что, как объяснено в ответе, оно напрямую получено из официального исправления Google.

0101100101 28.11.2019 06:29

@0101100101 Когда я использую этот код, я возвращаюсь к экрану, ориентация не меняется при onconfigchange

mdtuyen 28.11.2019 08:03

@phongvan Я заметил, что Google много работает над реализацией, и вполне возможно, что такие ошибки могут возникнуть. Смотрите обновление в моем ответе. Также кажется, что то, что вы описываете, может происходить совершенно случайным образом, иногда все работает нормально, а иногда ломается.

0101100101 16.12.2019 00:49

этот applyOverrideConfiguration обходной путь работает для меня. Без этой локали не работает Android 6.0. Я использую androidx.appcompat:appcompat:1.1.0

Deepak Goyal 17.12.2019 09:36

Не понимаю, у меня не работает. Я поместил applyOverrideConfiguration в свой MainActivity. Затем у меня есть метод updateResource в моем помощнике. Затем в моем UITest внутри @Before я позвонил: updateResources(InstrumentationRegistry.getInstrumentation()‌​.context, «EN»), но не работает, помогите, пожалуйста? NB: версия «androidx.appcompat: appcompat: 1.1.0»

Tai Nguyen 24.12.2019 13:42

@TaiNguyen, какой у вас метод updateResource, что он делает? Просто хочу отметить, что если вы используете ContextWrappers в attachBaseContext, для того, чтобы мое решение заработало, необходимы дополнительные изменения кода, поскольку у ContextWrappers может быть своя собственная логика, например, сохранение устаревших экземпляров Resources и Configuration.

0101100101 27.12.2019 01:17

Для меня этот дополнительный шаг (согласно комментарию чуть выше) был необходим... не передавать ContextWrapper в super.attachBaseContext(), а вместо этого просто передать Context, то есть без переноса. Это в сочетании с использованием applyOverrideConfiguration(), кажется, работает для меня. Спасибо. Но Зачем Google делает так жесткий, чтобы сделать что-то настолько просто??!!

drmrbrewer 05.05.2020 09:51

ОБНОВЛЕНИЕ.... аааа, нет, он все еще не работает стабильно. По-прежнему иногда используется язык по умолчанию (системный). Андроид это кошмар. После выхода из моей активности, если я снова открою ее вскоре после этого, будет использоваться язык по умолчанию (системный). Если я открою список «последние приложения» и закрою все, а затем открою «Действие», будет использован правильный язык (переопределение вручную). Как будто какая-то конфигурация по умолчанию (системная) все еще используется.

drmrbrewer 05.05.2020 10:22

дальнейшее ОБНОВЛЕНИЕ @ 0101100101 ... делая то, что вы предлагаете в этом ответе, плюс также понижение до appcompat 1.1.0, кажется, работает лучше для меня ... использовал appcompat 1.2.0-beta01. Меня беспокоит то, что appcompat развивается в направлении, которое сделает этот ответ излишним... есть идеи, куда сообщать об этих проблемах с appcompat?

drmrbrewer 05.05.2020 11:12

последнее ОБНОВЛЕНИЕ... нашел что-то, что работает лучше для меня... см. мой ответ здесь: stackoverflow.com/a/61643167/4070848

drmrbrewer 06.05.2020 21:22

@drmrbrewer «Меня беспокоит, что appcompat развивается в направлении, которое сделает этот ответ излишним», да, это было в значительной степени ожидаемо. Смотрите мое последнее обновление к этому ответу.

0101100101 10.08.2020 05:41

Использование ContextThemeWrapper вместо ContextWrapper не помогает.

Kreiri 18.08.2020 11:57

@Kreiri, можете ли вы попробовать, если это работает, добавив в дополнение к этому решение applyOverrideConfiguration? Я только что отредактировал свой ответ, чтобы предложить это. Я заметил, что код AppCompatDelegateImpl все еще нарушает конфигурацию, это смешно. Дайте мне знать, как это происходит.

0101100101 19.08.2020 04:29

@ 0101100101 У меня не получилось. applyOverrideConfiguration не вызывается с 1.2.0 в моем проекте...

Kreiri 19.08.2020 12:25

@Kreiri Я вижу, реализация на самом деле больше не вызывает applyOverrideConfiguration активность, а только базовый контекст. Смотрите обновленный ответ и решение: просто позвоните getResources(), прежде чем звонить super.attachBaseContext. Не нужно переопределять applyOverrideConfiguration.

0101100101 20.08.2020 04:56

@ 0101100101 Даже при вызове getResources() для нового контекста перед его передачей в super.attachBaseContext локаль в действии не меняется. attachBaseContext2 всегда возвращает контекст с языковым стандартом системы.

Kreiri 20.08.2020 07:27

@Kreiri, вы правы, я только что проверил, и реализации Google еще удается убрать локаль, вздох ... Попробуйте удалить все обходные пути и переключиться на решение BaseContextWrappingDelegate, независимо от того, используете ли вы тему или обычную оболочку. Вам просто нужно переместить обертку и изменение локали из вашей активности внутрь этого класса.

0101100101 21.08.2020 06:33

Да, начиная с 1.2.0, applyOverrideConfiguration не вызывается. Вы должны вызвать эту функцию вручную из attachBaseContext

Deepak Goyal 13.10.2020 08:49

В случае, если это может быть кому-то полезно, здесь представляет собой полную разницу в рабочей реализации изменения локали, основанную на объединении этого ответа с Еще один с использованием действия AppCompatActivity и библиотеки версии 1.2.0.

CNugteren 20.10.2020 16:32

спасибо за обновление appCompat 1.2.0, люблю вас.

Youtoo 17.11.2020 11:14

Наконец нашел ответ, который работает для меня, я работал над этим несколько дней и не смог заставить его работать; это здорово. Большое спасибо за этот отличный ответ

M. Abdel-Ghani 14.12.2020 18:09

Это также работает с AppCompat 1.3.0-beta01!

Claus Holst 17.02.2021 12:45

Ответ от @ 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().

В моем случае я расширил ContextWrapper с переопределенным getResources(). Но после вызова applyOverrideConfiguration я не могу получить доступ к своим пользовательским getResources. Вместо них я беру стандартные.

Если я использую код выше, все работает нормально.

Ошибка 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);
}

Поздний ответ, но я подумал, что это может быть полезно. Начиная с androidx.appcompat:appcompat:1.2.0-beta01 Решение переопределения 0101100101applyOverrideConfiguration больше не работает на мне. Вместо этого в переопределенном 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. Основываясь на моих исследованиях, это должно было быть официально исправлено. Просто странно, что этот баг все еще здесь. Я знаю, что использую бета-версию, но для тех, кто хочет использовать последнюю версию, это решение работает. Протестировано на уровне API эмулятора 21-25. Выше этого уровня API вам не нужно об этом беспокоиться.

это единственное решение, которое сработало для меня для androidx.appcompat... 1.2.0, в противном случае изменение локали не происходило на некоторых телефонах (в моем случае - samsung s5). Спасибо!

syed_noorullah 08.05.2021 23:14

Попробуйте что-то вроде этого:

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;
        }
    }
}   

Источники: комментарий об ошибке и первый образец, связанный с комментарием о проблеме.

В то время как вышеизложенное работает нормально для меня, другой вариант из второй образец, связанный с комментарием о проблеме выглядит следующим образом (я лично не пробовал):

@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;
    }
}
config.setLocale(locale); должен быть на Android N (24) или выше.
Rahmat Ihsan 27.05.2020 03:48

Я использую "androidx.appcompat:appcompat:1.3.0-alpha01", но я полагаю, что он также будет работать на Версия 1.2.0.
Следующий код основан на Поиск кода Android.

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
    }
}

Я думаю, что это самый простой ответ, который решает мою проблему, спасибо! @EhsanShadi Я просто подожду обновлений 1.3.0, что тогда произойдет :)

Ric17101 04.11.2020 10:09

Как этот работает? создание новой конфигурации переопределяет все предварительно определенные конфигурации и ломает пользовательский интерфейс.

dor506 12.11.2020 09:31

@dor506 dor506 с какими поломками пользовательского интерфейса вы сталкиваетесь? Кажется, это единственное решение, которое сработало для меня androidx.appcompat:appcompat:1.3.0-alpha03, но я был бы рад узнать о некоторых предостережениях, которые необходимо подготовить к выпуску. Спасибо

saintjab 16.11.2020 17:15

@saintjab Взгляните на последнее обновление 0101100101 (20 августа), которое действительно работает при обновлении текущей конфигурации (context.getResources().getConfiguration()), вместо создания новой конфигурации в каждом действии.

dor506 16.11.2020 22:38

Решение:

В 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

Теперь язык не меняется с этими библиотеками: androidx.appcompat:appcompat:1.1.0, androidx.appcompat:appcompat:1.2.0

Проблема была решена только в этой библиотеке: androidx.appcompat:appcompat:1.3.0-rc01

Эй, хорошо ли использовать версию 1.3.0-rc01 вместо 1.2.0 в выпуске производственного уровня?

Coder 14.04.2021 10:05

да это хорошо. В противном случае 1.2.0 вообще не работает. В моем случае были проблемы с контекстом приложения, но все было хорошо с контекстом действия.

Tim Kruichkov 14.04.2021 14:51

Другие вопросы по теме