Подтвердить что ты не робот

Android - язык WebView резко меняется на Android 7.0 и выше

У меня есть многоязычное приложение с основным языком на английском и арабском языках.

Как описано в документации,

  • Я добавил android:supportsRtl="true" в манифест.
  • Я изменил все свойства xml с атрибутами left и right на start и end соответственно.
  • Я добавил строки арабского языка в strings-ar (и аналогично для других ресурсов).

Вышеуказанная настройка работает правильно. После изменения Locale на ar-AE, арабский текст и ресурсы отображаются правильно в моих действиях.

Однако каждый раз, когда я перехожу к Activity с WebViewи/или a WebViewClient, язык локали, текста и макета внезапно вернитесь к устройству по умолчанию.

Дополнительные советы:

  • Это происходит только на Nexus 6P с Android 7.0. Все работает правильно на Android 6.0.1 и ниже.
  • Резкий сдвиг в локали происходит только, когда я перехожу к Activity, который имеет WebView и/или a WebViewClient (и у меня их несколько). Это не происходит ни в одной из других Деяний.

Android 7.0 поддерживает многоязычность, позволяя пользователю устанавливать более одного стандарта по умолчанию. Поэтому, если я устанавливаю основной язык в Locale.UK:

введите описание изображения здесь

Затем при переходе к WebView, локаль изменяется с ar-AEдо en-GB.

Изменения API Android 7.0:

Как указано в списке изменений 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());
    }
    

Кто-нибудь сталкивался с этой проблемой раньше? В чем причина этого и как мне это решить?

Литература:

1. Встроенная поддержка RTL в Android 4.2.

2. Многоязыковая поддержка - язык и локаль.

3. Будьте осторожны с языковым стандартом по умолчанию.

4b9b3361

Ответ 1

Ответ теда Хоппа удалось решить проблему, но он не ответил на вопрос , почему это происходит.

Причина - изменения, внесенные в класс WebView и его пакет поддержки в Android 7.0.

Справочная информация:

Android WebView построен с использованием WebKit. Хотя изначально он был частью AOSP, начиная с KitKat было принято решение выделить WebView в отдельный компонент, который называется Android System WebView. По сути, это системное приложение Android, которое предустановлено на устройствах Android. Он периодически обновляется, как и другие системные приложения, такие как Google Play Services и приложение Play Store. Вы можете увидеть его в списке установленных системных приложений:

Android System WebView

Изменения в Android 7.0:

Начиная с Android N, приложение Chrome будет использоваться для рендеринга любых/всех WebView в сторонних приложениях Android. В телефонах с установленной ОС Android N приложение Android WebView System отсутствует вообще. На устройствах, которые получили OTA-обновление до Android N, веб-представление Android System отключено:

WebView disabled

и

WebView disabled

Кроме того, была введена поддержка нескольких локалей, когда устройства имеют более одного языка по умолчанию:

enter image description here

Это имеет важное значение для приложений с несколькими языками. Если в вашем приложении WebView, то они отображаются с помощью приложения Chrome. Поскольку Chrome сам по себе является приложением Android и работает в своем изолированном изолированном процессе, он не будет привязан к локали, установленной вашим приложением. Вместо этого Chrome вернется к основной локали устройства. Например, скажем, языковой стандарт вашего приложения установлен на ar-AE, а основным языковым стандартом устройства является en-US. В этом случае локаль Activity, содержащая WebView, изменится с ar-AE на en-US, и будут отображены строки и ресурсы из соответствующих папок локали. Вы можете увидеть мешанину строк/ресурсов LTR и RTL на тех Activity, которые имеют WebView s.

Решение:

Полное решение этой проблемы состоит из двух шагов:

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

Вызовите указанный выше метод перед вызовом setContentView(...) в методе onCreate() всех ваших действий. Параметр locale должен быть значением по умолчанию Locale, которое вы хотите установить. Например, если вы хотите установить арабский язык /UAE в качестве локали по умолчанию, вы должны передать new Locale("ar", "AE"). Или, если вы хотите установить язык по умолчанию (то есть Locale, который автоматически устанавливается операционной системой), вы должны передать Locale.US.

ШАГ 2:

Кроме того, вам нужно добавить следующую строку кода:

new WebView(this).destroy();

в onCreate() вашего класса Application (если он у вас есть) и везде, где пользователь может менять язык. Это позаботится обо всех видах крайних случаев, которые могут возникнуть при перезапуске приложения после изменения языка (вы могли заметить строки на других языках или с противоположным выравниванием после изменения языка на Activities, которые имеют WebView на Android 7. 0++).

В качестве дополнения пользовательские вкладки Chrome теперь являются предпочтительным способом отображения веб-страниц в приложении.

Ссылки:

1. Android7.0 - изменения для WebView.

2. Пониманиеисправлений безопасности для WebView и Android.

3. WebViewдля Android.

4. WebView:от "Работает на Chrome" до "Chrome".

5. NougatWebView.

6. Android7.0 Nougat.

7. ТайныAndroid N, часть 1: Android System WebView теперь просто "Chrome"?.

Ответ 2

Кажется, что ваш код устанавливает локаль в конфигурации самого приложения (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()), которые используют контекст.

Ответ 3

Прочитав все ответы, я обнаружил, что в каждом из них чего-то не хватает, поэтому вот решение, которое до сих пор работало для меня. Поскольку 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 переопределит ее, сработала для меня, в моем конкретном случае я обнаружил, что это легче реализовать, чем другие опубликованные решения. Важно то, что метод fix вызывается после выхода из WebView, например, при нажатии назад и в onStop. Если webView отображается в диалоге, вы должны позаботиться о том, чтобы метод fix вызывался после закрытия диалогового окна, в основном в onResume и/или onCreate. И если веб-представление загружается непосредственно в onCreate действия, а не после этого в новом фрагменте, исправление также должно вызываться непосредственно после setContentView до установки заголовка действия и т.д. Если веб-представление загружается внутри фрагмента в действии, вызовите активность в onViewCreated фрагмента и активность должны вызывать метод fix. Не все виды деятельности должны расширяться вышеупомянутым классом, как отмечается в ответе, что это излишнее и не нужно. Эта проблема также не решена путем замены WebView на вкладки Google Chrome или открытия внешнего браузера.

Если вам действительно нужно настроить свои ресурсы для настройки всего списка языков, а не только одного, то вам нужно объединить это решение с тем, что находится на https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce В моем случае это было не нужно.

Я также не нашел необходимости вызывать новый WebView (this).destroy(); как отмечено в ответе здесь.

Ответ 4

Такая же проблема. У меня грязное, но простое решение.

Поскольку я замечаю, что локаль по-прежнему хороша в функции Activity.onCreate(...) и не более достоверна в функции Activity.onPostCreate(...), я просто сохраняю Locale и заставляю ее в конце функции 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);
    }
}

Надеюсь, что это поможет.

Ответ 5

Ни один из ответов выше не помог мне, мне удалось reset локализовать приложение снова внутри onStop() метода активности, содержащей Webview

Ответ 6

Я хочу добавить еще один пример использования:

При возврате из операции веб-просмотра (то есть при отображении экрана оплаты и нажатии пользователем кнопки "назад") 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() базовой активности или, по крайней мере, в веб-просмотре.

Изменить: Если вы имеете дело с фрагментами, убедитесь, что этот метод вызывается при выходе пользователя из веб-просмотра.

Ответ 7

Если вы используете WebView только для отображения расширенного текста (текст с некоторыми абзацами или жирный и курсивный текст с разными размерами шрифтов), вы можете использовать TextView и Html.fromHtml(). TextViews не имеет проблем с настройками локали; -)

Ответ 8

Просто измените параметр для SEt Local Method с передачи BaseContext на "this" или "Точное действие", особенно на Android 7.0 и старше

Ответ 9

В Android N, когда вы выполняете new WebView(), он добавит /system/app/WebViewGoogle/WebViewGoogle.apk в путь к ресурсу, и если он не добавил к пути, это приведет к восстановлению ресурса.

Итак, если вы хотите решить вопрос, просто сделайте new WebView(application) в приложении до того, как вы измените локальный.

Если вы знаете китайский, вы можете прочитать этот блог.