Android - WebView语言在Android N

时间:2016-11-03 09:57:50

标签: android android-webview locale android-7.0-nougat nexus-6p

我有一个多语言应用程序,主要使用英语和第二语言阿拉伯语。

documentation中所述,

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

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

  

但是,每次我使用Activity导航到WebView   和/或WebViewClient,区域设置,文本和布局方向   突然恢复为设备默认值。

进一步提示:

  • Android 7.0 的Nexus 6P上发生 。一切都在Android 6.0.1及更低版本上正常运行。
  • 当我导航到Activity和/或{{1}的WebView时,区域设置突然发生 (我有几个)。任何其他活动都不会发生这种情况。

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

enter image description here

  

然后在导航到Locale.UK时,语言区域从WebView更改   到ar-AE

Android 7.0 API更改:

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

en-GB

Locale

  

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

此外......

  • Nexus 6P仿真器也出现问题。

  • 要获取默认语言区域,我使用的是Locale.getDefault()

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

    Configuration

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

参考文献:

1。 Native RTL support in Android 4.2

2。 Multilingual Support - Language and Locale

3。 Be wary of the default locale

13 个答案:

答案 0 :(得分:53)

Ted Hopp's answer设法解决了这个问题,但他没有解决 为什么 这个问题。

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

<强>背景

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

Android System WebView

Android 7.0更改

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

WebView disabled

WebView disabled

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

enter image description here

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

解决方案:

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

第1步:

首先,在每个WebView手动重置默认语言区域,或者至少每ActivityActivity重置一次。

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()); } 方法中调用setContentView(...)之前,请调用上述方法。 onCreate()参数应为您要设置的默认locale。例如,如果您希望将阿拉伯语/阿联酋设置为默认语言环境,则应传递Locale。或者,如果您希望设置默认语言环境(即操作系统自动设置的new Locale("ar", "AE")),则应传递Locale

第2步:

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

Locale.US

new WebView(this).destroy(); 课程的onCreate()(如果有的话),以及用户可能正在更改语言的其他任何地方。这将解决在更改语言后应用程序重新启动时可能出现的各种边缘情况(您可能已经注意到其他语言中的字符串或更改Application上具有Activities的语言后的相反对齐方式在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?

答案 1 :(得分:19)

您的代码似乎是在应用程序本身的配置中设置区域设置(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;
        }
    }
}

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

答案 2 :(得分:4)

在阅读完所有答案之后,我发现每个答案中都缺少一些东西,所以这里是迄今为止对我有用的解决方案。由于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直接加载到Activity的onCreate中而不是之后的新片段中,则必须在setContentView之后直接调用修复,然后才能设置活动的标题等。如果WebView被加载到片段中活动,调用片段的onViewCreated中的活动,活动应该调用fix方法。 并不是所有的活动都需要如上所述扩展上面的课程,这是一种过度杀伤而不是必要的。 此问题也无法解决,无法通过Google Chrome Tabs替换WebView或打开外部浏览器。

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

我也没有必要调用新的WebView(this).destroy();正如这里的答案所述。

答案 3 :(得分:4)

这对于具有多种语言的应用程序具有重要的意义。如果您的应用程序具有WebView,则将使用Chrome应用程序呈现这些视图。由于Chrome本身就是一个Android应用,因此以其自己的沙盒流程运行,因此不会绑定到您的应用设置的语言环境。取而代之的是,Chrome会还原为主要设备的语言环境。例如,假设您的应用语言环境设置为ar-AE,而设备的主要语言环境是en-US。在这种情况下,包含WebView的活动的区域设置将从ar-AE更改为en-US,并且将显示来自相应区域设置文件夹的字符串和资源。在具有WebView的活动上,您可能会看到LTR和RTL字符串/资源杂乱无章的情况。

解决方案:

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

步骤1:

首先,在每个“活动”或至少每个具有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());
    }

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

步骤2:

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

new WebView(this).destroy();

在Application类的onCreate()中(如果有的话),以及用户可能在其他任何地方更改语言。这将解决在更改语言后重新启动应用程序时可能发生的各种极端情况(您可能已经注意到其他语言的字符串或在具有Android 7.0 ++的带有WebViews的“活动”中更改语言后具有相反的对齐方式)。

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

答案 4 :(得分:1)

这里的问题相同。我有一个肮脏但简单的解决方案。

因为我发现Activity.onCreate(...)函数中的语言环境仍然很好,并且在Activity.onPostCreate(...)函数中不再有效,所以我只保存语言环境并强制它结束ofPostCreate(...)函数。

我们走了:

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

希望它会有所帮助。

答案 5 :(得分:0)

上述答案都没有帮助我,我设法在包含Webview的活动的 onStop()方法中再次重置应用区域设置

答案 6 :(得分:0)

我想在此处再添加一个用例

当从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活动中至少调用上述方法。

修改 如果您正在处理Fragments,请确保在用户退出webview时调用此方法。

答案 7 :(得分:0)

如果您仅使用WebView显示富文本(带有某些段落的文本或不同字体大小的粗体和斜体文本),则可以使用TextView和Html.fromHtml()代替。 TextViews没有区域设置问题; - )

答案 8 :(得分:0)

仅将SEt本地方法的参数更改从传递BaseContext传递到“ this”或确切的活动(特别是在android 7.0及更早版本上)

答案 9 :(得分:0)

我在片段中面临着同样的问题。 通过在 onDestroyView 函数

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

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

我通过使用 lang 标签更改 HTML 的语言来解决发音问题。 HTML Language Accessibility

答案 12 :(得分:-1)

在Android N中,当您执行new WebView()时,它会将/system/app/WebViewGoogle/WebViewGoogle.apk添加到资源路径,如果它未添加到路径,则会导致资源重新创建。

因此,如果您想解决问题,请在更改本地之前在应用程序中执行new WebView(application)

如果你懂中文,you can read this blog