Android context.getResources.updateConfiguration () kullanımdan kaldırıldı


92

Kısa süre önce context.getResources (). updateConfiguration () , Android API 25'te kullanımdan kaldırılmıştır ve bağlam kullanılması önerilir. bunun yerine createConfigurationContext () .

CreateConfigurationContext'in android sistem yerel ayarını geçersiz kılmak için nasıl kullanılabileceğini bilen var mı ?

bu yapılmadan önce:

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

ApplyOverrideConfiguration (test edilmemiş) nasıl olur ?
user1480019

burada da basit bir çözüm var, buna çok benzer stackoverflow.com/questions/39705739/…
Thanasis Saxanidis

[updateConfiguration, API seviyesi 25'te kullanımdan kaldırıldı] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

Yanıtlar:


126

Kaligrafiden ilham alarak bir bağlam sarmalayıcı oluşturdum. Benim durumumda, uygulama kullanıcılarıma uygulama dilini değiştirme seçeneği sağlamak için sistem dilinin üzerine yazmam gerekiyor, ancak bu, uygulamanız gereken herhangi bir mantıkla özelleştirilebilir.

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

ve sarmalayıcınızı enjekte etmek için, her aktivitede aşağıdaki kodu ekleyin:

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

GÜNCELLEME 22/12/2020 Karanlık modu desteklemek için ContextThemeWrapper'ın android Materyal kitaplığı uygulamasından sonra, dil ayarı bozulur ve dil ayarı kaybolur. Aylarca süren kafa tırmalamasından sonra, aşağıdaki kod Activity ve Fragment onCreate yöntemine eklenerek sorun çözüldü.

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

GÜNCELLEME 10/19/2018 Bazen oryantasyon değişikliğinden veya etkinlik duraklatıldıktan / sürdürüldükten sonra Yapılandırma nesnesi varsayılan sistem Yapılandırmasına sıfırlanır ve sonuç olarak, içeriği Fransızca "fr" yerel ayarıyla sarmalamış olsak bile uygulamanın İngilizce "en" metnini görüntülediğini göreceğiz . Bu nedenle ve iyi bir uygulama olarak, Context / Activity nesnesini asla etkinliklerdeki veya parçalardaki genel bir değişkende tutmayın.

ayrıca, bir MyBaseFragment veya MyBaseActivity'de aşağıdakileri oluşturun ve kullanın:

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

Bu uygulama size% 100 hatasız çözüm sağlayacaktır.


5
Bu yaklaşımla ilgili bir endişem var ... Bu şu anda tüm uygulamaya değil, yalnızca faaliyetlere uygulanmaktadır. Hizmetler gibi etkinliklerden başlamayan uygulama bileşenleri için ne olacak?
rfgamaral

6
ContextWrapper'ı neden genişletiyorsunuz? İçinde hiçbir şey yok, sadece statik yöntemler mi?
vladimir123

7
İf-else dalından createConfigurationContext / updateConfiguration'ı çıkarıp altına eklemek zorunda kaldım, aksi takdirde ilk Aktivitede her şey yolundaydı, ancak ikinci açıldığında, dil cihaz varsayılanına geri döndü. Sebebini bulamadım.
kroky

3
Gerekli satırı ekledim ve şu özet
Muhammad Naderi

2
@kroky haklı. Sistem yerel ayarı doğru bir şekilde değiştirildi, ancak yapılandırma varsayılana geri dönüyor. Sonuç olarak, dizeler kaynak dosyası varsayılana geri döner. Her aktivitede her seferinde yapılandırmayı ayarlamanın dışında başka bir yol var mı
Yash Ladia

32

Muhtemelen şöyle:

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

Bonus: createConfigurationContext () kullanan bir blog makalesi


Beni doğru yöne yönlendirdiğiniz için teşekkür ederim, sanırım sonunda bir ContextWrapper oluşturmalı ve onu Calligraphy tarafından yapıldığı gibi etkinliklere eklemeli. Her neyse, ödül sizindir, ancak geçici çözümün doğru kodlamasını gönderene kadar bunu nihai bir cevap olarak görmeyeceğim.
Bassel Mourjan

59
API 24 + ... Aptal google, bize basit bir yol sağlayamazlar mı?
Ali Bdeir

7
@click_whir "Yalnızca bu cihazları hedeflerseniz çok basit" demek, işi gerçekten kolaylaştırmaz.
Vlad

1
@Vlad 2012'den önce üretilmiş cihazları desteklemenize gerek yoksa basit bir yöntem var. Uygulama geliştirmeye hoş geldiniz!
click_whir

1
nereden aldınLocaleList
EdgeDev

4

Kaligrafi ve Mourjan'dan ve kendimden ilham alarak bunu yarattım.

önce bir Uygulama alt sınıfı oluşturmalısınız:

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

ardından bunu AndroidManifest.xml uygulama etiketinize ayarlamanız gerekir:

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

ve bunu AndroidManifest.xml etkinlik etiketinize ekleyin.

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

pref_locale'in bunun gibi bir dize kaynağı olduğuna dikkat edin:

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

ve pref_locale ayarlanmamışsa "en" sabit kodu varsayılan dildir


Bu yeterli değildir, ayrıca her aktivitede bağlamı geçersiz kılmanız gerekir. Sonunda, baseContext'inizin bir yerel ayarı olduğu ve uygulamanızın başka bir yerel ayarı olduğu durumuyla karşılaşacaksınız. Sonuç olarak, kullanıcı arayüzünüzde karışık dillere sahip olacaksınız. Lütfen cevabıma bakın.
Oleksandr Albul

3

İşte% 100 çalışan bir çözüm yok. Her iki kullanmak gerekir createConfigurationContextve applyOverrideConfiguration. Aksi takdirde baseContext, her etkinlikte yeni yapılandırmayı değiştirseniz bile , etkinlik eski yerel ayardan Resourcesitibaren kullanılmaya devam edecektir ContextThemeWrapper.

İşte API 29'a kadar çalışan benim çözümüm:

MainApplicationSınıfınızı şunlardan alt sınıflandırın :

abstract class LocalApplication : Application() {

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

Ayrıca her biri 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.ktSonraki uzantı işlevleriyle ekleyin :

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.xmlDesteklediğiniz dillere dizi olarak ekleyin :

<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>

Bahsetmek istiyorum:

  • config.setLayoutDirection(toLocale);Arapça, Farsça, vb. Gibi RTL yerel ayarlarını kullandığınızda düzen yönünü değiştirmek için kullanın .
  • "sys" kodda "sistem varsayılan dilini devral" anlamına gelen bir değerdir.
  • Burada "langPref", kullanıcının geçerli dilini koyduğunuz yerde bir tercih anahtarıdır.
  • Zaten gerekli yerel ayarı kullanıyorsa, bağlamı yeniden oluşturmaya gerek yoktur.
  • ContextWraperBuraya gönderilmiş createConfigurationContextolmasına gerek yoktur , sadece baseContext olarak döndürülen yeni içeriği ayarlayın
  • Bu çok önemli! Aradığınızda createConfigurationContextsize geçmelidir yapılandırma sıfırdan crated ve yalnızca Localeözellik kümesi. Bu konfigürasyona ayarlanmış başka bir özellik olmamalıdır. Çünkü bu yapılandırma için başka özellikler ayarlarsak ( örneğin yönlendirme ), bu özelliği sonsuza kadar geçersiz kılarız ve bağlamımız ekranı döndürsek bile artık bu yönlendirme özelliğini değiştirmez .
  • Yalnızca recreatekullanıcı farklı bir dil seçtiğinde etkinlik yapmak yeterli değildir , çünkü applicationContext eski yerel ayarda kalacaktır ve beklenmeyen davranışlar sağlayabilir. Bu nedenle, tercih değişikliğini dinleyin ve bunun yerine tüm uygulama görevini yeniden başlatın:

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

Bu çalışmıyor. Ayrıca, her etkinlikte kodu çoğaltmak yerine tüm etkinlikler için temel bir etkinlik oluşturmayı düşünün. Ayrıca, recreateTask(Context context)herhangi bir değişiklik olmadan düzeni hala gördüğüm için yöntem düzgün çalışmıyor.
blueware

@blueware Örnekleri güncelledim. Daha önce bazı hatalar vardı. Ancak şu anda çalışmalı, bu benim üretim uygulamamdaki kod. Eğlence
rekreateTask

1

İşte @ bassel-mourjan'ın biraz kotlin iyiliği içeren çözümü :):

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

Ve işte onu nasıl kullanıyorsun:

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

Bu çizgi val config = baseContext.resources.configurationçok çok yanlış. Bu nedenle çok fazla hatayla karşılaşacaksınız. Bunun yerine yeni bir yapılandırma oluşturmanız gerekir. Cevabımı gör.
Oleksandr Albul


-1

Bunu dene:

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

1
Yalnızca yenisiyle bağlamı değiştirmek için ihtiyacınız olan etkinliği oluşturur
Ali Karaca
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.