Android - WebView 语言在 Android 7.0 及更高版本上突然更改

Android - WebView language changes abruptly on Android 7.0 and above

我有一个多语言应用,主要语言是英语,第二语言是阿拉伯语。

documentation

中所述

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

However, every time I navigate to an Activity with a WebView and/or a WebViewClient, the locale, text and layout direction abruptly revert to the device default.

进一步提示:

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

Then on navigating to the WebView, the locale changes from ar-AE to en-GB.

Android 7.0 API 变化:

list of API changes 中所述,已将与语言环境相关的新方法添加到 API 24 中的以下 类:

Locale:

Configuration:

However, I am building my app with API 23, and am not using any of these new methods.

还有...

有没有人遇到过这个问题?这是什么原因,我该如何解决?

参考文献:

1. Native RTL support in Android 4.2.

2. Multilingual Support - Language and Locale.

3. Be wary of the default locale.

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

我的方法是子类化 AppCompatActivity(或 Activity,如果不使用兼容性库),然后从中派生出我所有的 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() 方法 调用 调用任何使用的方法(例如 setContentView())之前调用 super.onCreate(savedInstanceState)上下文。

设法解决了问题,但他没有解决为什么会这样的问题。

原因是 WebView class 及其在 Android 7.0 中的支持包的更改。

背景:

Android 的 WebView 是使用 WebKit. While it was originally a part of AOSP, from KitKat onwards a decision was made to spin off WebView into a separate component called Android System WebView 构建的。它本质上是一个预装在 Android 设备上的 Android 系统应用程序。它会定期更新,就像 Google Play 服务和 Play 商店应用等其他系统应用程序一样。您可以在已安装的系统应用程序列表中看到它:

Android 7.0 变化:

从 Android N 开始,Chrome 应用程序将用于在第三方 Android 应用程序中呈现 any/all WebViews。在开箱即用 Android N 的手机中,Android WebView 系统应用根本不存在。在已收到 Android N OTA 更新的设备中,Android 系统 WebView 被禁用:

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

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

解决方法:

这个问题的完整解决方案包括两个步骤:

第 1 步:

首先,在每个 Activity 或至少每个具有 WebView.

Activity 中手动重置默认区域设置
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。例如,如果您希望将 Arabic/UAE 设置为默认语言环境,则应传递 new Locale("ar", "AE")。或者,如果您希望设置默认区域设置(即操作系统自动设置的 Locale),您应该传递 Locale.US.

第 2 步:

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

new WebView(this).destroy();

在您的 Application class 的 onCreate() 中(如果您有),以及用户可能更改语言的其他任何地方。这将处理更改语言后应用重新启动时可能发生的各种边缘情况(您可能已经注意到其他语言的字符串或在 Activities 上更改语言后具有 WebViews 在 Android 7.0++).

作为附录,Chrome custom tabs 现在是呈现应用内网页的首选方式。

参考文献:

1. Android 7.0 - changes for WebView.

2. Understanding WebView and Android security patches.

3. WebView for Android.

4. WebView: From "Powered by Chrome" to straight up Chrome.

5. Nougat WebView.

6. Android 7.0 Nougat.

7. Android N Mysteries, Part 1: Android System WebView is just "Chrome" Now?.

None 上面的答案帮助了我,我设法在 onStop() 包含 Webviewactivity 的方法中再次重置应用程序区域设置=10=]

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

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

如果你懂中文,你可以阅读this blog

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

当从 webview activity 返回时(即显示支付屏幕和用户按下返回按钮),之前 activity 的 onCreate() 不会执行,因此语言再次重置。为了保持它没有错误,我们必须在基础 Activity.

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

在基础 activity 的 onResume() 中或至少在 webview activity 中调用上述方法。

编辑: 如果您正在处理片段,请确保在用户退出 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.fromHtml()。 TextViews 没有区域设置问题;-)

阅读所有答案后,我发现每个答案都缺少一些东西,所以这里是迄今为止对我有用的解决方案。由于 WebView 覆盖了 activity 的上下文和应用程序上下文的语言配置,因此您必须确保每次发生这种情况时调用一个方法来重置这些更改。在我的例子中,我在 class 之后写道,我的活动扩展了这个问题(显示 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 覆盖之前保存初始配置的想法对我有用,在我的特定情况下,我发现这比发布的其他解决方案更容易实现。 重要的是 fix 方法在退出 WebView 后被调用,例如当按下并在 onStop 时。 如果 webView 显示在对话框中,您必须注意在关闭对话框后调用 fix 方法,主要是在 onResume and/or onCreate 中。如果 webView 直接加载到 Activity 的 onCreate 中,而不是之后在新片段中加载,则还必须在设置 activity 的标题之前直接在 setContentView 之后调用修复,等等。如果 WebView在 activity 中的片段内加载,在片段的 onViewCreated 中调用 activity 并且 activity 应该调用 fix 方法。 并非所有活动都需要扩展上面的 class,如回答中所述,这是一种矫枉过正,没有必要。 通过 Google Chrome 选项卡或打开外部浏览器替换 WebView 也无法解决此问题。

如果您真的需要您的资源配置来设置整个语言列表,而不仅仅是一种语言,那么您需要将此解决方案与 https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce 中的解决方案合并 就我而言,这是没有必要的。

我也没有发现有必要调用 new WebView(this).destroy();如此处的答案所述。

只需将 SEt 本地方法的参数从传递 BaseContext 更改为 "this" 或精确 activity 特别是 android 7.0 及更早版本

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

解决方案:

这个问题的完整解决方案包括两个步骤:

第 1 步:

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

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。例如,如果您希望将 Arabic/UAE 设置为默认语言环境,您应该传递 new Locale("ar", "AE")。或者如果你想设置默认区域设置(即操作系统自动设置的区域设置),你应该通过 Locale.US.

第 2 步:

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

new WebView(this).destroy();

在您的应用程序 class(如果您有)的 onCreate() 中,以及用户可能更改语言的其他任何地方。这将处理更改语言后应用重新启动时可能发生的各种边缘情况(您可能已经注意到其他语言的字符串或在 Android 7.0 上具有 WebView 的 Activity 上更改语言后具有相反对齐方式的字符串++).

作为附录,Chrome 自定义标签现在是呈现应用内网页的首选方式。

我在片段中遇到了同样的问题。 通过在 onDestroyView 函数

中再次设置语言来解决此问题
@Override
    public void onDestroyView() {
        Utils.setLanguage(requireActivity());
        super.onDestroyView();
    }

我注意到这只会在您第一次在应用程序中使用网络视图时发生,但之后就不会发生(即,如果从应用程序内部更改语言并且您再次打开网络视图- 或者另一个 webview,那么这次 webview 不会像第一次那样改回语言。 因此,基于此,我所做的一个简单解决方案是我添加了一个 webview,其可见性已转到我在应用程序中的第一个 activity(如果您使用单个 [=21=,则为主机 activity ] 带有片段的应用程序)然后我在我的 activity.xml 中调用 setContentView.So 之后在 activity 的 onCreate 方法中应用我的语言环境我将有:

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

在我的 activity 中:

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