web-dev-qa-db-ja.com

Androidxへの移行後にロケールの変更が機能しない

多言語をサポートする古いプロジェクトがあります。サポートライブラリとターゲットプラットフォームをアップグレードしたいAndroidxに移行する前に、すべて正常に動作しますが、言語を変更すると機能しません!

このコードを使用して、アプリのデフォルトロケールを変更します

_private static Context updateResources(Context context, String language)
{
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

    Configuration configuration = context.getResources().getConfiguration();
    configuration.setLocale(locale);

    return context.createConfigurationContext(configuration);
}
_

そして、次のようにattachBaseContextをオーバーライドして、各アクティビティでこのメソッドを呼び出します。

_@Override
protected void attachBaseContext(Context newBase)
{
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    String language = preferences.getString(SELECTED_LANGUAGE, "fa");
    super.attachBaseContext(updateResources(newBase, language));
}
_

文字列を取得する他の方法を試してみましたが、‍‍‍‍getActivity().getBaseContext().getStringが機能し、getActivity().getStringが機能しないことに気付きました。次のコードでも機能せず、デフォルトのリソースstring.xmlに常に_app_name_ vlaueが表示されます。

_<TextView
    Android:id="@+id/textView"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="@string/app_name"/>
_

https://github.com/Freydoonk/LanguageTest でサンプルコードを共有します

また、getActivity()..getResources().getIdentifierは機能せず、常に0を返します。

37
Fred

最後に、私は自分のアプリに問題を見つけました。プロジェクトをAndroidxに移行すると、プロジェクトの依存関係は次のように変わります。

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.Android.material:material:1.1.0-alpha04'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02'
} 

ご覧のとおり、androidx.appcompat:appcompatのバージョンは1.1.0-alpha03であり、最新の安定したバージョンである1.0.2に変更すると、問題が解決され、変更言語が正しく機能します。

appcompatライブラリの最新の安定バージョンが Maven Repository にあります。他のライブラリも最新の安定バージョンに変更します。

今私のアプリの依存関係セクションは次のようです:

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.Android.material:material:1.0.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
14
Fred

基本的に、バックグラウンドで起こっていることは、attachBaseContextで構成を正しく設定している間、AppCompatDelegateImplが実行されて、構成がロケール

_ final Configuration conf = new Configuration();
 conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);

 try {
     ...
     ((Android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
     handled = true;
 } catch (IllegalStateException e) {
     ...
 }
_

Chris Banesによる未リリースのコミット これは実際に修正されました:新しい設定は、ベースコンテキストの設定のディープコピーです。

_final Configuration conf = new Configuration(baseConfiguration);
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
    ...
    ((Android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
    handled = true;
} catch (IllegalStateException e) {
    ...
}
_

これがリリースされるまで、まったく同じことを手動で行うことができます。バージョン1.1.0を引き続き使用するには、attachBaseContextの下にこれを追加します。

Kotlinソリューション

_override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
    if (overrideConfiguration != null) {
        val uiMode = overrideConfiguration.uiMode
        overrideConfiguration.setTo(baseContext.resources.configuration)
        overrideConfiguration.uiMode = uiMode
    }
    super.applyOverrideConfiguration(overrideConfiguration)
}
_

Javaソリューション

_@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (overrideConfiguration != null) {
        int uiMode = overrideConfiguration.uiMode;
        overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
        overrideConfiguration.uiMode = uiMode;
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}
_

このコードは、内部でConfiguration(baseConfiguration)が行うのとまったく同じですが、に行うため、AppCompatDelegateはすでに正しい値を設定していますuiMode、上書きしたuiModeを修正してから、ダーク/ライトモードの設定が失われないようにする必要があります。

注意してくださいこれは、内部で_configChanges="uiMode"_を指定しない場合にのみ機能しますあなたのマニフェスト。もしそうなら、さらに別のバグがあります:onConfigurationChangedの内部では_newConfig.uiMode_はAppCompatDelegateImplonConfigurationChangedによって設定されません。現在の夜間モードを計算するためにAppCompatDelegateImplが使用するすべてのコードをベースアクティビティコードにコピーし、_super.onConfigurationChanged_呼び出しの前にそれをオーバーライドする場合も、これを修正できます。 Kotlinでは、次のようになります。

_private var activityHandlesUiMode = false
private var activityHandlesUiModeChecked = false

private val isActivityManifestHandlingUiMode: Boolean
    get() {
        if (!activityHandlesUiModeChecked) {
            val pm = packageManager ?: return false
            activityHandlesUiMode = try {
                val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
                info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
            } catch (e: PackageManager.NameNotFoundException) {
                false
            }
        }
        activityHandlesUiModeChecked = true
        return activityHandlesUiMode
    }

override fun onConfigurationChanged(newConfig: Configuration) {
    if (isActivityManifestHandlingUiMode) {
        val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED) 
            delegate.localNightMode
        else
            AppCompatDelegate.getDefaultNightMode()
        val configNightMode = when (nightMode) {
            AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
            AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
            else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
        }
        newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
    }
    super.onConfigurationChanged(newConfig)
}
_

12月16日更新:

_AppCompatDelegateImpl.Java_ の最近のバージョンは、この投稿を最初に作成してから、さらに多くの変更を受けています。上記で述べたChris Banesによる修正は、configChangesに関連するすべてのバグを完全に取り除くには十分ではなかったようです(_configChanges="uiMode"_の上記の回避策を確認でき、コメントで1人のユーザーが指摘しました)ここでは、彼の場合、orientationの値はonConfigurationChangedで変化していませんでした。最新の実装では、代わりにgenerateConfigDeltaと呼ばれる新しいメソッドに依存して正しい構成インスタンスを作成しているため、ナイトモードを処理する他のすべての方法は効果的に非推奨になっています。

個人的に、私は何ヶ月も問題なく、またはユーザーからの不満なしに私のソリューションを使用してきましたが、AppCompatDelegateImplはまだ進行中の作業であることを認識しておく必要があります。したがって、私の判断で私のソリューションを使用してください。また、以前のライブラリバージョンへのダウングレードを示唆する他の回答も確認してください可能性がありますあなたの状況でより適切である。

67
0101100101

Android 21〜25)の設定をオーバーライドする原因となる、ナイトモードに関連する新しいアプリ互換ライブラリに問題があります。これは、このパブリック関数が呼び出されたときに設定を適用することで修正できます:

public void applyOverrideConfiguration(Configuration overrideConfiguration

私にとって、この小さなトリックは、上書きされた構成から私の構成に設定をコピーすることで機能しましたが、あなたは好きなように何でもできます。エラーを最小限に抑えるには、言語ロジックを新しい構成に再適用することをお勧めします

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (Build.VERSION.SDK_INT >= 21&& Build.VERSION.SDK_INT <= 25) {
        //Use you logic to update overrideConfiguration locale
        Locale locale = getLocale()//your own implementation here;
        overrideConfiguration.setLocale(locale);
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}
8
Jack Lebbos

最後に、ロケートの解決策を得ました。私の場合、実際にはロケートファイルを分割するため、問題はbundle apkでした。 bundle apkでは、デフォルトですべての分割が生成されます。ただし、Android build.gradleファイルのブロック内で、どのスプリットを生成するかを宣言できます。

bundle {
            language {
                // Specifies that the app bundle should not support
                // configuration APKs for language resources. These
                // resources are instead packaged with each base and
                // dynamic feature APK.
                enableSplit = false
            }
        }

このコードをAndroidブロックbuild.gradleファイルに追加すると、問題が解決します。

2
Mansukh Ahir

今も動作する新しいバージョンがあります:

implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'

@Fredが述べたようにappcompat:1.1.0-alpha03には問題がありますが、 リリースバージョンのログには記載されていません

1
Choletski

androidx.appcompat:appcompat:1.1.0にも同じバグがありました。 androidx.appcompat:appcompat:1.1.0-rc01に切り替えて、Android 5-6.の言語が変更されました

0
Spinner