Android context.getResources.updateConfiguration()は非推奨


92

つい最近、context.getResources()。updateConfiguration()はAndroid API 25で非推奨になり、コンテキストを使用することをお勧めします。代わりにcreateConfigurationContext()

createConfigurationContextを使用してAndroidシステムロケールをオーバーライドする方法を知っている人はいますか?

これが行われる前に:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
                                 context.getResources().getDisplayMetrics());

どの程度applyOverrideConfiguration(未テスト)?
user1480019 2016年

ここにも簡単な解決策があります。これはstackoverflow.com/questions/39705739/
ます…

[updateConfigurationはAPIレベル25で非推奨になりました] developer.android.com/reference/android/content/res/Resources
Md SifatulIslam18年

回答:


126

書道に触発されて、私はコンテキストラッパーを作成することになりました。私の場合、アプリユーザーにアプリ言語を変更するオプションを提供するためにシステム言語を上書きする必要がありますが、これは実装する必要のある任意のロジックでカスタマイズできます。

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.ContextWrapper;
    import android.content.res.Configuration;
    import android.os.Build;
    
    import java.util.Locale;
    
    public class MyContextWrapper extends ContextWrapper {

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

    @SuppressWarnings("deprecation")
    public static ContextWrapper wrap(Context context, String language) {
        Configuration config = context.getResources().getConfiguration();
        Locale sysLocale = null;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
            sysLocale = getSystemLocale(config);
        } else {
            sysLocale = getSystemLocaleLegacy(config);
        }
        if (!language.equals("") && !sysLocale.getLanguage().equals(language)) {
            Locale locale = new Locale(language);
            Locale.setDefault(locale);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                setSystemLocale(config, locale);
            } else {
                setSystemLocaleLegacy(config, locale);
            }
            
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             context = context.createConfigurationContext(config);
        } else {
             context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
            }
        return new MyContextWrapper(context);
    }

    @SuppressWarnings("deprecation")
    public static Locale getSystemLocaleLegacy(Configuration config){
        return config.locale;
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static Locale getSystemLocale(Configuration config){
        return config.getLocales().get(0);
    }

    @SuppressWarnings("deprecation")
    public static void setSystemLocaleLegacy(Configuration config, Locale locale){
        config.locale = locale;
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static void setSystemLocale(Configuration config, Locale locale){
        config.setLocale(locale);
    }
}

ラッパーを挿入するには、すべてのアクティビティで次のコードを追加します。

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}

更新22/12/2020 ダークモードをサポートするためのContextThemeWrapperのAndroidマテリアルライブラリの実装後、言語設定が壊れ、言語設定が失われます。数か月のヘッドスクラッチの後、Activity and Fragment onCreateメソッドに次のコードを追加することで、問題が解決しました。

Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr");
   getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());

UPDATE 10/19/2018 向きの変更後、またはアクティビティの一時停止/再開後、構成オブジェクトがデフォルトのシステム構成にリセットされることがあります。その結果、コンテキストをフランス語の「fr」ロケールでラップしたにもかかわらず、アプリに英語の「en」テキストが表示されます。 。したがって、グッドプラクティスとして、アクティビティまたはフラグメントのグローバル変数にContext / Activityオブジェクトを保持しないでください。

さらに、MyBaseFragmentまたはMyBaseActivityで以下を作成して使用します。

public Context getMyContext(){
    return MyContextWrapper.wrap(getContext(),"fr");
}

この方法により、100%バグのないソリューションが提供されます。


5
このアプローチには1つの懸念があります...これは現在、アプリケーション全体ではなく、アクティビティにのみ適用されています。サービスなどのアクティビティから開始されない可能性のあるアプリコンポーネントはどうなりますか?
rfgamaral 2016

6
なぜContextWrapperを拡張するのですか?静的メソッドだけで、何もありませんか?
vladimir123 2016

7
if-elseブランチからcreateConfigurationContext / updateConfigurationを取り出してその下に追加する必要がありました。それ以外の場合、最初のアクティビティではすべて問題ありませんでしたが、2番目に開くと、言語がデバイスのデフォルトに戻りました。理由が見つかりませんでした。
kroky 2017年

3
必要な行を追加して、この要点として投稿しました:gist.github.com/muhammad-naderi/…–
Muhammad Naderi

2
@krokyは正しいです。システムロケールは正しく変更されますが、構成はデフォルトに戻ります。その結果、文字列リソースファイルはデフォルトに戻ります。すべてのアクティビティで毎回構成を設定する以外に、他の方法はありますか
Yash Ladia 2017年

32

おそらくこのように:

Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context  = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();

ボーナス:createConfigurationContext()を使用するブログ記事


私を正しい方向に向けてくれてありがとう。最終的にはContextWrapperを作成して、書道のようにアクティビティに添付する必要があると思います。とにかく賞はあなたのものですが、私が回避策の正しいコーディングを投稿するまで、それを最終的な答えとは見なしません。
Bassel Mourjan 2016年

59
API 24 + ...愚かなグーグル、彼らは私たちに簡単な方法を提供するだけではありませんか?
Ali Bdeir 2017

7
@click_whir「これらのデバイスのみをターゲットにすれば簡単です」と言っても、実際には簡単ではありません。
vlad 2017

1
@Vlad 2012年より前に製造されたデバイスをサポートする必要がない場合は、簡単な方法があります。アプリケーション開発へようこそ。
click_whir 2017

1
あなたが手に入れたところからLocaleList
EdgeDev

4

書道&モージャン&私に触発されて、私はこれを作成しました。

まず、Applicationのサブクラスを作成する必要があります。

public class MyApplication extends Application {
    private Locale locale = null;

    @Override
    public void onCreate() {
        super.onCreate();

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = preferences.getString(getString(R.string.pref_locale), "en");
        String systemLocale = getSystemLocale(config).getLanguage();
        if (!"".equals(lang) && !systemLocale.equals(lang)) {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            setSystemLocale(config, locale);
            updateConfiguration(config);
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (locale != null) {
            setSystemLocale(newConfig, locale);
            Locale.setDefault(locale);
            updateConfiguration(newConfig);
        }
    }

    @SuppressWarnings("deprecation")
    private static Locale getSystemLocale(Configuration config) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return config.getLocales().get(0);
        } else {
            return config.locale;
        }
    }

    @SuppressWarnings("deprecation")
    private static void setSystemLocale(Configuration config, Locale locale) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            config.setLocale(locale);
        } else {
            config.locale = locale;
        }
    }

    @SuppressWarnings("deprecation")
    private void updateConfiguration(Configuration config) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            getBaseContext().createConfigurationContext(config);
        } else {
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

次に、これをAndroidManifest.xmlアプリケーションタグに設定する必要があります。

<application
    ...
    android:name="path.to.your.package.MyApplication"
    >

これをAndroidManifest.xmlアクティビティタグに追加します。

<activity
    ...
    android:configChanges="locale"
    >

pref_localeは次のような文字列リソースであることに注意してください。

<string name="pref_locale">fa</string>

pref_localeが設定されていない場合、ハードコード「en」がデフォルトの言語になります


これだけでは不十分です。すべてのアクティビティでコンテキストをオーバーライドする必要もあります。baseContextに1つのロケールがあり、アプリケーションに別のロケールがあるという状況になってしまうためです。その結果、UIに混合言語が含まれるようになります。私の答えを見てください。
OleksandrAlbul20年

3

これは100%機能するソリューションではありません。createConfigurationContextとの両方を使用する必要がありapplyOverrideConfigurationます。そうしないとbaseContext、すべてのアクティビティを新しい構成に置き換えても、アクティビティは古いロケールのResourcesfromContextThemeWrapperを使用します。

だからここにAPI29まで動作する私のソリューションがあります:

次のMainApplicationクラスからクラスをサブクラス化します。

abstract class LocalApplication : Application() {

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(
            base.toLangIfDiff(
                PreferenceManager
                    .getDefaultSharedPreferences(base)
                    .getString("langPref", "sys")!!
             )
        )
    }
}

また、すべてActivityから:

abstract class LocalActivity : AppCompatActivity() {

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(            
            PreferenceManager
                .getDefaultSharedPreferences(base)
                    .getString("langPref", "sys")!!
        )
    }

    override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
        super.applyOverrideConfiguration(baseContext.resources.configuration)
    }
}

LocaleExt.kt次の拡張機能で追加:

const val SYSTEM_LANG = "sys"
const val ZH_LANG = "zh"
const val SIMPLIFIED_CHINESE_SUFFIX = "rCN"


private fun Context.isAppLangDiff(prefLang: String): Boolean {
    val appConfig: Configuration = this.resources.configuration
    val sysConfig: Configuration = Resources.getSystem().configuration

    val appLang: String = appConfig.localeCompat.language
    val sysLang: String = sysConfig.localeCompat.language

    return if (SYSTEM_LANG == prefLang) {
        appLang != sysLang
    } else {
        appLang != prefLang
                || ZH_LANG == prefLang
    }
}

fun Context.toLangIfDiff(lang: String): Context =
    if (this.isAppLangDiff(lang)) {
        this.toLang(lang)
    } else {
        this
    }

@Suppress("DEPRECATION")
fun Context.toLang(toLang: String): Context {
    val config = Configuration()

    val toLocale = langToLocale(toLang)

    Locale.setDefault(toLocale)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        config.setLocale(toLocale)

        val localeList = LocaleList(toLocale)
        LocaleList.setDefault(localeList)
        config.setLocales(localeList)
    } else {
        config.locale = toLocale
    }

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        config.setLayoutDirection(toLocale)
        this.createConfigurationContext(config)
    } else {
        this.resources.updateConfiguration(config, this.resources.displayMetrics)
        this
    }
}

/**
 * @param toLang - two character representation of language, could be "sys" - which represents system's locale
 */
fun langToLocale(toLang: String): Locale =
    when {
        toLang == SYSTEM_LANG ->
            Resources.getSystem().configuration.localeCompat

        toLang.contains(ZH_LANG) -> when {
            toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) ->
                Locale.SIMPLIFIED_CHINESE
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
                Locale(ZH_LANG, "Hant")
            else ->
                Locale.TRADITIONAL_CHINESE
        }

        else -> Locale(toLang)
    }

@Suppress("DEPRECATION")
private val Configuration.localeCompat: Locale
    get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        this.locales.get(0)
    } else {
        this.locale
    }

res/values/arrays.xmlサポートされている言語を配列で追加します。

<string-array name="lang_values" translatable="false">
    <item>sys</item> <!-- System default -->
    <item>ar</item>
    <item>de</item>
    <item>en</item>
    <item>es</item>
    <item>fa</item>
    ...
    <item>zh</item> <!-- Traditional Chinese -->
    <item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>

私が言及したい:

  • config.setLayoutDirection(toLocale);アラビア語、ペルシア語などのRTLロケールを使用するときに、レイアウトの方向を変更するために使用します。
  • "sys" コード内には、「システムのデフォルト言語を継承する」という意味の値があります。
  • ここで「langPref」は、ユーザーの現在の言語を入力する際の優先キーです。
  • すでに必要なロケールを使用している場合は、コンテキストを再作成する必要はありません。
  • ContextWraperここに投稿されているようにする必要はありませんcreateConfigurationContext。baseContextとして返される新しいコンテキストを設定するだけです。
  • これは非常に重要です!呼び出すcreateConfigurationContextときは、プロパティが設定されている場合にのみ、最初から作成された構成を渡す必要がありますLocale。この構成に設定されている他のプロパティはありません。この構成に他のいくつかのプロパティ(たとえば、方向)を設定すると、そのプロパティが永久にオーバーライドされ、画面を回転させてもコンテキストがこの方向プロパティを変更しなくなるためです。
  • recreateapplicationContextは古いロケールのままであり、予期しない動作を引き起こす可能性があるため、ユーザーが別の言語を選択したときにアクティビティを実行するだけでは不十分です。したがって、設定の変更を聞いて、代わりにアプリケーションタスク全体を再起動してください。

fun Context.recreateTask() {
    this.packageManager
        .getLaunchIntentForPackage(context.packageName)
        ?.let { intent ->
            val restartIntent = Intent.makeRestartActivityTask(intent.component)
            this.startActivity(restartIntent)
            Runtime.getRuntime().exit(0)
         }
}

これは動作しません。また、すべてのアクティビティでコードを複製するのではなく、すべてのアクティビティの基本アクティビティを作成することを検討してください。また、recreateTask(Context context)レイアウトを変更せずに表示しているため、メソッドが正しく機能していません。
ブルーウェア

@bluewareサンプルを更新しました。以前はいくつかのバグがありました。しかし、現在は機能するはずです。これは私の本番アプリのコードです。「言語は、再起動後に変更されます」のような楽しいrecreateTaskは仕事が、あなたは...乾杯を表示することができませんでした
オレクサンドルAlbul

1

これが@ bassel-mourjanのソリューションで、kotlinの良さが少しあります:):

import android.annotation.TargetApi
import android.content.ContextWrapper
import android.os.Build
import java.util.*

@Suppress("DEPRECATION")
fun ContextWrapper.wrap(language: String): ContextWrapper {
    val config = baseContext.resources.configuration
    val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        this.getSystemLocale()
    } else {
        this.getSystemLocaleLegacy()
    }

    if (!language.isEmpty() && sysLocale.language != language) {
        val locale = Locale(language)
        Locale.setDefault(locale)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            this.setSystemLocale(locale)
        } else {
            this.setSystemLocaleLegacy(locale)
        }
    }

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        val context = baseContext.createConfigurationContext(config)
        ContextWrapper(context)
    } else {
        baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
        ContextWrapper(baseContext)
    }

}

@Suppress("DEPRECATION")
fun ContextWrapper.getSystemLocaleLegacy(): Locale {
    val config = baseContext.resources.configuration
    return config.locale
}

@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.getSystemLocale(): Locale {
    val config = baseContext.resources.configuration
    return config.locales[0]
}


@Suppress("DEPRECATION")
fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) {
    val config = baseContext.resources.configuration
    config.locale = locale
}

@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.setSystemLocale(locale: Locale) {
    val config = baseContext.resources.configuration
    config.setLocale(locale)
}

そして、これがあなたがそれをどのように使うかです:

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}

この行val config = baseContext.resources.configurationは非常に間違っています。このため、多くのバグが発生することになります。代わりに、新しい構成を作成する必要があります。私の答えを見てください。
OleksandrAlbul20年


-1

これを試して:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);

1
コンテキストを新しいものに切り替えるために必要なアクティビティを作成するだけです
Ali Karaca 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.