Android context.getResources.updateConfiguration () ist veraltet

85

Erst kürzlich context.getResources (). updateConfiguration () ist in der Android API 25 veraltet und es wird empfohlen, den Kontext zu verwenden. stattdessen createConfigurationContext () .

Weiß jemand, wie createConfigurationContext verwendet werden kann, um das Gebietsschema des Android-Systems zu überschreiben?

bevor dies geschehen würde durch:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
                                 context.getResources().getDisplayMetrics());
Bassel Mourjan
quelle
Wie wäre es mit applyOverrideConfiguration (ungetestet)?
user1480019
Hier gibt es auch eine einfache Lösung, die dieser sehr ähnlich ist. stackoverflow.com/questions/39705739/…
Thanasis Saxanidis
[updateConfiguration war in API Level 25 veraltet] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

Antworten:

122

Inspiriert von der Kalligraphie habe ich schließlich einen Kontext-Wrapper erstellt. In meinem Fall muss ich die Systemsprache überschreiben, um meinen App-Benutzern die Möglichkeit zu geben, die App-Sprache zu ändern. Dies kann jedoch mit jeder Logik angepasst werden, die Sie implementieren müssen.

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

Um Ihren Wrapper zu injizieren, fügen Sie in jeder Aktivität den folgenden Code hinzu:

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

UPDATE 23/09/2020 Wenn Sie beispielsweise das App-Thema überschreiben, um beispielsweise den Dunkelmodus anzuwenden, wird ContextThemeWrapper die Spracheinstellung aufheben. Fügen Sie daher Ihrer Aktivität den folgenden Code hinzu, um das gewünschte Gebietsschema zurückzusetzen

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

UPDATE 19.10.2008 Manchmal wird das Konfigurationsobjekt nach einer Änderung der Ausrichtung oder einer Pause / Wiederaufnahme der Aktivität auf die Standardsystemkonfiguration zurückgesetzt. Im Ergebnis wird in der App englischer "en" -Text angezeigt, obwohl der Kontext mit dem französischen Gebietsschema "fr" umbrochen wurde . Behalten Sie daher das Context / Activity-Objekt in einer globalen Variablen in Aktivitäten oder Fragmenten niemals als bewährte Methode bei.

Erstellen und verwenden Sie außerdem Folgendes in MyBaseFragment oder MyBaseActivity:

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

Diese Vorgehensweise bietet Ihnen eine 100% fehlerfreie Lösung.

Bassel Mourjan
quelle
5
Ich habe ein Problem mit diesem Ansatz ... Dies wird derzeit nur für Aktivitäten und nicht für die gesamte Anwendung angewendet. Was passiert für App-Komponenten, die möglicherweise nicht von Aktivitäten wie Diensten ausgehen?
Rfgamaral
6
Warum sollten Sie den ContextWrapper erweitern? Sie haben nichts drin, nur statische Methoden?
Vladimir123
7
Ich musste createConfigurationContext / updateConfiguration aus dem if-else-Zweig entfernen und darunter hinzufügen, sonst war in der ersten Aktivität alles in Ordnung, aber beim zweiten Öffnen wurde die Sprache wieder auf die Standardeinstellung des Geräts geändert. Konnte den Grund nicht finden.
Kroky
3
Ich fügte die benötigte Zeile hinzu und postete sie wie folgt: gist.github.com/muhammad-naderi/…
Muhammad Naderi
2
@ Kroky ist richtig. Das Systemgebietsschema wird korrekt geändert, die Konfiguration wird jedoch auf die Standardeinstellungen zurückgesetzt. Infolgedessen wird die String-Ressourcendatei auf den Standard zurückgesetzt. Gibt es eine andere Möglichkeit, als die Konfiguration jedes Mal in jeder Aktivität festzulegen
Yash Ladia
29

Wahrscheinlich so:

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

Bonus: Ein Blog-Artikel, der createConfigurationContext () verwendet

compte14031879
quelle
Vielen Dank, dass Sie mich in die richtige Richtung gelenkt haben. Ich denke, irgendwann muss man einen ContextWrapper erstellen und ihn an Aktivitäten anhängen, wie sie von Calligraphy ausgeführt werden. Wie auch immer, die Auszeichnung gehört Ihnen, wird aber erst dann als endgültige Antwort betrachtet, wenn ich die richtige Kodierung der Problemumgehung veröffentlicht habe.
Bassel Mourjan
58
API 24 + ... Dummes Google, können sie uns nicht einfach einen einfachen Weg bieten?
Ab
7
@click_whir Zu sagen "Es ist einfach, wenn Sie nur auf diese Geräte abzielen" macht es nicht wirklich einfach.
Vlad
1
@Vlad Es gibt eine einfache Methode, wenn Sie keine Geräte unterstützen müssen, die vor 2012 hergestellt wurden. Willkommen bei der Anwendungsentwicklung!
click_whir
1
LocaleList
Woher
4

Inspiriert von Calligraphy & Mourjan & mir habe ich das erstellt.

Zuerst müssen Sie eine Unterklasse der Anwendung erstellen:

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

Dann müssen Sie dies auf Ihr AndroidManifest.xml-Anwendungstag setzen:

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

und fügen Sie dies Ihrem Aktivitäts-Tag AndroidManifest.xml hinzu.

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

Beachten Sie, dass pref_locale eine Zeichenfolgenressource wie diese ist:

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

und der Hardcode "en" ist die Standardsprache, wenn pref_locale nicht festgelegt ist

Mahpooya
quelle
Dies reicht nicht aus, Sie müssen auch den Kontext in jeder Aktivität überschreiben. Sie werden am Ende die Situation haben, dass Ihr baseContext ein Gebietsschema hat und Ihre Anwendung ein anderes. Als Ergebnis erhalten Sie gemischte Sprachen in Ihrer Benutzeroberfläche. Bitte sehen Sie meine Antwort.
Oleksandr Albul
3

Hier gibt es keine 100% funktionierende Lösung. Sie müssen beide createConfigurationContextund verwenden applyOverrideConfiguration. Ansonsten , auch wenn Sie ersetzen baseContextbei jeder Tätigkeit mit neuer Konfiguration würde Aktivität noch verwendet Resourcesaus ContextThemeWrappermit alten locale.

Hier ist meine Lösung, die bis zu API 29 funktioniert:

Unterklassifizieren Sie Ihre MainApplicationKlasse von:

abstract class LocalApplication : Application() {

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

Auch jeder Activityvon:

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

In LocaleExt.ktmit dem nächsten Erweiterungsfunktionen:

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
    }

Fügen Sie res/values/arrays.xmlIhren unterstützten Sprachen im Array Folgendes hinzu:

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

Ich möchte erwähnen:

  • Verwenden config.setLayoutDirection(toLocale);Sie diese Option, um die Layoutrichtung zu ändern, wenn Sie RTL-Gebietsschemas wie Arabisch, Persisch usw. verwenden.
  • "sys" im Code ist ein Wert, der "Systemstandardsprache erben" bedeutet.
  • Hier ist "langPref" ein bevorzugter Schlüssel, bei dem Sie die aktuelle Sprache des Benutzers eingeben.
  • Der Kontext muss nicht neu erstellt werden, wenn bereits das erforderliche Gebietsschema verwendet wird.
  • Es ist nicht erforderlich, ContextWraperwie hier veröffentlicht, createConfigurationContextsondern nur den neuen Kontext festzulegen, der als baseContext zurückgegeben wird
  • Dies ist sehr wichtig! Wenn Sie anrufen createConfigurationContext, sollten Sie die Konfiguration von Grund auf neu und nur mit festgelegten LocaleEigenschaften übergeben. Für diese Konfiguration sollte keine andere Eigenschaft festgelegt sein. Denn wenn wir einige andere Eigenschaften für diese Konfiguration festlegen ( z. B. Ausrichtung ), überschreiben wir diese Eigenschaft für immer, und unser Kontext ändert diese Ausrichtungseigenschaft nicht mehr, selbst wenn wir den Bildschirm drehen.
  • Es reicht nicht aus, nur zu recreateaktivieren, wenn der Benutzer eine andere Sprache auswählt, da applicationContext im alten Gebietsschema verbleibt und unerwartetes Verhalten hervorrufen kann. Hören Sie sich also die Änderung der Einstellungen an und starten Sie stattdessen die gesamte Anwendungsaufgabe neu:

fun Context.recreateTask() {
    this.packageManager
        .getLaunchIntentForPackage(context.packageName)
        ?.let { intent ->
            val restartIntent = Intent.makeRestartActivityTask(intent.component)
            this.startActivity(restartIntent)
            Runtime.getRuntime().exit(0)
         }
}
Oleksandr Albul
quelle
Das funktioniert nicht. Erwägen Sie auch, eine Basisaktivität für alle Aktivitäten zu erstellen, anstatt Code in jeder Aktivität zu duplizieren. recreateTask(Context context)Außerdem funktioniert die Methode nicht richtig, da ich das Layout immer noch ohne Änderung sehe.
Blueware
@blueware Ich habe die Beispiele aktualisiert. Es gab zuvor einige Fehler. Aber momentan sollte es funktionieren, das ist der Code aus meiner Produktions-App. Die lustige recreateTask konnte nicht funktionieren, Sie könnten einen Toast wie "Die Sprache wird nach dem Neustart geändert" anzeigen.
Oleksandr Albul
1

Hier ist @ bassel-mourjans Lösung mit ein bisschen Kotlin-Güte :):

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

Und so verwenden Sie es:

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}
Michael
quelle
Diese Zeile val config = baseContext.resources.configurationist sehr sehr falsch. Sie werden deshalb viele Fehler haben. Sie müssen stattdessen eine neue Konfiguration erstellen. Siehe meine Antwort.
Oleksandr Albul
0

Hier gibt es eine einfache Lösung mit contextWrapper: Android N ändert die Sprache programmgesteuert. Achten Sie auf die Methode recreate ()

Thanasis Saxanidis
quelle
Der Link ist hilfreich und eine gute Referenz. Ich glaube, es ist besser, die eigentliche Antwort hier aufzunehmen, als einen zusätzlichen Klick zu benötigen.
ToothlessRebel
Sie haben Recht, ich bin neu in Stackoverflow und ich dachte, es wäre falsch, Credits für die Antwort zu nehmen, also poste ich den Link des ursprünglichen Autors
Thanasis Saxanidis
-1

Versuche dies:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
Eric B.
quelle
1
Es schafft nur die Aktivität, die Sie benötigen, um den Kontext mit einer neuen zu wechseln
Ali Karaca