Android context.getResources.updateConfiguration () không được dùng nữa


85

Vừa mới context.getResources (). updateConfiguration () không được dùng nữa trong Android API 25 và bạn nên sử dụng ngữ cảnh. createConfigurationContext () để thay thế.

Có ai biết cách createConfigurationContext có thể được sử dụng để ghi đè ngôn ngữ hệ thống android không?

trước khi điều này được thực hiện bởi:

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

Làm thế nào về applyOverrideConfiguration (chưa được kiểm tra)?
user1480019

cũng có một giải pháp đơn giản ở đây, rất giống với giải pháp này là stackoverflow.com/questions/39705739/…
Thanasis Saxanidis

[updateConfiguration không được dùng nữa trong API cấp 25] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam 17/09/18

Câu trả lời:


122

Lấy cảm hứng từ Thư pháp , tôi đã kết thúc việc tạo một trình bao bọc ngữ cảnh. Trong trường hợp của tôi, tôi cần ghi đè ngôn ngữ hệ thống để cung cấp cho người dùng ứng dụng của mình tùy chọn thay đổi ngôn ngữ ứng dụng nhưng điều này có thể được tùy chỉnh theo bất kỳ logic nào mà bạn cần triển khai.

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

và để chèn trình bao bọc của bạn, trong mọi hoạt động, hãy thêm mã sau:

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

CẬP NHẬT 23/09/2020 Ví dụ: Trong trường hợp ghi đè chủ đề ứng dụng để áp dụng chế độ tối, ContextThemeWrapper sẽ phá vỡ cài đặt ngôn ngữ, do đó hãy thêm mã sau vào Hoạt động của bạn để đặt lại ngôn ngữ mong muốn

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

CẬP NHẬT 19/10/2018 Đôi khi sau khi thay đổi hướng hoặc tạm dừng hoạt động / tiếp tục hoạt động, đối tượng Cấu hình được đặt lại về Cấu hình hệ thống mặc định và kết quả là chúng ta sẽ thấy ứng dụng hiển thị văn bản tiếng Anh "en" mặc dù chúng tôi đã bao bọc ngữ cảnh bằng ngôn ngữ "fr" tiếng Pháp . Do đó và là một thông lệ tốt, không bao giờ giữ lại đối tượng Context / Activity trong một biến toàn cục trong các hoạt động hoặc phân đoạn.

hơn nữa, hãy tạo và sử dụng phần sau trong MyBaseFragment hoặc MyBaseActivity:

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

Thực hành này sẽ cung cấp cho bạn giải pháp không có lỗi 100%.


5
Tôi có một mối quan tâm với cách tiếp cận này ... Cách này hiện chỉ được áp dụng cho các hoạt động chứ không phải toàn bộ ứng dụng. Điều gì sẽ xảy ra đối với các thành phần ứng dụng có thể không bắt đầu từ các hoạt động, chẳng hạn như dịch vụ?
rfgamaral

6
Tại sao bạn lại mở rộng ContextWrapper? Bạn không có bất cứ thứ gì trong đó, chỉ có các phương thức tĩnh?
vladimir123

7
Tôi phải lấy createConfigurationContext / updateConfiguration từ nhánh if-else và thêm bên dưới nó, else trong Activity đầu tiên thì mọi thứ đều ổn, nhưng khi mở thứ hai, ngôn ngữ đã thay đổi trở lại mặc định của thiết bị. Không thể tìm thấy lý do.
kroky

3
Tôi đã thêm dòng cần thiết và đăng nó dưới dạng ý chính sau: gist.github.com/muhammad-naderi/…
Muhammad Naderi

2
@kroky nói đúng. Ngôn ngữ hệ thống được thay đổi chính xác, nhưng cấu hình trở lại mặc định. Do đó, tệp tài nguyên chuỗi trở lại mặc định. Có cách nào khác, trừ trường thiết lập cấu hình mọi trong mọi hoạt động
Yash Ladia

29

Có lẽ như thế này:

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

Phần thưởng: Một bài viết trên blog sử dụng createConfigurationContext ()


Cảm ơn bạn đã chỉ cho tôi đúng hướng, tôi đoán cuối cùng người ta sẽ phải tạo một ContextWrapper và gắn nó vào các hoạt động giống như nó được thực hiện bởi Thư pháp. Dù sao thì giải thưởng là của bạn, nhưng sẽ không coi đó là câu trả lời cuối cùng cho đến khi tôi đăng mã đúng về giải pháp.
Bassel Mourjan

58
API 24 + ... Google ngu ngốc, họ không thể chỉ cung cấp cho chúng ta một cách đơn giản sao?
Ab

7
@click_whir Nói "Thật đơn giản nếu bạn chỉ nhắm mục tiêu các thiết bị này" không thực sự làm cho nó trở nên đơn giản.
Vlad

1
@Vlad Có một phương pháp đơn giản nếu bạn không cần hỗ trợ các thiết bị được sản xuất trước năm 2012. Chào mừng bạn đến với phát triển ứng dụng!
click_whir

1
bạn lấy từ đâuLocaleList
EdgeDev

4

Lấy cảm hứng từ Thư pháp & Mourjan & chính tôi, tôi đã tạo ra cái này.

trước tiên, bạn phải tạo một lớp con của Ứng dụng:

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

thì bạn cần đặt điều này thành thẻ ứng dụng AndroidManifest.xml của mình:

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

và thêm thẻ này vào thẻ hoạt động AndroidManifest.xml của bạn.

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

lưu ý rằng pref_locale là một tài nguyên chuỗi như thế này:

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

và hardcode "en" là ngôn ngữ mặc định nếu pref_locale không được thiết lập


Điều này là chưa đủ, bạn cũng cần ghi đè ngữ cảnh trong mọi hoạt động. Vì bạn sẽ gặp phải trường hợp baseContext của bạn có một ngôn ngữ, và ứng dụng của bạn sẽ có một ngôn ngữ khác. Kết quả là bạn sẽ có nhiều ngôn ngữ hỗn hợp trong ui của mình. Hãy xem câu trả lời của tôi.
Oleksandr Albul

3

Đây không phải là giải pháp làm việc 100%. Bạn cần sử dụng cả hai createConfigurationContextapplyOverrideConfiguration. Mặt khác, ngay cả khi bạn thay thế baseContexttrong mọi hoạt động bằng cấu hình mới, hoạt động sẽ vẫn sử dụng Resourcestừ ContextThemeWrappervới ngôn ngữ cũ.

Vì vậy, đây là giải pháp của tôi hoạt động với API 29:

Phân loại lớp của bạn MainApplicationtừ:

abstract class LocalApplication : Application() {

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

Cũng Activitytừ:

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

Thêm LocaleExt.ktvới các chức năng mở rộng tiếp theo:

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
    }

Thêm vào các res/values/arrays.xmlngôn ngữ được hỗ trợ của bạn trong mảng:

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

Tôi muốn đề cập đến:

  • Sử dụng config.setLayoutDirection(toLocale);để thay đổi hướng bố cục khi bạn sử dụng các ngôn ngữ RTL như tiếng Ả Rập, tiếng Ba Tư, v.v.
  • "sys" trong mã là một giá trị có nghĩa là "kế thừa ngôn ngữ mặc định của hệ thống".
  • Ở đây "langPref" là một khóa ưu tiên nơi bạn đặt ngôn ngữ hiện tại của người dùng.
  • Không cần tạo lại ngữ cảnh nếu nó đã sử dụng ngôn ngữ cần thiết.
  • Không cần ContextWraper như đã đăng ở đây, chỉ cần đặt ngữ cảnh mới được trả về từ createConfigurationContextbaseContext
  • Cái này rất quan trọng! Khi bạn gọi, createConfigurationContextbạn nên chuyển cấu hình được tạo từ đầu và chỉ với Localebộ thuộc tính. Không nên đặt bất kỳ thuộc tính nào khác cho cấu hình này. Bởi vì nếu chúng tôi đặt một số thuộc tính khác cho cấu hình này ( ví dụ: định hướng ), chúng tôi sẽ ghi đè thuộc tính đó mãi mãi và ngữ cảnh của chúng tôi không còn thay đổi điều này tính hướng này ngay cả khi chúng tôi xoay màn hình.
  • Chỉ recreatehoạt động khi người dùng chọn một ngôn ngữ khác là không đủ , bởi vì applicationContext sẽ vẫn tồn tại với ngôn ngữ cũ và nó có thể cung cấp hành vi không mong muốn. Vì vậy, hãy lắng nghe thay đổi tùy chọn và khởi động lại toàn bộ tác vụ ứng dụng thay thế:

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

Điều này không hoạt động. Cũng nên xem xét tạo hoạt động cơ sở cho tất cả các hoạt động thay vì sao chép mã trong mọi hoạt động. Ngoài ra, recreateTask(Context context)phương pháp này không hoạt động bình thường vì tôi vẫn thấy bố cục mà không có bất kỳ thay đổi nào.
blueware

@blueware Tôi đã cập nhật các mẫu. Có một số lỗi trước đây. Nhưng hiện tại nó sẽ hoạt động, đây là mã từ ứng dụng sản xuất của tôi.
RecreateTask

1

Đây là giải pháp của @ bassel-tangjan với một chút tính tốt của 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)
}

Và đây là cách bạn sử dụng nó:

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

Dòng val config = baseContext.resources.configurationnày rất rất sai. Bạn sẽ gặp rất nhiều lỗi vì điều này. Bạn cần tạo cấu hình mới để thay thế. Hãy xem câu trả lời của tôi.
Oleksandr Albul

0

có một giải pháp đơn giản với contextWrapper tại đây: Android N thay đổi ngôn ngữ theo chương trình Hãy chú ý đến phương thức recreate ()


Liên kết rất hữu ích và là một tài liệu tham khảo tốt. Tôi tin rằng tốt hơn là nên đưa câu trả lời thực tế vào đây thay vì yêu cầu thêm một cú nhấp chuột.
ToothlessRebel

bạn là đúng tôi chỉ mới để stackoverflow và tôi nghĩ rằng nó sẽ là sai lầm để mất tín dụng cho câu trả lời vì vậy tôi đăng các liên kết của tác giả ban đầu
Thanasis Saxanidis

-1

Thử đi:

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

1
Nó chỉ tạo ra các hoạt động u cần phải bối cảnh chuyển đổi với cái mới
Ali Karaca
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.