Android context.getResources.updateConfiguration () 지원 중단됨


85

최근에 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 2011

간단한 해결책은 여기이 매우 유사있다 stackoverflow.com/questions/39705739/...
Thanasis Saxanidis

[updateConfiguration는 API 레벨 25에 사용되지 않습니다] developer.android.com/reference/android/content/res/Resources
메릴랜드 Sifatul 이슬람

답변:


122

Calligraphy 에서 영감을 받아 컨텍스트 래퍼를 만들었습니다. 제 경우에는 앱 사용자에게 앱 언어 변경 옵션을 제공하기 위해 시스템 언어를 덮어 써야하지만 구현해야하는 모든 로직으로 사용자 지정할 수 있습니다.

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

UPDATE 23/09/2020 예를 들어 다크 모드를 적용하기 위해 앱 테마를 재정의하는 경우 ContextThemeWrapper는 언어 설정을 중단하므로 원하는 로케일을 재설정하기 위해 활동에 다음 코드를 추가합니다.

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
      Locale locale = new Locale("fr");
      overrideConfiguration.setLocale(locale);
      super.applyOverrideConfiguration(overrideConfiguration);
}

업데이트 2018 년 10 월 19 일 때때로 방향 변경 또는 활동 일시 중지 / 재개 후 Configuration 개체가 기본 시스템 구성으로 재설정되고 결과적으로 프랑스어 "fr"로케일로 컨텍스트를 래핑하더라도 앱에 영어 "en"텍스트가 표시됩니다. . 따라서 좋은 방법으로 활동 또는 조각의 전역 변수에 Context / Activity 개체를 유지하지 마십시오.

또한 MyBaseFragment 또는 MyBaseActivity에서 다음을 만들고 사용합니다.

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

이 연습은 100 % 버그없는 솔루션을 제공합니다.


5
이 접근 방식에 대해 한 가지 우려 사항이 있습니다 ... 이것은 현재 전체 응용 프로그램이 아닌 활동에만 적용되고 있습니다. 서비스와 같은 활동에서 시작되지 않을 수있는 앱 구성 요소는 어떻게 되나요?
rfgamaral

6
ContextWrapper를 확장하는 이유는 무엇입니까? 그 안에 아무것도없고 정적 메서드 만 있습니까?
vladimir123

7
if-else 브랜치에서 createConfigurationContext / updateConfiguration을 꺼내서 그 아래에 추가해야했습니다. 그렇지 않으면 첫 번째 활동에서 모든 것이 정상 이었지만 두 번째로 열었을 때 언어가 장치 기본값으로 다시 변경되었습니다. 이유를 찾을 수 없습니다.
kroky

3
내가 필요한 라인을 추가하고이 요점으로 게시 : gist.github.com/muhammad-naderi/...
무하마드 데리

2
@kroky가 맞습니다. 시스템 로케일이 올바르게 변경되었지만 구성이 기본값으로 돌아갑니다. 결과적으로 문자열 리소스 파일이 기본값으로 돌아갑니다. 다른 방법으로는, 모든 활동에 구성 매번 설정하는 것보다 다른 거기
야쉬 Ladia

29

아마 다음과 같습니다.

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

보너스 : createConfigurationContext ()를 사용하는 블로그 기사


저를 올바른 방향으로 안내 해주셔서 감사합니다. 결국 ContextWrapper를 만들어서 Calligraphy가하는 것처럼 활동에 첨부해야 할 것 같습니다. 어쨌든상은 귀하의 것이지만 해결 방법의 올바른 코딩을 게시 할 때까지 최종 답변으로 간주하지 않을 것입니다.
Bassel Mourjan

58
API 24 + ... 멍청한 Google, 단순한 방법을 제공 할 수 없나요?
Ab의

7
@click_whir "이러한 장치 만 대상으로하면 간단합니다."라고 말하는 것은 정말 간단하지 않습니다.
Vlad

1
@Vlad 2012 년 이전에 만들어진 장치를 지원할 필요가없는 경우 간단한 방법이 있습니다. 응용 프로그램 개발에 오신 것을 환영합니다!
click_whir

1
어디서 얻었습니까LocaleList
EdgeDev

4

Calligraphy & Mourjan & 나 자신에게 영감을 받아 이것을 만들었습니다.

먼저 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에 하나의 로케일이 있고 애플리케이션에 다른 로케일이있는 상황이 발생합니다. 결과적으로 UI에 혼합 언어가 생깁니다. 내 대답을 참조하십시오.
Oleksandr Albul

3

100 % 작동하는 솔루션은 없습니다. 당신은 모두를 사용해야 createConfigurationContext하고 applyOverrideConfiguration. 그렇지 않으면 baseContext모든 활동을 새 구성으로 바꾸더라도 활동은 이전 로케일로 Resourcesfrom ContextThemeWrapper을 계속 사용 합니다.

그래서 여기에 API 29까지 작동하는 내 솔루션이 있습니다.

클래스를 다음 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여기에 게시 된대로 필요하지 않습니다. createConfigurationContextbaseContext로 반환 된 새 컨텍스트를 설정 하기 만하면됩니다.
  • 이건 매우 중요합니다! 호출 createConfigurationContext할 때 처음부터 생성 된 구성 을 Locale속성 세트 로만 전달해야합니다 . 이 구성에 설정된 다른 속성이 없어야합니다. 이 구성에 대해 다른 속성 ( 예 : 방향) 을 설정하면 해당 속성을 영원히 재정의 하고 화면을 회전하더라도 컨텍스트가 더 이상이 방향 속성을 변경하지 않기 때문 입니다.
  • recreate사용자가 다른 언어를 선택할 때만 활동하는 것만으로는 충분하지 않습니다. applicationContext는 이전 로케일로 유지되고 예상치 못한 동작을 제공 할 수 있기 때문입니다. 따라서 환경 설정 변경을 듣고 대신 전체 응용 프로그램 작업을 다시 시작하십시오.

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

@blueware 샘플을 업데이트했습니다. 이전에 몇 가지 버그가있었습니다. 그러나 현재는 작동합니다. 이것은 내 프로덕션 앱의 코드입니다. 재미있는 recreateTask가 작동하지 않았습니다. "다시 시작하면 언어가 변경됩니다"와 같은 토스트를 표시 할 수 있습니다.
Oleksandr Albul

1

다음은 약간의 kotlin 장점이있는 @ bassel-mourjan의 솔루션입니다. :)

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은 매우 잘못되었습니다. 이로 인해 많은 버그가 발생합니다. 대신 새 구성을 만들어야합니다. 내 대답을 참조하십시오.
Oleksandr Albul

0

contextWrapper를 사용한 간단한 솔루션이 있습니다. Android N 프로그래밍 방식으로 언어 변경 recreate () 메서드에주의


링크는 도움이되며 좋은 참고 자료입니다. 추가 클릭을 요구하는 것보다 여기에 실제 답변을 포함하는 것이 더 낫다고 생각합니다.
ToothlessRebel

당신이 맞아요 저는 그냥 stackoverflow를 처음 사용하고 답변에 대한 크레딧을 취하는 것이 잘못이라고 생각했기 때문에 원래 저자의 링크를 게시합니다
Thanasis Saxanidis

-1

이 시도:

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

1
그냥 u는 새와 스위치 컨텍스트에 필요한 활동 생성
알리 카라 카
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.