web-dev-qa-db-de.com

Android N ändert die Sprache programmgesteuert

Ich fand einen wirklich seltsamen Fehler, der nur auf Android N-Geräten reproduziert wird.

In der Tour meiner App gibt es eine Möglichkeit, die Sprache zu wechseln. Hier ist der Code, der es ändert. 

 public void update(Locale locale) {

    Locale.setDefault(locale);

    Configuration configuration = res.getConfiguration();

    if (BuildUtils.isAtLeast24Api()) {
        LocaleList localeList = new LocaleList(locale);

        LocaleList.setDefault(localeList);
        configuration.setLocales(localeList);
        configuration.setLocale(locale);

    } else if (BuildUtils.isAtLeast17Api()){
        configuration.setLocale(locale);

    } else {
        configuration.locale = locale;
    }

    res.updateConfiguration(configuration, res.getDisplayMetrics());
}

Dieser Code funktioniert gut in der Aktivität meiner Tour (mit recreate() call), aber bei allen nächsten Aktivitäten sind alle String-Ressourcen falsch. Bildschirmdrehung behebt das Problem. Was kann ich mit diesem Problem anfangen? Sollte ich die Ländereinstellung für Android N anders ändern, oder ist es nur ein Systemfehler?

P.S. Hier ist was ich gefunden habe. Beim ersten Start von MainActivity (was nach meiner Tour ist) ist Locale.getDefault() korrekt, aber die Ressourcen sind falsch. Bei anderen Aktivitäten gibt es jedoch falsche Gebietsschemas und falsche Ressourcen aus diesem Gebietsschema. Nach dem Drehen des Bildschirms (oder einer anderen Konfigurationsänderung) ist Locale.getDefault() korrekt.

42
Kuva

OK. Endlich habe ich eine Lösung gefunden. 

Zunächst sollten Sie wissen, dass in 25 API Resources.updateConfiguration(...) veraltet ist. Stattdessen kannst du so etwas tun:

1) Sie müssen einen eigenen ContextWrapper erstellen, der alle Konfigurationsparameter in baseContext überschreibt. Dies ist beispielsweise mein ContextWrapper, der das Gebietsschema korrekt ändert. Beachten Sie die context.createConfigurationContext(configuration)-Methode.

public class ContextWrapper extends Android.content.ContextWrapper {

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

public static ContextWrapper wrap(Context context, Locale newLocale) {

    Resources res = context.getResources();
    Configuration configuration = res.getConfiguration();

    if (BuildUtils.isAtLeast24Api()) {
        configuration.setLocale(newLocale);

        LocaleList localeList = new LocaleList(newLocale);
        LocaleList.setDefault(localeList);
        configuration.setLocales(localeList);

        context = context.createConfigurationContext(configuration);

    } else if (BuildUtils.isAtLeast17Api()) {
        configuration.setLocale(newLocale);
        context = context.createConfigurationContext(configuration);

    } else {
        configuration.locale = newLocale;
        res.updateConfiguration(configuration, res.getDisplayMetrics());
    }

    return new ContextWrapper(context);
}}

2) Was Sie in Ihrer BaseActivity tun sollten:

  @Override
protected void attachBaseContext(Context newBase) {

    Locale newLocale;
    // .. create or get your new Locale object here.

    Context context = ContextWrapper.wrap(newBase, newLocale);
    super.attachBaseContext(context);
}

Hinweis:

Denken Sie daran, Ihre Aktivität neu zu erstellen, wenn Sie das Gebietsschema in .__ ändern möchten. Deine App irgendwo. Sie können jede gewünschte Konfiguration mit .__ überschreiben. diese Lösung.

84
Kuva

Inspiriert von verschiedenen Codes (d. H. Unseren Stackoverflow-Brüdern), hatte ich eine viel einfachere Version erstellt. Die Erweiterung ContextWrapper ist nicht erforderlich.

Nehmen wir an, Sie haben 2 Knöpfe für 2 Sprachen, EN und KH. Speichern Sie im onClick für die Schaltflächen den Sprachcode in SharedPreferences und rufen Sie dann die Activity recreate()-Methode auf.

Beispiel:

@Override
public void onClick(View v) {
    switch(v.getId()) {
        case R.id.btn_lang_en:
            //save "en" to SharedPref here
            break;
        case R.id.btn_lang_kh:
            //save "kh" to SharedPref here
            break;

        default:
        break;
    }
    getActivity().recreate();
}

Dann erstellen Sie eine statische Methode, die ContextWrapper zurückgibt, möglicherweise in einer Utils-Klasse (coz ist das, was ich getan habe, lul).

public static ContextWrapper changeLang(Context context, String lang_code){
    Locale sysLocale;

    Resources rs = context.getResources();
    Configuration config = rs.getConfiguration();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        sysLocale = config.getLocales().get(0);
    } else {
        sysLocale = config.locale;
    }
    if (!lang_code.equals("") && !sysLocale.getLanguage().equals(lang_code)) {
        Locale locale = new Locale(lang_code);
        Locale.setDefault(locale);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            config.setLocale(locale);
        } else {
            config.locale = 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 ContextWrapper(context);
}

Laden Sie abschließend den Sprachcode von SharedPreferences in der ALL ACTIVITY'S attachBaseContext(Context newBase)-Methode.

@Override
protected void attachBaseContext(Context newBase) {
    String lang_code = "en"; //load it from SharedPref
    Context context = Utils.changeLang(newBase, lang_code);
    super.attachBaseContext(context);
}

BONUS: Um Palm Sweat auf der Tastatur zu speichern, habe ich eine LangSupportBaseActivity-Klasse erstellt, die die Activity erweitert und den letzten Code-Code dort verwendet. Und ich habe alle anderen Aktivitäten erweitert LangSupportBaseActivity.

Beispiel:

public class LangSupportBaseActivity extends Activity{
    ...blab blab blab so on and so forth lines of neccessary code

    @Override
    protected void attachBaseContext(Context newBase) {
        String lang_code = "en"; //load it from SharedPref
        Context context = Utils.changeLang(newBase, lang_code);
        super.attachBaseContext(context);
    }
}

public class HomeActivity extends LangSupportBaseActivity{
    ...blab blab blab
}
15
thyzz

Die obigen Antworten brachten mich auf den richtigen Weg, ließen jedoch einige Probleme

  1. Unter Android 7 und 9 konnte ich gerne zu einer anderen Sprache als der Standardeinstellung der App wechseln. Als ich zur Standardsprache der App zurückkehrte, wurde die zuletzt gewählte Sprache angezeigt. Dies ist nicht weiter verwunderlich, da dies die Standardeinstellung überschrieben hat (obwohl dies interessanterweise bei Android 8 kein Problem war!).
  2. Für RTL-Sprachen wurden die Layouts nicht in RTL aktualisiert

Um das erste Element zu reparieren, habe ich beim Start der App das Standardgebietsschema gespeichert. 

Note Wenn Ihre Standardsprache auf "en" gesetzt ist, müssen die Ländereinstellungen von "enGB" oder "enUS" beide dem Standardgebietsschema entsprechen (es sei denn, Sie geben getrennte Lokalisierungen für sie an). Ähnlich wie in dem folgenden Beispiel, wenn das Gebietsschema des Benutzers arLY (Arabisch-Libyen) ist, muss die DefLanguage "ar" und nicht "arLY" sein.

private Locale defLocale = Locale.getDefault();
private Locale locale = Locale.getDefault();
public static myApplication myApp;
public static Resources res;
private static String defLanguage = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry();
private static sLanguage = "en";
private static final Set<String> SUPPORTEDLANGUAGES = new HashSet<>(Arrays.asList(new String[]{"en", "ar", "arEG"})); 

@Override
protected void attachBaseContext(Context base) {
  if (myApp == null) myApp = this;
  if (base == null) super.attachBaseContext(this);
  else super.attachBaseContext(setLocale(base));
}

@Override
public void onCreate() {
  myApp = this;

  if (!SUPPORTEDLANGUAGES.contains(test)) {
    // The default locale (eg enUS) is not in the supported list - lets see if the language is
    if (SUPPORTEDLANGUAGES.contains(defLanguage.substring(0,2))) {
      defLanguage = defLanguage.substring(0,2);
    }
  }
}

private static void setLanguage(String sLang) {
  Configuration baseCfg = myApp.getBaseContext().getResources().getConfiguration();
  if ( sLang.length() > 2 ) {
    String s[] = sLang.split("_");
    myApp.locale = new Locale(s[0],s[1]);
    sLanguage = s[0] + s[1];
  }
  else {
    myApp.locale = new Locale(sLang);
    sLanguage = sLang;
  }
}

public static Context setLocale(Context ctx) {
  Locale.setDefault(myApp.locale);
  Resources tempRes = ctx.getResources();
  Configuration config = tempRes.getConfiguration();

  if (Build.VERSION.SDK_INT >= 24) {
    // If changing to the app default language, set locale to the default locale
    if (sLanguage.equals(myApp.defLanguage)) {
      config.setLocale(myApp.defLocale);
      // restored the default locale as well
      Locale.setDefault(myApp.defLocale);
    }
    else config.setLocale(myApp.locale);

    ctx = ctx.createConfigurationContext(config);

    // update the resources object to point to the current localisation
    res = ctx.getResources();
  } else {
    config.locale = myApp.locale;
    tempRes.updateConfiguration(config, tempRes.getDisplayMetrics());
  }

  return ctx;
}

Um die RTL-Probleme zu beheben, erweiterte ich AppCompatActivity gemäß den Fragmenten-Kommentaren in dieser Antwort

public class myCompatActivity extends AppCompatActivity {
  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(myApplication.setLocale(base));
  }

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (Build.VERSION.SDK_INT >= 17) {
      getWindow().getDecorView().setLayoutDirection(myApplication.isRTL() ?
              View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
    }
  }
}
0
QuantumTiger