J'ai trouvé un bug vraiment bizarre qui est reproduit uniquement sur les appareils Android N.
En tournée de mon application, il y a une possibilité de changer de langue. Voici le code qui le change.
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());
}
Ce code fonctionne très bien dans l'activité de ma tournée (avec l'appel recreate()
) mais dans toutes les activités suivantes, toutes les ressources String sont fausses. La rotation de l'écran le corrige. Que puis-je faire avec ce problème? Devrais-je changer les paramètres régionaux pour Android N différemment ou c'est juste un bogue du système?
P.S. Voici ce que j'ai trouvé. Au premier démarrage de MainActivity (qui est après ma visite), Locale.getDefault()
est correct mais les ressources sont fausses. Mais dans d'autres activités, cela me donne une mauvaise localisation et de mauvaises ressources provenant de cette localisation. Après l'écran de rotation (ou peut-être un autre changement de configuration), Locale.getDefault()
est correct.
D'accord. Finalement, j'ai réussi à trouver une solution.
Tout d’abord, vous devez savoir que dans 25 API Resources.updateConfiguration(...)
est obsolète. Au lieu de cela, vous pouvez faire quelque chose comme ceci:
1) Vous devez créer votre propre ContextWrapper qui remplacera tous les paramètres de configuration dans baseContext. Par exemple, c’est le mien ContextWrapper qui modifie les paramètres régionaux correctement. Faites attention à la méthode context.createConfigurationContext(configuration)
.
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) Voici ce que vous devriez faire dans votre BaseActivity:
@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);
}
Remarque:
N'oubliez pas de recréer votre activité si vous souhaitez modifier les paramètres régionaux dans votre App quelque part. Vous pouvez remplacer n'importe quelle configuration souhaitée à l'aide de cette solution.
Inspiré par divers codes (nos frères de Stackoverflow (crier garçons)), j’avais produit une version beaucoup plus simple. L'extension ContextWrapper
est inutile.
D'abord, supposons que vous avez 2 boutons pour 2 langues, EN et KH. Dans le onClick pour les boutons enregistrer le code de langue dans SharedPreferences
, puis appelez la méthode activity recreate()
.
Exemple:
@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();
}
Créez ensuite une méthode statique qui retourne ContextWrapper
, peut-être dans une classe Utils (c'est ce que j'ai fait, 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);
}
Enfin, chargez le code de langue de SharedPreferences
dans la méthode ALL ACTIVITY'S attachBaseContext(Context newBase)
.
@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: Pour éviter les problèmes de Palm sur le clavier, j'ai créé une classe LangSupportBaseActivity
qui étend la Activity
et utilise le dernier bloc de code. Et j'ai toutes les autres activités étend LangSupportBaseActivity
.
Exemple:
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
}
Les réponses ci-dessus m'ont mis sur la bonne voie, mais ont laissé quelques problèmes
Pour réparer le premier élément, j'ai enregistré les paramètres régionaux par défaut au début de l'application.
Remarque Si la langue par défaut est "en", les paramètres régionaux "enGB" ou "enUS" doivent correspondre à la langue par défaut (à moins que vous ne fournissiez des localisations distinctes). De même, dans l'exemple ci-dessous, si les paramètres régionaux du téléphone de l'utilisateur sont arLY (Arabic Libya), le defLanguage doit être "ar" et non "arLY".
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;
}
Pour résoudre les problèmes de RTL, j'ai étendu AppCompatActivity conformément aux commentaires de Fragments dans cette answer
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);
}
}
}