Android-WebView 语言在 Android 7.0及以上版本中突然改变

我有一个多语言的应用程序与主要语言英语和中学语言阿拉伯语。

根据 文件的描述,

  • 我已经在清单中加入了 android:supportsRtl="true"
  • 我已经将具有 leftright属性的所有 xml 属性分别更改为 startend
  • 我已经在 strings-ar中添加了阿拉伯语字符串(对于其他资源也是如此)。

上述设置工作正常。将 Locale改为 ar-AE后,阿拉伯文本和资源将正确显示在我的活动中。

但是,每次我导航到一个 ActivityWebView 及/或 WebViewClient、地区、文字及布局方向 突然恢复到设备默认状态。

进一步提示:

  • 这是发生在带有 Android 7.0的 Nexus 6P 上的 只有。在 Android 6.0.1及以下版本上,一切都能正常工作。
  • 当我导航到一个具有 WebView和/或 WebViewClient(我有几个)的 Activity时,语言环境的突变发生在 只有。它不会出现在任何其他活动中。

Android 7.0支持多语言环境,允许用户设置多个默认语言环境。因此,如果我将主语言环境设置为 Locale.UK:

enter image description here

然后,在导航到 WebView时,语言环境从 ar-AE改变 到 en-GB

Android 7.0 API 更改:

空气污染指数变化清单所示,在 API 24中的以下类中添加了与区域设置有关的新方法:

返回文章页面

返回文章页面

但是,我正在用 API 23构建我的应用程序,并且没有使用任何 这些新方法。

而且..。

  • 这个问题也发生在 Nexus6P 仿真器上。

  • 要获得默认的区域设置,我使用的是 Locale.getDefault()

  • 要设置默认语言环境,我使用以下代码:

    public static void setLocale(Locale locale){
    Locale.setDefault(locale);
    Configuration config = new Configuration();
    config.setLocale(locale);
    Context context = MyApplication.getInstance();
    context.getResources().updateConfiguration(config,
    context.getResources().getDisplayMetrics());
    }
    

Has anyone encountered this problem before? What is the reason for it, and how do I resolve this?

References:

1. Native RTL support in Android 4.2.

2. Multilingual Support - Language and Locale.

3. Be wary of the default locale.

21657 次浏览

您的代码似乎正在为应用程序本身(MyApplication.getInstance())设置配置中的区域设置。但是,在膨胀活动的内容视图之前,您需要更新活动上下文的配置。我发现修改应用程序的上下文是不够的(事实证明,甚至没有必要)。如果我不更新每个活动上下文,那么不同活动之间的行为是不一致的。

解决这个问题的方法是使用子类 AppCompatActivity(或者 Activity,如果不使用兼容性库的话) ,然后从这个子类派生所有的活动类。下面是我的代码的一个简化版本:

public class LocaleSensitiveActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
Locale locale = ... // the locale to use for this activity
fixupLocale(this, locale);
super.onCreate(savedInstanceState);
...
}


static void fixupLocale(Context ctx, Locale newLocale) {
final Resources res = ctx.getResources();
final Configuration config = res.getConfiguration();
final Locale curLocale = getLocale(config);
if (!curLocale.equals(newLocale)) {
Locale.setDefault(newLocale);
final Configuration conf = new Configuration(config);
conf.setLocale(newLocale);
res.updateConfiguration(conf, res.getDisplayMetrics());
}
}


private static Locale getLocale(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return config.getLocales().get(0);
} else {
//noinspection deprecation
return config.locale;
}
}
}

然后,确保在每个子类的 onCreate()方法 之前中调用 super.onCreate(savedInstanceState),调用使用上下文的任何方法(如 setContentView())。

Ted Hopp 的回答设法解决了这个问题,但是他没有解决 为什么的问题。

原因是在 Android 7.0中对 WebView类及其支持包所做的更改。

背景:

Android 的 WebView是使用 WebKit构建的。虽然它最初是 AOSP 的一部分,但从 KitKat 开始,决定将 WebView剥离成一个称为 Android 系统 WebView的单独组件。它本质上是一个预装 Android 设备的 Android 系统应用程序。它定期更新,就像其他系统应用程序,如谷歌播放服务和播放商店应用程序。你可以在已安装的系统应用程序列表中看到它:

Android System WebView

Android 7.0变化 :

从 Android N 开始,Chrome 应用程序将用于渲染第三方 Android 应用程序中的所有 WebView。在 Android N 开箱即用的手机中,Android WebView 系统应用程序根本不存在。在接收到 Android N 的 OTA 更新的设备中,Android System WebView 被禁用:

WebView disabled

还有

WebView disabled

此外,还引入了多语言环境支持,设备具有多种默认语言:

enter image description here

这对于使用多种语言的应用程序有着重要的影响。如果你的应用程序有 WebView,那么这些都是使用 Chrome 应用程序渲染的。因为 Chrome 是一个安卓应用程序 本身,运行在它自己的沙箱进程中,它不会被绑定到你的应用程序设置的地区。相反,Chrome 将恢复到主设备区域。例如,假设您的应用程序区域设置为 ar-AE,而设备的主区域设置为 en-US。在这种情况下,包含 WebViewActivity的区域设置将从 ar-AE变为 en-US,并且将显示来自相应区域设置文件夹的字符串和资源。您可能会在那些具有 WebViewActivity上看到大量 LTR 和 RTL 字符串/资源。

解决方案:

这个问题的完整解决办法包括两个步骤:

第一步:

首先,在每个 Activity中手动重置默认语言环境,或者至少在每个具有 WebViewActivity中手动重置默认语言环境。

public static void setLocale(Locale locale){
Context context = MyApplication.getInstance();
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale.setDefault(locale);
configuration.setLocale(locale);


if (Build.VERSION.SDK_INT >= 25) {
context = context.getApplicationContext().createConfigurationContext(configuration);
context = context.createConfigurationContext(configuration);
}


context.getResources().updateConfiguration(configuration,
resources.getDisplayMetrics());
}

在所有活动的 onCreate()方法中调用 setContentView(...)之前调用上述方法。locale参数应该是您希望设置的默认 Locale。例如,如果希望将阿拉伯语/UAE 设置为默认语言环境,则应该传递 new Locale("ar", "AE")。或者,如果您希望设置默认语言环境(即操作系统自动设置的 Locale) ,则应该传递 Locale.US

第二步:

此外,还需要添加以下代码行:

new WebView(this).destroy();

Application类的 onCreate()中(如果有的话) ,以及用户可能正在更改语言的其他任何地方。这将解决所有在改变语言后重启应用程序时可能出现的边缘情况(你可能已经注意到了其他语言中的字符串,或者在改变 Activities上的语言(Android 7.0 + + 上有 WebView)后出现了相反的对齐方式)。

作为补充,Chrome 自定义标签现在是渲染应用程序内网页的首选方式。

参考文献:

1. < strong > Android 7.0-WebView的更改 .

2. 了解 WebView 和 Android 安全补丁 .

3. < strong > WebView for Android .

4. < strong > WebView: 从“由 Chrome 驱动”到直接的 Chrome .

5. < strong > 牛轧糖 WebView .

6. < strong > Android 7.0 Nougat .

7. Android N 之谜,第一部分: Android System WebView 现在只是“ Chrome”.

上面的答案都没有帮助到我,我设法在包含 Webview 的活动的 OnStop ()方法中重置了应用程序的语言环境

在 Android N 中,当您执行 new WebView()时,它会将 /system/app/WebViewGoogle/WebViewGoogle.apk添加到资源路径。如果它还没有添加到路径中,它将导致资源重新创建(只有第一次在应用程序中使用 WebView)。

因此,如果您想解决这个问题,只需在 Application#OnCreate()中执行 new WebView(applicationContext),然后再更改语言环境。

如果你懂中文,你就可以读 这个博客

我想在这里再加一个 用例:

当从 webview 活动按回(即显示付款屏幕和用户按回按钮) ,onCreate ()以前的活动不执行,所以语言得到重置。为了保持它的错误自由,我们必须重置在 基础活动 onResume()应用程序的地区

private static void updateResources(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration config = new Configuration();
config.setLocale(locale);
config.setLayoutDirection(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
}

在基本活动的 onResume ()或至少在 webview 活动中调用上述方法。

编辑: 如果您正在处理片段,请确保在用户从 webview 退出时调用此方法。

同样的问题,我有个肮脏但简单的解决方案。

因为我观察到在 Activity.onCreate (...)函数中语言环境仍然很好,而在 Activity.onPostCreate (...)函数中不再有效,所以我只是保存语言环境并在 onPostCreate (...)函数的末尾强制它。

开始了:

private Locale backedUpLocale = null;


@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
backedUpLocale = getApplicationContext().getResources().getConfiguration().locale;
}


@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
changeLocale(backedUpLocale);
}

额外收获-更改区域设置函数:

public void changeLocale(final Locale locale) {


final Configuration config = res.getConfiguration();


if(null != locale && !config.locale.equals(locale)) {
Locale.setDefault(locale);


final Configuration newConfig = new Configuration(config);


if(PlatformVersion.isAtLeastJellyBeanMR1()) {
newConfig.setLocale(new Locale(locale.getLanguage()));
} else {
newConfig.locale = new Locale(locale.getLanguage());
}


res.updateConfiguration(newConfig, null);
}
}

希望能有所帮助。

如果您使用 WebView 只是为了显示富文本(包含一些段落的文本或不同字体大小的粗体和斜体文本) ,那么您可以改用 TextView 和 来自 Html ()。TextView 与区域设置没有问题; -)

在阅读了所有的答案之后,我发现每一个都缺少了一些东西,所以这里是目前为止对我起作用的解决方案。由于 WebView 覆盖了活动上下文和应用程序上下文的语言配置,因此必须确保每次发生这种情况时都调用一个方法来重置这些更改。在我的案例中,我写了下面的类,我的活动提出了这个问题扩展(那些显示一个 WebView) :

public class WebViewFixAppCompatActivity extends AppCompatActivity {


private Locale mBackedUpLocale = null;


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mBackedUpLocale = getApplicationContext().getResources().getConfiguration().getLocales().get(0);
}
}


@Override
protected void onStop() {
super.onStop();
fixLocale();
}


@Override
public void onBackPressed() {
fixLocale();
super.onBackPressed();
}


/**
* The locale configuration of the activity context and the global application context gets overridden with the first language the app supports.
*/
public void fixLocale() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Resources resources = getResources();
final Configuration config = resources.getConfiguration();


if (null != mBackedUpLocale && !config.getLocales().get(0).equals(mBackedUpLocale)) {
Locale.setDefault(mBackedUpLocale);
final Configuration newConfig = new Configuration(config);
newConfig.setLocale(new Locale(mBackedUpLocale.getLanguage(), mBackedUpLocale.getCountry()));
resources.updateConfiguration(newConfig, null);
}


// Also this must be overridden, otherwise for example when opening a dialog the title could have one language and the content other, because
// different contexts are used to get the resources.
Resources appResources = getApplicationContext().getResources();
final Configuration appConfig = appResources.getConfiguration();
if (null != mBackedUpLocale && !appConfig.getLocales().get(0).equals(mBackedUpLocale)) {
Locale.setDefault(mBackedUpLocale);
final Configuration newConfig = new Configuration(appConfig);
newConfig.setLocale(new Locale(mBackedUpLocale.getLanguage(), mBackedUpLocale.getCountry()));
appResources.updateConfiguration(newConfig, null);
}


}
}
}

@ Tobliug 发布的在 WebView 覆盖之前保存初始配置的想法对我很有效,在我的特殊情况下,我发现这比发布的其他解决方案更容易实现。 重要的是,在退出 WebView 之后调用 fix 方法,例如在回按和 onStop 中。 如果 webView 显示在一个对话框中,那么您必须注意在忽略该对话框之后调用 fix 方法,主要是在 onResume 和/或 onCreate 中。如果 webView 直接加载到活动的 onCreate 中,而不是之后加载到新的片段中,那么在设置活动的标题之前,还必须在 setContentView 之后直接调用该修复程序,等等。如果 WebView 装载在活动的片段中,那么调用片段的 onViewCreated 中的活动,该活动应该调用 fix 方法。 并不是所有的活动都需要扩展上面的类,正如在回答中指出的那样,这是一个过度的要求,没有必要。 这个问题也没有得到解决,取代 WebView 的谷歌浏览器标签或打开一个外部浏览器。

如果您确实需要配置资源来设置整个语言列表,而不仅仅是一种语言,那么您需要将这个解决方案与位于 < a href = “ https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce”rel = “ norefrer”> https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce 的语言合并 对我来说,这是没有必要的。

我也认为没有必要调用 new WebView (this) . delete () ; 正如在这里的回答中所指出的那样。

只需将 SET 本地方法的参数从 BaseContext 传递到“ this”或精确活动即可,特别是在 android 7.0及以上版本中

这对于使用多种语言的应用程序有着重要的影响。如果你的应用程序有网络视图,那么这些都是使用 Chrome 应用程序呈现的。因为 Chrome 本身就是一个 Android 应用程序,运行在它自己的沙箱进程中,它不会被绑定到你的应用程序设置的语言环境中。相反,Chrome 将恢复到主设备区域。例如,假设您的应用程序区域设置为 ar-AE,而设备的主区域设置为 en-US。在这种情况下,包含 WebView 的 Activity 的区域设置将从 ar-AE 更改为 en-US,并且将显示来自相应区域设置文件夹的字符串和资源。您可能会在那些具有 WebView 的活动上看到 LTR 和 RTL 字符串/资源的混杂。

解决方案:

这个问题的完整解决办法包括两个步骤:

第一步:

首先,在每个活动中手动重置默认语言环境,或者至少在每个具有 WebView 的活动中手动重置默认语言环境。

public static void setLocale(Locale locale){
Context context = MyApplication.getInstance();
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale.setDefault(locale);
configuration.setLocale(locale);


if (Build.VERSION.SDK_INT >= 25) {
context = context.getApplicationContext().createConfigurationContext(configuration);
context = context.createConfigurationContext(configuration);
}


context.getResources().updateConfiguration(configuration,
resources.getDisplayMetrics());
}

在调用所有活动的 onCreate ()方法中的 setContentView (...)之前调用上述方法。Locale 参数应该是要设置的默认 Locale。例如,如果希望将阿拉伯语/UAE 设置为默认语言环境,则应该传递新的语言环境(“ ar”,“ AE”)。或者,如果希望设置默认的语言环境(即操作系统自动设置的语言环境) ,则应该传递 Locale.US。

第二步:

此外,还需要添加以下代码行:

new WebView(this).destroy();

在 Application 类的 onCreate ()中(如果有的话) ,以及用户可能正在更改语言的其他地方。这将解决在改变语言后重启应用程序时可能出现的各种边缘情况(你可能已经注意到了其他语言中的字符串,或者在改变 Android 7.0 + + 上支持 WebView 的活动中的语言后出现了相反的对齐方式)。

作为补充,Chrome 自定义标签页现在是渲染应用内页面的首选方式。

我在碎片里也遇到了同样的问题。 通过在 OnDestroyView函数中再次设置语言来解决这个问题

@Override
public void onDestroyView() {
Utils.setLanguage(requireActivity());
super.onDestroyView();
}

我注意到这种情况只发生在你第一次在应用中使用 web 视图的时候,但是之后就不会发生了(也就是说,如果语言从应用内部改变,你打开了一个 web 视图——再一次——或者另一个 webview,那么这次 webview 不会像第一次那样改变语言。 所以基于这一点,一个简单的解决方案就是我添加了一个 webview,它的可见性会出现在我在这个应用程序中的第一个活动(或者主机活动,如果你使用带有片段的单个活动应用程序)中,然后在调用 activity.xml 中的 setcontentview.so 之后,我会在该活动的 onCreate 方法中应用我的 locale:

<WebView
android:id="@+id/webview"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

在我的活动中:

    override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)//at this moment and because there is the webview in the xml, the language will be reverted to the device's language
    

    

updateLocal(this ,"en")//this reset it back to whatever language you want to use(from shared preferences for example )
}

和 updateLocal,其代码如下:

fun updateLocale(
c: Context,
languageToSwitchTo: String
): Context {
val locale = Locale(languageToSwitchTo)
Locale.setDefault(locale)
var context = c
val resources = context.resources


val configuration: Configuration = resources.configuration
configuration.setLayoutDirection(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val localeList = LocaleList(locale)
LocaleList.setDefault(localeList)
configuration.setLocales(localeList)
} else {
configuration.locale = locale


}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
context = context.createConfigurationContext(configuration)
}


resources.updateConfiguration(configuration, resources.displayMetrics)


return context
}

我通过使用 lang 标记改变 HTML 的语言来解决发音问题。 HTML 语言无障碍