Android context.getResources.updateConfiguration() устарел

Совсем недавно context.getResources(). updateConfiguration() устарел в Android API 25, и рекомендуется использовать контекст. createConfigurationContext().

Кто-нибудь знает, как createConfigurationContext можно использовать для переопределения локали системы Android?

прежде чем это будет сделано:

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

Ответ 1

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

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

ОБНОВЛЕНИЕ 19/19/2018 Иногда после изменения ориентации или приостановки/возобновления действия объект конфигурации сбрасывается до конфигурации системы по умолчанию, и в результате мы увидим приложение, отображающее текст на английском языке "en", даже если мы обернули контекст французским языком "fr". Поэтому и не рекомендуется сохранять объект Context/Activity в глобальной переменной в действиях или фрагментах.

более того, создайте и используйте следующее в MyBaseFragment или MyBaseActivity:

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

Эта практика предоставит вам 100% безошибочное решение.

Ответ 3

Вдохновленный Каллиграфией и Моуржаном и мной, я создал это.

сначала вы должны создать подкласс приложения:

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" по умолчанию lang, если pref_locale не установлен

Ответ 4

Здесь @bassel-mourjan решение с небольшим количеством совершенства kotlin :):

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

Ответ 5

Попробуйте следующее:

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

Ответ 7

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

Итак, вот мое решение, которое работает до API 29:

В вашем классе приложений:

public class MainApplication extends Application {

    protected void attachBaseContext(Context base) {
        super.attachBaseContext(LocaleHelper.changeLanguageIfDiff(
                base, PreferenceManager.getDefaultSharedPreferences(base).getString("langPref", "sys")));
    }
}

Также в каждом занятии:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(LocaleHelper.changeLanguageIfDiff(
                newBase, PreferenceManager.getDefaultSharedPreferences(base).getString("langPref", "sys")));
    }

    @Override
    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
       super.applyOverrideConfiguration(getBaseContext().getResources().getConfiguration());
    }
}

А вот и LocaleHelper:

public class LocaleHelper {
    public static boolean isAppLangDiff(final Context context, final String prefLang) {
        final Configuration appConfig = context.getResources().getConfiguration();
        final Configuration sysConfig = Resources.getSystem().getConfiguration();

        final String appLang = getLocale(appConfig).getLanguage();
        final String sysLang = getLocale(sysConfig).getLanguage();

        if ("sys".equals(prefLang)) { // "System default" preference value
            return !appLang.equals(sysLang);
        } else {
            return !appLang.equals(prefLang);
        }
    }

    public static Context changeLanguageIfDiff(final Context context, final String prefLang) {
        return isAppLangDiff(context, prefLang)
                ? changeLanguage(context, prefLang)
                : context;
    }

    public static Context changeLanguage(final Context context, final String toLang) {
        final Resources res = context.getResources();
        final Configuration config = res.getConfiguration();
        // Here some workaround with Chinese Simplified/Traditional
        final Locale toLocale = toLang.contains("zh")
                ? (toLang.contains("rCN")
                ? Locale.SIMPLIFIED_CHINESE : Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                    ? new Locale("zh", "Hant") : Locale.TRADITIONAL_CHINESE)
                    : new Locale(toLang);

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

            final LocaleList localeList = new LocaleList(toLocale);
            LocaleList.setDefault(localeList);
            config.setLocales(localeList);
        } else {
            config.locale = toLocale;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            config.setLayoutDirection(toLocale);
            return context.createConfigurationContext(config);
        } else {
            res.updateConfiguration(config, res.getDisplayMetrics());
            return context;
        }
    }

    public static Locale getLocale(Configuration config) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                ? config.getLocales().get(0)
                : config.locale;
    }
}

Я использую следующие языковые значения для предпочтения:

<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" в коде является значением, которое означает "наследовать язык системы по умолчанию".
  • Нет необходимости воссоздавать контекст, если он уже использует необходимый локали.
  • Нет необходимости в ContextWraper, как здесь опубликовано, просто установите новый контекст, возвращаемый из createConfigurationContext, как baseContext
  • Недостаточно только действия recreate, когда пользователь выбирает другой язык, потому что applicationContext останется со старой локалью и может обеспечить непредвиденное поведение. Поэтому прослушайте изменение предпочтений и перезапустите всю задачу приложения:

public static void recreateTask(final Context context) {
    final PackageManager pm = context.getPackageManager();
    final Intent intent = pm.getLaunchIntentForPackage(context.getPackageName());
    final ComponentName componentName = intent.getComponent();
    final Intent mainIntent = Intent.makeRestartActivityTask(componentName);
    context.startActivity(mainIntent);
    Runtime.getRuntime().exit(0);
}