Android - WebView语言在Android N上突然改变

Y.S*_*Y.S 57 android locale android-webview android-7.0-nougat android-chrome

我有一个多语言的应用程序与主要语言英语和第二语言阿拉伯语.

文档中所述,

  • android:supportsRtl="true"在清单中添加了.
  • 我已经改变了所有XML属性与leftright属性start,并end分别.
  • 我已添加阿拉伯语言字符串strings-ar(和其他资源类似).

以上设置正常.改变后的Localear-AE,阿拉伯文字和资源都正确地显示在我的活动.

但是,每次我导航到Activitya WebView 和/或a时WebViewClient,语言环境,文本和布局方向都会突然恢复为设备默认值.

进一步提示:

  • 在具有Android 7.0的Nexus 6P上发生.一切都在Android 6.0.1及更低版本上正常运行.
  • 只有当我导航到Activity有a WebView和/或a WebViewClient(并且我有几个)时才会发生语言环境的突然转变.它不会发生在任何其他活动上.

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

在此输入图像描述

然后在导航到时WebView,语言环境从更改ar-AEen-GB.

Android 7.0 API更改:

API更改列表中所示,API 24中的以下类中添加了与语言环境相关的新方法:

Locale:

Configuration:

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

此外......

  • 问题也出现在Nexus 6P仿真器上.

  • 要获取默认语言环境,我正在使用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());
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

参考文献:

1. Android 4.2中的原生RTL支持.

2. 多语言支持 - 语言和语言环境.

3. 警惕默认语言环境.

Y.S*_*Y.S 62

Ted Hopp的答案设法解决了这个问题,但他没有解决为什么会这样的问题.

原因是WebView在Android 7.0中对类及其支持包进行了更改.

背景:

Android WebView是使用WebKit构建的.虽然它最初是AOSP的一部分,但从KitKat开始,我们决定将其分拆WebView为一个名为Android System WebView的独立组件.它本质上是预装Android设备的Android系统应用程序.它会定期更新,就像其他系统应用程序一样,例如Google Play服务和Play商店应用.您可以在已安装的系统应用列表中看到它:

Android系统WebView

Android 7.0更改:

从Android N开始,Chrome应用将用于呈现WebView第三方Android应用中的任何/所有内容.在具有Android N开箱即用功能的手机中,Android WebView System应用程序根本不存在.在已收到Android N的OTA更新的设备中,Android系统WebView被禁用:

WebView已禁用

WebView已禁用

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

在此输入图像描述

这对于具有多种语言的应用程序具有重要意义.如果您的应用具有WebViews,则会使用Chrome应用呈现这些应用.由于Chrome 本身就是一个Android应用,在自己的沙盒流程中运行,因此不会绑定到您的应用设置的区域设置.相反,Chrome将恢复为主要设备区域设置.例如,假设您的应用程序区域设置设置为ar-AE,而设备的主要区域设置是en-US.在这种情况下,Activity包含a 的区域设置WebView将从更改ar-AEen-US,并且将显示来自相应区域设置文件夹的字符串和资源.您可能会看到ActivityWebViews 的混合LTR和RTL字符串/资源.

解决方案:

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

步骤1:

首先,手动重置默认语言环境Activity,或者至少每个语言环境Activity都有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());
}
Run Code Online (Sandbox Code Playgroud)

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

第2步:

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

new WebView(this).destroy();
Run Code Online (Sandbox Code Playgroud)

onCreate()你的Application班级(如果你有的话),以及用户可能正在改变语言的任何地方.这将采取各种改变语言后重新启动应用程序可能发生的极端情况护理(你可能已经注意到在其它语言或改变的语言之后反向对齐串ActivitiesWebView在Android 7.0 + S).

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

参考文献:

1. Android 7.0 - 更改WebView.

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

3. 适用于Android的WebView.

4. WebView:从"由Chrome提供支持"到直接Chrome.

5. Nougat WebView.

6. Android 7.0牛轧糖.

7. Android N之谜,第1部分:Android系统WebView现在只是"Chrome"吗?.

  • 在预算设备或旧设备上将“new WebView(this).destroy();”添加到“Application.onCreate()”冷启动时间会增加约 1 秒。 (2认同)

Ted*_*opp 20

您的代码似乎是在应用程序本身(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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


小智 7

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

解决方案:

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

步骤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());
    }
Run Code Online (Sandbox Code Playgroud)

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

第2步:

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

new WebView(this).destroy();
Run Code Online (Sandbox Code Playgroud)

在您的 Application 类(如果有的话)的 onCreate() 中,以及用户可能更改语言的其他位置。这将处理更改语言后应用程序重新启动时可能出现的各种边缘情况(您可能已经注意到其他语言的字符串或在更改 Android 7.0++ 上具有 WebViews 的活动的语言后具有相反的对齐方式)。

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


Dav*_*vid 5

阅读完所有答案后,我发现每个答案都缺少一些东西,所以这是迄今为止对我有用的解决方案。由于 WebView 会覆盖 Activity 上下文和应用程序上下文的语言配置,因此您必须确保每次发生这种情况时都调用一个方法来重置这些更改。在我的情况下,我写了以下课程,我的活动呈现这个问题(那些显示 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);
        }

    }
}
}
Run Code Online (Sandbox Code Playgroud)

@Tobliug 发布的想法是在 WebView 覆盖它对我有用之前保存初始配置,在我的特殊情况下,我发现这比发布的其他解决方案更容易实现。重要的是在退出 WebView 后会调用 fix 方法,例如在按下返回和 onStop 时。如果 webView 显示在对话框中,您必须注意在关闭对话框后调用修复方法,主要是在 onResume 和/或 onCreate 中。如果 webView 直接在 Activity 的 onCreate 中加载,而不是在新片段中加载,则还必须在 setContentView 之后直接调用修复程序,然后再设置 Activity 的标题等。如果 WebView 加载到 Activity 的片段中,请调用片段的 onViewCreated 中的活动和活动应调用修复方法。并非所有活动都需要扩展上述类,如答案中所述,这是一种矫枉过正且没有必要。这个问题也没有得到解决,用谷歌浏览器标签替换 WebView 或打开外部浏览器。

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

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