Android N以编程方式更改语言

问题描述 投票:43回答:3

我发现非常奇怪的错误只能在Android N设备上重现。

在浏览我的应用程序时,有可能改变语言。这是改变它的代码。

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

这段代码非常适合我的游览活动(使用recreate()调用),但在所有下一个活动中,所有String资源都是错误的。屏幕旋转修复它。我该怎么办这个问题?我应该以不同方式更改Android N的区域设置,还是只是系统错误?

附:这是我发现的。首次启动MainActivity(在我的巡演之后)Locale.getDefault()是正确的,但资源是错误的。但在其他活动中,它给了我错误的Locale和来自此语言环境的错误资源。旋转屏幕(或可能是其他一些配置更改)后Locale.getDefault()是正确的。

java android android-7.0-nougat
3个回答
90
投票

好。最后我设法找到了解决方案。

首先你应该知道,在25个API中,Resources.updateConfiguration(...)已被弃用。所以你可以这样做:

1)您需要创建自己的ContextWrapper,它将覆盖baseContext中的所有配置参数。例如,这是我的ContextWrapper,可以正确地更改Locale。注意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)以下是您应该在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);
}

注意:

如果要在某个位置更改应用程序中的区域设置,请记住重新创建活动。您可以使用此解决方案覆盖所需的任何配置。


18
投票

受到各种代码的启发(即:我们的Stackoverflow团队(大声喊叫)),我制作了一个更简单的版本。 ContextWrapper扩展是不必要的。

首先,假设您有2个语言的两个按钮,EN和KH。在按钮的onClick中将语言代码保存到SharedPreferences中,然后调用活动recreate()方法。

例:

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

然后创建一个返回ContextWrapper的静态方法,也许是在Utils类中(因为我做的就是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);
}

最后,在所有ACTIVITY'S SharedPreferences方法中加载来自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);
}

奖励:为了节省键盘上的手掌汗,我创建了一个LangSupportBaseActivity类,扩展了Activity并使用那里的最后一块代码。而我所有其他活动都延伸了LangSupportBaseActivity

例:

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
}

0
投票

上面的答案让我走上正轨,但留下了一些问题

  1. 在Android 7和9上,我可以愉快地更改为除应用默认之外的任何语言。当我改回应用程序默认语言时,它显示了最后选择的语言 - 这并不奇怪,因为它已经覆盖了默认语言(虽然有趣的是这不是Android 8上的问题!)。
  2. 对于RTL语言,它没有将布局更新为RTL

要修复第一项,我在app start上存储了默认语言环境。

注意如果您的默认语言设置为“en”,则“enGB”或“enUS”的语言环境都需要与默认语言环境匹配(除非您为它们提供单独的本地化)。类似地,在下面的示例中,如果用户的电话区域设置是arLY(阿拉伯利比亚),那么defLanguage需要是“ar”而不是“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;
}

为了解决RTL问题,我根据此answer中的Fragments注释扩展了AppCompatActivity

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);
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.