Android N - Change Locale in runtime

2019-08-23 00:27发布

问题:

I have an android app to which I want to add a simple preferences screen with a single option to switch between to languages (english and portuguese). I have already the proper string resources files in place.

If I change the main language of the OS in the system preferences, and reload the app, it will use that language, however I would like to be able to do it via the preferences screen.

I saw in other questions here that in this was a lot easier to do in previous Android versions, but that code is now deprecated so I followed the approach of overriding the attachBaseContext method in every activity in order to recreate the context via a wrapper in which I load the locale currently selected in the preferences, as seen in this post:

Android N change language programatically

public class TCPreferenceActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {


@Override
public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.headers_preference, target);
}

@Override
protected boolean isValidFragment(String fragmentName) {
    return TCPreferenceFragment.class.getName().equals(fragmentName);
}


@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    if (key.equals("lang")) {
        recreate();
    }
}



@Override
protected void attachBaseContext(Context newBase) {

    SharedPreferences pref =  PreferenceManager.getDefaultSharedPreferences(newBase);

    String lang = pref.getString("lang", null);

    Locale locale = new Locale(lang);

    Context context = TCContextWrapper.wrap(newBase, locale);
    super.attachBaseContext(context);
}

}

So to my understanding, when changing the preference, the method onSharedPreferenceChanged is called. I recreate the activity there so that it can be relaunched with the new context.

This is my context wrapper:

public class TCContextWrapper extends ContextWrapper {

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

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

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

    if (android.os.Build.VERSION.SDK_INT >= 24) {
        configuration.setLocale(newLocale);

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

        context = context.createConfigurationContext(configuration);


    } else if (android.os.Build.VERSION.SDK_INT >= 17) {
        configuration.setLocale(newLocale);
        context = context.createConfigurationContext(configuration);

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

    return new ContextWrapper(context);
}

}

Debugging I can see that the onChange method is called, the preferences activity is recreated, the context wrapper is called, the new locale value is correctly created in the wrapper but as the activity launches I keep seeing the same default strings.

Any idea?

回答1:

Language @parameter that you are setting for Locale object should be

An ISO 639 alpha-2 or alpha-3 language code, or a language subtag
     * up to 8 characters in length.

According to the documentation. Therefore if you're using something else, you'll not see the updating locales.