Android संदर्भ.getResources.updateConfiguration () पदावनत


85

अभी हाल ही के संदर्भ .getResources ()। updateConfiguration () को Android API 25 में चित्रित किया गया है और इसे संदर्भ का उपयोग करने की सलाह दी जाती है। createConfigurationContext () के बजाय।

क्या किसी को पता है कि android सिस्टम लोकेल को ओवरराइड करने के लिए createConfigurationContext का उपयोग कैसे किया जा सकता है?

इससे पहले कि यह किया जाएगा:

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

कैसे के बारे में applyOverrideConfiguration (untested)?
user1480019

वहाँ भी एक सरल उपाय यहाँ बहुत ही इस एक के समान है, stackoverflow.com/questions/39705739/...
Thanasis Saxanidis

[updateConfiguration एपीआई स्तर 25 में पदावनत किया गया] डेवलपर.
android.com/reference/android/content/res/Resources

जवाबों:


122

सुलेख से प्रेरित होकर , मैंने एक संदर्भ आवरण तैयार किया। मेरे मामले में, मुझे अपने ऐप उपयोगकर्ताओं को ऐप भाषा बदलने के विकल्प के साथ प्रदान करने के लिए सिस्टम भाषा को अधिलेखित करने की आवश्यकता है लेकिन इसे किसी भी तर्क के साथ अनुकूलित किया जा सकता है जिसे आपको लागू करने की आवश्यकता है।

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

अद्यतन 10/19/2018 कभी-कभी उन्मुखीकरण परिवर्तन के बाद, या डिफ़ॉल्ट सिस्टम कॉन्फ़िगरेशन के लिए कॉन्फ़िगरेशन ऑब्जेक्ट रीसेट करें / फिर से शुरू करें और परिणाम में हम एप्लिकेशन को अंग्रेजी "एन" टेक्स्ट प्रदर्शित करते हुए देखेंगे, भले ही हमने फ्रेंच "fr" लोकेल के साथ संदर्भ को लपेटा हो। । इसलिए और एक अच्छे अभ्यास के रूप में, गतिविधियों या टुकड़ों में एक वैश्विक चर में प्रसंग / गतिविधि वस्तु को कभी भी बनाए न रखें।

इसके अलावा, MyBaseFragment या MyBaseActivity में निम्नलिखित बनाएं और उपयोग करें:

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

यह अभ्यास आपको 100% बग नि: शुल्क समाधान प्रदान करेगा।


5
मुझे इस दृष्टिकोण के साथ एक चिंता है ... यह वर्तमान में केवल गतिविधियों के लिए लागू किया जा रहा है न कि पूरे आवेदन पर। ऐप घटकों के लिए क्या होगा जो सेवाओं की तरह गतिविधियों से शुरू नहीं हो सकता है?
rfgamaral 15

6
आप ContextWrapper का विस्तार क्यों करेंगे? आपके पास इसमें कुछ भी नहीं है, बस स्थिर तरीके हैं?
vladimir123

7
मुझे if-else शाखा से createConfigurationContext / updateConfiguration को बाहर निकालना पड़ा और इसे नीचे जोड़ा गया, और पहली गतिविधि में सब कुछ ठीक था, लेकिन जब दूसरा खोला गया, तो भाषा वापस डिवाइस डिफ़ॉल्ट में बदल गई। कारण नहीं मिल सका।
क्रोक

3
मैंने आवश्यक पंक्ति जोड़ दी और इसे इस पोस्ट के रूप में पोस्ट किया: gist.github.com/muhammad-naderi/…
मुहम्मद

2
@kroky सही है। सिस्टम लोकेल को सही ढंग से बदल दिया जाता है, लेकिन कॉन्फ़िगरेशन डिफ़ॉल्ट रूप से वापस आ जाता है। परिणामस्वरूप, स्ट्रिंग संसाधन फ़ाइल डिफ़ॉल्ट रूप से वापस आ जाती है। क्या हर गतिविधि में हर समय कॉन्फ़िगरेशन सेट करने के अलावा कोई और तरीका है
यश लादिया

29

शायद इस तरह:

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

बोनस: एक ब्लॉग लेख जो createConfigurationContext () का उपयोग करता है


मुझे सही दिशा में इंगित करने के लिए धन्यवाद, मुझे लगता है कि आखिरकार एक कॉन्टेक्स्टवॉपर बनाना होगा और इसे उन गतिविधियों को संलग्न करना होगा जैसे कि सुलेख द्वारा किया जाता है। वैसे भी पुरस्कार आपका है, लेकिन मैं इसे अंतिम उत्तर के रूप में नहीं मानूंगा जब तक कि मैं वर्कअराउंड के सही कोडिंग को पोस्ट नहीं करता।
बासेल मूरजन

58
एपीआई 24 + ... बेवकूफ गूगल, वे सिर्फ हमें एक सरल तरीका प्रदान नहीं कर सकते हैं?
Ab

7
@click_whir यह कहना कि "यह सरल है यदि आप केवल इन उपकरणों को लक्षित करते हैं" वास्तव में इसे सरल नहीं बनाता है।
Vlad

1
@Vlad 2012 से पहले बनाए गए उपकरणों का समर्थन करने की आवश्यकता नहीं है, तो एक सरल तरीका है। आवेदन विकास में आपका स्वागत है!
click_whir

1
आपको कहां से मिलाLocaleList
एजडेव

4

सुलेख और मूरजन और खुद से प्रेरित होकर, मैंने इसे बनाया।

सबसे पहले आपको एप्लिकेशन का एक उपवर्ग बनाना होगा:

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 व्यवस्थित नहीं है तो हार्डकोड "एन" डिफ़ॉल्ट लैंग है


यह पर्याप्त नहीं है, आपको हर गतिविधि में संदर्भ को ओवरराइड करने की भी आवश्यकता है। जैसा कि आप स्थिति के साथ समाप्त हो जाएंगे कि आपके बेस कॉन्टेक्स्ट में एक स्थान है, और आपके पास एक और आवेदन होगा। परिणामस्वरूप आपको अपने यूआई में मिश्रित भाषाएं मिलेंगी। कृपया मेरा उत्तर देखें।
ऑलेक्ज़ेंडर अल्बुल

3

यहां 100% काम करने वाला समाधान नहीं है। आप दोनों का उपयोग करने की जरूरत है createConfigurationContextऔर applyOverrideConfiguration। अन्यथा भले ही आप baseContextहर गतिविधि को नए कॉन्फ़िगरेशन के साथ बदल दें , फिर भी गतिविधि पुराने लोकेल Resourcesसे उपयोग की जाएगी ContextThemeWrapper

तो यहाँ मेरा समाधान है जो एपीआई 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पोस्ट की जरूरत नहीं है, बस नए संदर्भ createConfigurationContextको बेस कॉन्टेक्स्ट के रूप में सेट करें
  • यह बहुत महत्वपूर्ण है! जब आप कॉल createConfigurationContextकरते हैं तो आपको स्क्रैच से क्रमित कॉन्फ़िगरेशन पास करना चाहिए और केवल Localeसंपत्ति सेट के साथ । इस कॉन्फ़िगरेशन में कोई अन्य गुण सेट नहीं होना चाहिए। क्योंकि यदि हम इस कॉन्फिगरेशन ( उदाहरण के लिए ओरिएंटेशन ) के लिए कुछ अन्य प्रॉपर्टीज सेट करते हैं , तो हम उस प्रॉपर्टी को हमेशा के लिए ओवरराइड कर देते हैं, और हमारा संदर्भ अब इस ओरिएंटेशन प्रॉपर्टी को नहीं बदलता है भले ही हम स्क्रीन को घुमाएं।
  • यह केवल recreateगतिविधि के लिए पर्याप्त नहीं है जब उपयोगकर्ता एक अलग भाषा का चयन करता है, क्योंकि एप्लिकेशन कॉन्टेक्स्ट पुराने लोकेल के साथ रहेगा और यह अप्रत्याशित व्यवहार कर सकता है। इसलिए वरीयता परिवर्तन को सुनो और इसके बजाय पूरे आवेदन कार्य को फिर से शुरू करें:

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 मैंने नमूने अपडेट किए हैं। पहले कुछ कीड़े थे। लेकिन वर्तमान में यह काम करना चाहिए, यह मेरे उत्पादन ऐप का कोड है। मज़ेदार
रीति-रिवाज़

1

यहाँ @ 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बहुत गलत है। आप इस वजह से बहुत से कीड़े खत्म कर देंगे। आपको इसके बजाय नया कॉन्फ़िगरेशन बनाने की आवश्यकता है। मेरा जवाब देखिए।
ऑलेक्ज़ेंडर अल्बुल

0

यहाँ रेफरेंस के साथ एक सरल उपाय है: यहाँ एंड्रॉइड N भाषा को प्रोग्रामेटिक रूप से बदलता है, रीक्रिएट () विधि पर ध्यान दें


लिंक सहायक है, और एक अच्छा संदर्भ है। मेरा मानना ​​है कि अतिरिक्त क्लिक की आवश्यकता के बजाय यहां वास्तविक उत्तर को शामिल करना बेहतर है।
टूथलेसरेल

आप सही कह रहे हैं कि मैं
स्टैकओवरफ़्लो के

-1

इसे इस्तेमाल करे:

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

1
यह केवल गतिविधि को नए एक के साथ संदर्भ को बदलने की आवश्यकता पैदा करता है
अली कराका
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.