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()
呼び出し)でうまく機能しますが、次のすべてのアクティビティでは、すべての文字列リソースが間違っています。画面の回転により修正されます。この問題で何ができますか? Android Nのロケールを変更する必要がありますか、それとも単なるシステムバグですか?
追伸ここに私が見つけたものがあります。 MainActivityの最初の開始時(ツアーの後)Locale.getDefault()
は正しいが、リソースが間違っている。しかし、他のアクティビティでは、このロケールから間違ったロケールと誤ったリソースを取得します。回転画面(またはおそらく他の構成変更)Locale.getDefault()
が正しい後。
OK。最後に、解決策を見つけることができました。
最初に、25 API Resources.updateConfiguration(...)
が非推奨になっていることを知っておく必要があります。そのため、代わりに次のようなことができます。
1)baseContextのすべての構成パラメーターをオーバーライドする独自のContextWrapperを作成する必要があります。たとえば、これはロケールを正しく変更する私のContextWrapperです。 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);
}
注:
アプリのロケールをどこかで変更する場合は、アクティビティを再作成することを忘れないでください。このソリューションを使用して、必要な構成をオーバーライドできます。
さまざまなコード(つまり、Stackoverflowチーム(大声で叫ぶ))に触発されて、私はもっと単純なバージョンを作成しました。 ContextWrapper
拡張子は不要です。
まず、ENとKHの2つの言語用の2つのボタンがあるとします。ボタンの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();
}
次に、おそらくUtilsクラスでContextWrapper
を返す静的メソッドを作成します(これは私がやったことです、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);
}
最後に、言語コードをSharedPreferences
からALL ACTIVITY'SattachBaseContext(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);
}
ボーナス:キーボードのPalm汗を節約するために、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
}
上記の答えは私を正しい方向に導きましたが、いくつかの問題を残しました
最初の項目を修正するために、アプリの起動時にデフォルトのロケールを保存しました。
注デフォルト言語が「en」に設定されている場合、「enGB」または「enUS」のロケールは両方ともデフォルトロケールに一致する必要があります(指定しない限り)彼らのために別々のローカライズ)。同様に、以下の例では、ユーザーの電話ロケールがarly(アラビア語リビア)の場合、defLanguageは「arLY」ではなく「ar」である必要があります。
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 のフラグメントコメントに従って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);
}
}
}