Android context.getResources.updateConfiguration () deprecato


92

Di recente context.getResources (). updateConfiguration () è stato deprecato nell'API 25 di Android e si consiglia di utilizzare il contesto. createConfigurationContext () invece.

Qualcuno sa come createConfigurationContext per sovrascrivere le locali del sistema Android?

prima che ciò venga fatto da:

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

Che ne dici di applyOverrideConfiguration (non testato)?
user1480019

v'è anche una soluzione semplice qui, molto simile a questo stackoverflow.com/questions/39705739/...
Thanasis Saxanidis

[updateConfiguration è stato deprecato nel livello API 25] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

Risposte:


126

Ispirato dalla calligrafia , ho finito per creare un involucro di contesto. Nel mio caso, ho bisogno di sovrascrivere la lingua del sistema per fornire agli utenti della mia app la possibilità di cambiare la lingua dell'app, ma questo può essere personalizzato con qualsiasi logica che è necessario implementare.

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

e per iniettare il tuo wrapper, in ogni attività aggiungi il seguente codice:

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

AGGIORNAMENTO 22/12/2020 Dopo l'implementazione della libreria di materiali Android di ContextThemeWrapper per supportare la modalità oscura, l'impostazione della lingua si interrompe e l'impostazione della lingua viene persa. Dopo mesi di grattarsi la testa, il problema è stato risolto aggiungendo il seguente codice al metodo Activity e Fragment onCreate

Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr");
   getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());

AGGIORNAMENTO 19/10/2018 A volte dopo la modifica dell'orientamento o la pausa / ripresa dell'attività, l'oggetto Configurazione si ripristina alla configurazione di sistema predefinita e di conseguenza vedremo l'app che mostra il testo inglese "en" anche se abbiamo racchiuso il contesto con la lingua francese "fr" . Pertanto, come buona pratica, non conservare mai l'oggetto Context / Activity in una variabile globale in attività o frammenti.

inoltre, creare e utilizzare quanto segue in un MyBaseFragment o MyBaseActivity:

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

Questa pratica ti fornirà una soluzione priva di bug al 100%.


5
Ho una preoccupazione con questo approccio ... Questo è attualmente applicato solo alle attività e non all'intera applicazione. Cosa succederà ai componenti dell'app che potrebbero non iniziare da attività, come i servizi?
rfgamaral

6
Perché estendere il ContextWrapper? Non hai niente dentro, solo metodi statici?
vladimir123

7
Ho dovuto estrarre createConfigurationContext / updateConfiguration dal ramo if-else e aggiungerlo sotto, altrimenti nella prima attività tutto era ok, ma quando è stato aperto per secondo, la lingua è tornata all'impostazione predefinita del dispositivo. Impossibile trovare il motivo.
kroky

3
Ho aggiunto la riga necessaria e l'ho postata
Muhammad Naderi

2
@kroky ha ragione. Le impostazioni internazionali del sistema vengono modificate correttamente, ma la configurazione torna all'impostazione predefinita. Di conseguenza, il file di risorse delle stringhe torna al valore predefinito. C'è un altro modo, oltre a impostare la configurazione ogni volta in ogni attività
Yash Ladia

32

Probabilmente in questo modo:

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

Bonus: un articolo del blog che usa createConfigurationContext ()


Grazie per avermi indirizzato nella giusta direzione, immagino che alla fine si dovrà creare un ContextWrapper e collegarlo alle attività come è fatto da Calligraphy. In ogni caso il premio è tuo, ma non lo considererò una risposta definitiva fino a quando non pubblicherò la giusta codifica della soluzione alternativa.
Bassel Mourjan

59
API 24 + ... Stupido Google, non possono semplicemente fornirci un modo semplice?
Ali Bdeir

7
@click_whir Dire "È semplice se scegli come target solo questi dispositivi" non lo rende davvero semplice.
Vlad

1
@Vlad Esiste un metodo semplice se non è necessario supportare dispositivi realizzati prima del 2012. Benvenuti nello sviluppo dell'applicazione!
click_whir

1
da dove hai presoLocaleList
EdgeDev

4

Ispirato da Calligraphy, Mourjan e me stesso, ho creato questo.

prima devi creare una sottoclasse di applicazione:

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

quindi devi impostarlo sul tag dell'applicazione AndroidManifest.xml:

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

e aggiungilo al tuo tag attività AndroidManifest.xml.

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

nota che pref_locale è una risorsa stringa come questa:

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

e hardcode "en" è la lingua predefinita se pref_locale non è impostato


Questo non è abbastanza, devi anche sovrascrivere il contesto in ogni attività. Poiché ti ritroverai con una situazione in cui il tuo baseContext ha una locale e la tua applicazione ne avrà un'altra. Di conseguenza avrai lingue miste nella tua interfaccia utente. Si prega di vedere la mia risposta.
Oleksandr Albul

3

Non esiste una soluzione funzionante al 100%. Devi usare sia createConfigurationContexte applyOverrideConfiguration. Altrimenti, anche se sostituisci baseContextin ogni attività con una nuova configurazione, l'attività sarebbe ancora utilizzata Resourcesda ContextThemeWrappercon la vecchia locale.

Quindi ecco la mia soluzione che funziona fino all'API 29:

Sottoclasse la tua MainApplicationclasse da:

abstract class LocalApplication : Application() {

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

Inoltre ogni Activityda:

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

Aggiungi LocaleExt.ktcon le prossime funzioni di estensione:

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
    }

Aggiungi alle tue res/values/arrays.xmllingue supportate in array:

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

Voglio menzionare:

  • Uso config.setLayoutDirection(toLocale); per modificare la direzione del layout quando si utilizzano le impostazioni locali RTL come arabo, persiano, ecc.
  • "sys" nel codice c'è un valore che significa "eredita la lingua predefinita del sistema".
  • Qui "langPref" è una chiave di preferenza in cui inserisci la lingua corrente dell'utente.
  • Non è necessario ricreare il contesto se utilizza già le impostazioni locali necessarie.
  • Non è necessario ContextWraperquanto pubblicato qui, basta impostare il nuovo contesto restituito dacreateConfigurationContext come baseContext
  • Questo è molto importante! Quando si chiama, createConfigurationContextè necessario passare la configurazione creata da zero e solo con la Localeproprietà impostata. Non dovrebbero esserci altre proprietà impostate su questa configurazione. Perché se impostiamo alcune altre proprietà per questa configurazione ( orientamento per esempio), sovrascriviamo quella proprietà per sempre e il nostro contesto non cambia più questa proprietà di orientamento anche se ruotiamo lo schermo.
  • Non è sufficiente solo recreateattivarsi quando l'utente seleziona una lingua diversa, perché applicationContext rimarrà con le vecchie impostazioni internazionali e potrebbe fornire un comportamento imprevisto. Quindi ascolta la modifica delle preferenze e riavvia invece l'intera attività dell'applicazione:

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

Questo non funziona. Considera anche la possibilità di creare un'attività di base per tutte le attività invece di duplicare il codice in ogni attività. Inoltre, il recreateTask(Context context)metodo non funziona correttamente poiché vedo ancora il layout senza alcuna modifica.
blueware

@blueware ho aggiornato i campioni. C'erano alcuni bug in precedenza. Ma attualmente dovrebbe funzionare, questo è il codice della mia app di produzione. Il divertente recreateTask non poteva funzionare, potresti visualizzare un brindisi del tipo "La lingua verrà cambiata dopo il riavvio" ..
Oleksandr Albul

1

Ecco la soluzione di @ bassel-mourjan con un po 'di bontà 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)
}

Ed ecco come lo usi:

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

Questa linea val config = baseContext.resources.configurationè molto molto sbagliata. Ti ritroverai con molti bug a causa di questo. È invece necessario creare una nuova configurazione. Vedi la mia risposta.
Oleksandr Albul

0

c'è una soluzione semplice con contextWrapper qui: Android N cambia lingua in modo programmatico Presta attenzione al metodo recreate ()


Il collegamento è utile e un buon riferimento. Credo che sia meglio includere la risposta effettiva qui piuttosto che richiedere un clic aggiuntivo.
Sdentato

hai ragione, sono appena nuovo di StackOverflow e ho pensato che sarebbe stato sbagliato prendere i crediti per la risposta, quindi
inserisco

-1

Prova questo:

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

1
Crea solo l'attività di cui hai bisogno per cambiare contesto con uno nuovo
Ali Karaca
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.