我发现非常奇怪的错误仅在Android N设备上重现。
在浏览我的应用程序时,有可能改变语言。这是改变它的代码。
public void update(Locale locale) {
Locale.setDefault(locale);
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
LocaleList localeList = new LocaleList(locale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
configuration.setLocale(locale);
} else if (BuildUtils.isAtLeast17Api()){
configuration.setLocale(locale);
} else {
configuration.locale = locale;
}
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
此代码在我的巡演活动中非常有效(使用recreate()
调用),但在所有下一个活动中,所有String资源都是错误的。屏幕旋转修复它。我该怎么办这个问题?我应该以不同方式更改Android N的区域设置,还是只是系统错误?
P.S。这是我发现的。首次启动MainActivity(在我的巡演之后)Locale.getDefault()
是正确的,但资源是错误的。但在其他活动中,它给了我错误的Locale和来自此语言环境的错误资源。旋转屏幕(或者可能是其他一些配置更改)Locale.getDefault()
是正确的。
答案 0 :(得分:86)
确定。最后我设法找到了解决方案。
首先,您应该知道在25 API Resources.updateConfiguration(...)
中已弃用。所以你可以这样做:
1)您需要创建自己的ContextWrapper,它将覆盖baseContext中的所有配置参数。例如,这是我的ContextWrapper,可以正确地更改Locale。注意context.createConfigurationContext(configuration)
方法。
public class ContextWrapper extends android.content.ContextWrapper {
public ContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (BuildUtils.isAtLeast17Api()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}}
2)以下是您在BaseActivity中应该做的事情:
@Override
protected void attachBaseContext(Context newBase) {
Locale newLocale;
// .. create or get your new Locale object here.
Context context = ContextWrapper.wrap(newBase, newLocale);
super.attachBaseContext(context);
}
注意:强>
如果要更改区域设置,请记住重新创建活动 你的应用程序在某处。您可以覆盖所需的任何配置 这个解决方案。
答案 1 :(得分:16)
受到各种代码的启发(即:我们的Stackoverflow团队(大声喊叫)),我制作了一个更简单的版本。 <{1}}扩展名是不必要的。
首先,我们假设您有两个语言的按钮,EN和KH。在按钮的onClick中将语言代码保存到ContextWrapper
,然后调用活动SharedPreferences
方法。
示例:
recreate()
然后创建一个返回@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.btn_lang_en:
//save "en" to SharedPref here
break;
case R.id.btn_lang_kh:
//save "kh" to SharedPref here
break;
default:
break;
}
getActivity().recreate();
}
的静态方法,也许是在Utils类中(因为我做了什么,lul)。
ContextWrapper
最后,在 ALL ACTIVITY&#39; S public static ContextWrapper changeLang(Context context, String lang_code){
Locale sysLocale;
Resources rs = context.getResources();
Configuration config = rs.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sysLocale = config.getLocales().get(0);
} else {
sysLocale = config.locale;
}
if (!lang_code.equals("") && !sysLocale.getLanguage().equals(lang_code)) {
Locale locale = new Locale(lang_code);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = locale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
}
return new ContextWrapper(context);
}
方法中加载SharedPreferences
的语言代码。
attachBaseContext(Context newBase)
奖励:为了节省键盘上的手掌汗,我创建了一个@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
类,扩展了LangSupportBaseActivity
并使用那里的最后一块代码。我所有其他活动都延伸Activity
。
示例:
LangSupportBaseActivity
答案 2 :(得分:3)
自Android 7.0以来,我的应用程序的某些部分不再更改其语言。即使使用上面提出的新方法。应用程序和活动上下文的更新对我有所帮助。这是Activity子类覆盖的Kotlin示例:
private fun setApplicationLanguage(newLanguage: String) {
val activityRes = resources
val activityConf = activityRes.configuration
val newLocale = Locale(newLanguage)
activityConf.setLocale(newLocale)
activityRes.updateConfiguration(activityConf, activityRes.displayMetrics)
val applicationRes = applicationContext.resources
val applicationConf = applicationRes.configuration
applicationConf.setLocale(newLocale)
applicationRes.updateConfiguration(applicationConf,
applicationRes.displayMetrics)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
setApplicationLanguage("fa");
}
注意:不建议使用updateConfiguration,但是无论如何,对于每个Activity,createConfigurationContext都保留了一些字符串。
答案 3 :(得分:3)
在Android应用中以编程方式更改语言环境是很痛苦的。我花了很多时间找到目前可以在生产环境中工作的解决方案。
您需要覆盖每个Activity
中的上下文,也需要覆盖Application
类中的上下文,否则最终会在ui中使用混合语言。
这是适用于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"
是一个值,表示“继承系统默认语言”。ContextWraper
,只需将createConfigurationContext
返回的新上下文设置为baseContext createConfigurationContext
时,您应该通过配置从头开始创建,并且仅设置了Locale
属性。不应为此配置设置任何其他属性。因为如果我们为此配置设置其他一些属性(例如 orientation ),我们将永远覆盖该属性,即使我们旋转了该属性,我们的上下文也不再更改此 orientation 属性。屏幕。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)
}
}
答案 4 :(得分:2)
这是我的代码,可以正常工作! 如果有问题,请告诉我
protected void attachBaseContext(Context newBase) {
String lang = "en"; // your language or load from SharedPref
Locale locale = new Locale(lang);
Configuration config = new Configuration(newBase.getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
newBase = newBase.createConfigurationContext(config);
} else {
newBase.getResources().updateConfiguration(config, newBase.getResources().getDisplayMetrics());
}
super.attachBaseContext(newBase);
}
答案 5 :(得分:1)
这对我有用,我正在使用 androidx.appcompat:appcompat:1.2.0
override fun attachBaseContext(newBase: Context?) {
val sp = PreferenceManager.getDefaultSharedPreferences(newBase)
val locale = when(sp.getString("app_language", "")) {
"en" -> { Locale("en") }
"hu" -> { Locale("hu") }
else -> {
if (Build.VERSION.SDK_INT >= 24) {
Resources.getSystem().configuration.locales.get(0);
}
else {
Resources.getSystem().configuration.locale
}
}
}
if(newBase != null) {
Locale.setDefault(locale)
newBase.resources.configuration.setLocale(locale)
applyOverrideConfiguration(newBase.resources.configuration)
}
super.attachBaseContext(newBase)
}
答案 6 :(得分:0)
以上答案使我走上了正确的轨道,但留下了一些问题
要解决第一个问题,我在应用启动时存储了默认语言环境。
注意:如果您的默认语言设置为“ en”,则“ enGB”或“ enUS”的语言环境都需要匹配默认语言环境(除非您为其提供了单独的语言环境)。同样,在下面的示例中,如果用户的电话区域设置为arLY(阿拉伯利比亚),则defLanguage必须为“ ar”而不是“ arLY”
private Locale defLocale = Locale.getDefault();
private Locale locale = Locale.getDefault();
public static myApplication myApp;
public static Resources res;
private static String defLanguage = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry();
private static sLanguage = "en";
private static final Set<String> SUPPORTEDLANGUAGES = new HashSet<>(Arrays.asList(new String[]{"en", "ar", "arEG"}));
@Override
protected void attachBaseContext(Context base) {
if (myApp == null) myApp = this;
if (base == null) super.attachBaseContext(this);
else super.attachBaseContext(setLocale(base));
}
@Override
public void onCreate() {
myApp = this;
if (!SUPPORTEDLANGUAGES.contains(test)) {
// The default locale (eg enUS) is not in the supported list - lets see if the language is
if (SUPPORTEDLANGUAGES.contains(defLanguage.substring(0,2))) {
defLanguage = defLanguage.substring(0,2);
}
}
}
private static void setLanguage(String sLang) {
Configuration baseCfg = myApp.getBaseContext().getResources().getConfiguration();
if ( sLang.length() > 2 ) {
String s[] = sLang.split("_");
myApp.locale = new Locale(s[0],s[1]);
sLanguage = s[0] + s[1];
}
else {
myApp.locale = new Locale(sLang);
sLanguage = sLang;
}
}
public static Context setLocale(Context ctx) {
Locale.setDefault(myApp.locale);
Resources tempRes = ctx.getResources();
Configuration config = tempRes.getConfiguration();
if (Build.VERSION.SDK_INT >= 24) {
// If changing to the app default language, set locale to the default locale
if (sLanguage.equals(myApp.defLanguage)) {
config.setLocale(myApp.defLocale);
// restored the default locale as well
Locale.setDefault(myApp.defLocale);
}
else config.setLocale(myApp.locale);
ctx = ctx.createConfigurationContext(config);
// update the resources object to point to the current localisation
res = ctx.getResources();
} else {
config.locale = myApp.locale;
tempRes.updateConfiguration(config, tempRes.getDisplayMetrics());
}
return ctx;
}
为解决RTL问题,我根据answer
中的Fragments注释扩展了AppCompatActivity。public class myCompatActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(myApplication.setLocale(base));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 17) {
getWindow().getDecorView().setLayoutDirection(myApplication.isRTL() ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
}
}
答案 7 :(得分:0)
UPDATE SEP 2020
对于最新的Androidx Appcombat稳定版1.2.0,请删除1.1.0的所有替代方法并添加
package androidx.appcompat.app
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
class BaseContextWrappingDelegate(private val superDelegate:
AppCompatDelegate) : AppCompatDelegate() {
override fun getSupportActionBar() = superDelegate.supportActionBar
override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
override fun onCreate(savedInstanceState: Bundle?) {
superDelegate.onCreate(savedInstanceState)
removeActivityDelegate(superDelegate)
addActiveDelegate(this)
}
override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
override fun onStart() = superDelegate.onStart()
override fun onStop() = superDelegate.onStop()
override fun onPostResume() = superDelegate.onPostResume()
override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
override fun setContentView(v: View?) = superDelegate.setContentView(v)
override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
override fun onDestroy() {
superDelegate.onDestroy()
removeActivityDelegate(this)
}
override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
override fun installViewFactory() = superDelegate.installViewFactory()
override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
superDelegate.isHandleNativeActionModesEnabled = enabled
}
override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
override fun applyDayNight() = superDelegate.applyDayNight()
override fun setLocalNightMode(mode: Int) {
superDelegate.localNightMode = mode
}
override fun getLocalNightMode() = superDelegate.localNightMode
private fun wrap(context: Context): Context {
TODO("your wrapping implementation here")
}
}
将您的语言环境逻辑添加到函数包装中(您可以在上面接受的答案中添加ContextWrapper)。此类必须在androidx.appcompat.app包内,因为唯一现有的AppCompatDelegate构造函数是包私有
private var baseContextWrappingDelegate: AppCompatDelegate? = null
override fun getDelegate() = baseContextWrappingDelegate ?:
BaseContextWrappingDelegate(super.getDelegate()).apply {
baseContextWrappingDelegate = this
}
配置更改可能会破坏区域设置更改。要解决这个问题
override fun createConfigurationContext(overrideConfiguration: Configuration)
: Context {
val context = super.createConfigurationContext(overrideConfiguration)
TODO("your wrapping implementation here")
}
就是这样。您最好使用最新的1.2.0 appCombat
答案 8 :(得分:0)
更新2020年11月
大家好,我只想分享我的经验。几天前,我开始收到有关Android N设备中的语言未从我的应用程序设置更改不变的问题的报告。我进行了大量搜索,尝试对代码进行多次更改后,发现它们在我的代码中没有问题,并且问题是由于androidx约束Layout gradle依赖版本 2.0.0 以及降级后导致更改为 1.1.3 ,语言问题已解决。 使用此版本的ConstraintLayout库可以解决我的问题。
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'