บริบท Android.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

นอกจากนี้ยังมีวิธีง่ายๆที่นี่คล้ายกับที่stackoverflow.com/questions/39705739/…
Thanasis Saxanidis

[updateConfiguration เลิกใช้ใน API ระดับ 25] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

คำตอบ:


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 หลังจากที่ Android ใช้ไลบรารี Material ของ ContextThemeWrapper เพื่อรองรับโหมดมืดการตั้งค่าภาษาจะหยุดทำงานและการตั้งค่าภาษาจะหายไป หลังจากหลายเดือนของการเกาหัวปัญหาได้รับการแก้ไขโดยการเพิ่มรหัสต่อไปนี้ในเมธอด Activity และ Fragment onCreate

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

อัปเดต 10/19/2018 บางครั้งหลังจากเปลี่ยนการวางแนวหรือกิจกรรมหยุดชั่วคราว / ดำเนินการต่อวัตถุการกำหนดค่าจะรีเซ็ตเป็นค่าเริ่มต้นของการกำหนดค่าระบบและด้วยเหตุนี้เราจะเห็นแอปแสดงข้อความ "en" ภาษาอังกฤษแม้ว่าเราจะรวมบริบทด้วยภาษาฝรั่งเศส "fr" . ดังนั้นและเป็นแนวทางปฏิบัติที่ดีอย่าเก็บวัตถุบริบท / กิจกรรมไว้ในตัวแปรส่วนกลางในกิจกรรมหรือส่วนย่อย

นอกจากนี้สร้างและใช้สิ่งต่อไปนี้ใน MyBaseFragment หรือ MyBaseActivity:

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

แนวทางปฏิบัตินี้จะช่วยให้คุณมีโซลูชันที่ปราศจากข้อผิดพลาด 100%


5
ฉันมีข้อกังวลประการหนึ่งเกี่ยวกับแนวทางนี้ ... ขณะนี้ใช้กับกิจกรรมเท่านั้นไม่ใช่แอปพลิเคชันทั้งหมด จะเกิดอะไรขึ้นกับส่วนประกอบของแอปที่อาจไม่ได้เริ่มจากกิจกรรมเช่นบริการ
rfgamaral

6
ทำไมคุณถึงขยาย ContextWrapper คุณไม่มีอะไรเลยมีแค่วิธีการคงที่?
vladimir123

7
ฉันต้องนำ createConfigurationContext / updateConfiguration ออกจากสาขา if-else และเพิ่มด้านล่างมิฉะนั้นในกิจกรรมแรกทุกอย่างก็โอเค แต่เมื่อเปิดครั้งที่สองภาษาก็เปลี่ยนกลับเป็นค่าเริ่มต้นของอุปกรณ์ ไม่พบเหตุผล
kroky

3
ฉันเพิ่มบรรทัดที่ต้องการและโพสต์เป็นส่วนสำคัญนี้: gist.github.com/muhammad-naderi/…
Muhammad Naderi

2
@kroky อยู่พอดี. ระบบมีการเปลี่ยนแปลงอย่างถูกต้อง แต่การกำหนดค่ากลับเป็นค่าเริ่มต้น เป็นผลให้ไฟล์ทรัพยากรสตริงกลับสู่ค่าเริ่มต้น มีวิธีอื่นอีกไหมนอกเหนือจากการตั้งค่าทุกครั้งในทุกกิจกรรม
Yash Ladia

32

อาจเป็นเช่นนี้:

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

โบนัส: บทความบล็อกที่ใช้ createConfigurationContext ()


ขอบคุณที่ชี้ให้ฉันไปในทิศทางที่ถูกต้องฉันเดาว่าในที่สุดก็ต้องสร้าง ContextWrapper และแนบเข้ากับกิจกรรมเหมือนที่ Calligraphy ทำ ยังไงก็ได้รางวัลเป็นของคุณ แต่จะไม่ถือเป็นคำตอบสุดท้ายจนกว่าฉันจะโพสต์การเข้ารหัสที่ถูกต้องของวิธีแก้ปัญหา
Bassel Mourjan

59
API 24 + ... Google โง่ ๆ ให้วิธีง่ายๆกับเราไม่ได้เหรอ?
Ali Bdeir

7
@click_whir การพูดว่า "มันง่ายมากถ้าคุณกำหนดเป้าหมายอุปกรณ์เหล่านี้เท่านั้น" ไม่ได้ทำให้มันง่าย
Vlad

1
@Vlad มีวิธีง่ายๆหากคุณไม่ต้องการรองรับอุปกรณ์ที่ผลิตก่อนปี 2012 ยินดีต้อนรับสู่การพัฒนาแอปพลิเคชัน!
click_whir

1
คุณได้รับจากที่ไหนLocaleList
EdgeDev

4

แรงบันดาลใจจาก Calligraphy & Mourjan และตัวฉันเองฉันสร้างสิ่งนี้ขึ้นมา

ก่อนอื่นคุณต้องสร้างคลาสย่อยของแอปพลิเคชัน:

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>

และฮาร์ดโค้ด "en" เป็นภาษาเริ่มต้นหากไม่ได้ตั้งค่า pref_locale


นี่ยังไม่เพียงพอคุณต้องลบล้างบริบทในทุกกิจกรรมด้วย ในขณะที่คุณจะพบกับสถานการณ์ที่ baseContext ของคุณมีโลแคลเดียวและแอปพลิเคชันของคุณจะมีอีก ดังนั้นคุณจะมีภาษาผสมใน UI ของคุณ โปรดดูคำตอบของฉัน
Oleksandr Albul

3

นี่คือโซลูชันที่ใช้งานได้ 100% คุณต้องใช้ทั้งสองcreateConfigurationContextและapplyOverrideConfiguration. มิฉะนั้นแม้ว่าคุณจะแทนที่baseContextทุกกิจกรรมด้วยการกำหนดค่าใหม่ แต่กิจกรรมก็ยังคงใช้Resourcesจาก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โพสต์ไว้ที่นี่เพียงตั้งค่าบริบทใหม่ที่ส่งคืนจากcreateConfigurationContextเป็น baseContext
  • นี่สำคัญมาก! เมื่อคุณโทร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 ฉันได้อัปเดตตัวอย่างแล้ว มีข้อบกพร่องบางอย่างก่อนหน้านี้ แต่ตอนนี้ควรใช้งานได้แล้วนี่คือรหัสจากแอปที่ใช้งานจริงของฉัน การสร้างความสนุกที่สร้างขึ้นใหม่ไม่สามารถทำงานได้คุณสามารถแสดงขนมปังเช่น "ภาษาจะถูกเปลี่ยนหลังจากรีสตาร์ท" ..
Oleksandr Albul

1

นี่คือวิธีแก้ปัญหาของ @ bassel -morejan ด้วยความดีของ 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นี้ผิดมาก คุณจะจบลงด้วยข้อบกพร่องมากมายเพราะเหตุนี้ คุณต้องสร้างการกำหนดค่าใหม่แทน ดูคำตอบของฉัน
Oleksandr Albul

0

มีวิธีแก้ปัญหาง่ายๆด้วย contextWrapper ที่นี่: Android N เปลี่ยนภาษาโดยทางโปรแกรม ให้ความสนใจกับวิธีการสร้าง () ใหม่


ลิงก์มีประโยชน์และเป็นข้อมูลอ้างอิงที่ดี ฉันเชื่อว่าเป็นการดีกว่าที่จะรวมคำตอบจริงไว้ที่นี่แทนที่จะต้องคลิกเพิ่มเติม
ToothlessRebel

คุณพูดถูกฉันเพิ่งใหม่สำหรับ stackoverflow และฉันคิดว่ามันไม่ถูกต้องที่จะใช้เครดิตสำหรับคำตอบดังนั้นฉันจึงโพสต์ลิงค์ของผู้เขียนต้นฉบับ
Thanasis Saxanidis

-1

ลองสิ่งนี้:

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

1
มันเพิ่งสร้างกิจกรรมที่คุณต้องเปลี่ยนบริบทกับสิ่งใหม่
Ali Karaca
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.