Android - язык WebView резко меняется на Android N
У меня есть многоязычное приложение с основным языком английский и дополнительный язык арабский.
Как описано в документации,
- я добавил
android:supportsRtl="true"
в манифесте. - Я изменил все свойства XML с
left
а такжеright
приписываетstart
а такжеend
соответственно. - Я добавил строки на арабском языке в
strings-ar
(и аналогично для других ресурсов).
Вышеуказанная настройка работает правильно. После изменения Locale
в ar-AE
, Арабский текст и ресурсы правильно отображаются в моей деятельности.
Тем не менее, каждый раз, когда я перехожу к
Activity
сWebView
и / илиWebViewClient
, язык, текст и направление макета внезапно возвращаются к устройству по умолчанию.
Дальнейшие подсказки:
- Это происходит только на Nexus 6P с Android 7.0. Все работает правильно на Android 6.0.1 и ниже.
- Резкий сдвиг в локали происходит только тогда, когда я
Activity
это имеетWebView
и / илиWebViewClient
(а у меня их несколько). Это не происходит ни на одном из других видов деятельности.
Android 7.0 имеет поддержку нескольких локалей, что позволяет пользователю установить более одной локали по умолчанию. Так что, если я установлю основной язык Locale.UK
:
Затем при переходе к
WebView
, язык меняется отar-AE
вen-GB
,
Изменения API Android 7.0:
Как указано в списке изменений API, в API 24 добавлены новые методы, относящиеся к локали:
Locale
:
Locale.getDefault(...)
Locale.setDefault(...)
Configuration
:
getLocales()
setLocales(...)
Однако я создаю свое приложение с 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.
13 ответов
Ответ Теда Хоппа сумел решить проблему, но он не ответил на вопрос,почему это происходит.
Причина в том, что изменения, внесенные вWebView
класс и его пакет поддержки в Android 7.0.
Фон:
андроида WebView
построен с использованием WebKit. Хотя изначально он был частью AOSP, начиная с KitKat было принято решение об отделении WebView
в отдельный компонент под названием Android System WebView. По сути, это системное приложение Android, которое предустановлено на устройствах Android. Он периодически обновляется, как и другие системные приложения, такие как Google Play Services и приложение Play Store. Вы можете увидеть его в списке установленных системных приложений:
Изменения в Android 7.0:
Начиная с Android N, приложение Chrome будет использоваться для рендеринга любых / всех WebView
в сторонних приложениях для Android. В телефонах с установленной ОС Android N приложение Android WebView System отсутствует вообще. На устройствах, которые получили OTA-обновление до Android N, веб-представление Android System отключено:
а также
Кроме того, была введена поддержка нескольких локалей с устройствами, имеющими более одного языка по умолчанию:
Это имеет важное значение для приложений с несколькими языками. Если ваше приложение имеет WebView
s, то они отображаются с помощью приложения 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
что вы хотите установить. Например, если вы хотите установить арабский язык / ОАЭ в качестве локали по умолчанию, вы должны передать new Locale("ar", "AE")
, Или, если вы хотите установить язык по умолчанию (т.е. Locale
это автоматически устанавливается операционной системой), вы должны пройти Locale.US
,
ШАГ 2:
Кроме того, вам нужно добавить следующую строку кода:
new WebView(this).destroy();
в onCreate()
вашей Application
класс (если он у вас есть) и везде, где пользователь может менять язык. Это позаботится обо всех видах крайних случаев, которые могут возникнуть при перезапуске приложения после изменения языка (вы могли заметить строки на других языках или с противоположным выравниванием после изменения языка на Activities
который имеет WebView
на Android 7.0++).
В качестве дополнения пользовательские вкладки Chrome теперь являются предпочтительным способом отображения веб-страниц в приложении.
Рекомендации:
1. Android 7.0 - изменения дляWebView
,
2. Понимание патчей безопасности для WebView и Android.
4. WebView: от "Powered by Chrome" до прямой Chrome.
5. Нуга WebView.
7. Тайны Android N: Часть 1. Система Android WebView теперь просто "Chrome"?,
Ваш код, кажется, устанавливает языковой стандарт в конфигурации для самого приложения (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()
) которые используют контекст.
Это имеет важное значение для приложений с несколькими языками. Если в вашем приложении есть WebView, они отображаются с помощью приложения Chrome. Поскольку Chrome сам по себе является приложением для Android, работающим в собственном изолированном процессе, он не будет привязан к языку, установленному вашим приложением. Вместо этого Chrome вернется к языку основного устройства. Например, предположим, что языковой стандарт вашего приложения установлен на ar-AE, а основной языковой стандарт устройства - en-US. В этом случае языковой стандарт Activity, содержащий WebView, изменится с ar-AE на en-US, и будут отображаться строки и ресурсы из соответствующих папок языкового стандарта. Вы можете увидеть мешанину из строк / ресурсов LTR и RTL в тех Activity, которые имеют WebView.
Решение:
Полное решение этой проблемы состоит из двух шагов:
ШАГ 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 по умолчанию, который вы хотите установить. Например, если вы хотите установить арабский язык / ОАЭ в качестве языкового стандарта по умолчанию, вы должны передать новый языковой стандарт ("ar", "AE"). Или, если вы хотите установить языковой стандарт по умолчанию (то есть языковой стандарт, который автоматически устанавливается операционной системой), вы должны передать Locale.US.
ШАГ 2:
Кроме того, вам необходимо добавить следующую строку кода:
new WebView(this).destroy();
в onCreate() вашего класса Application (если он у вас есть) и везде, где еще пользователь может менять язык. Это позаботится обо всех краевых случаях, которые могут возникнуть при перезапуске приложения после изменения языка (вы могли заметить строки на других языках или с противоположным выравниванием после изменения языка в действиях, которые имеют WebViews на Android 7.0++).
В качестве дополнения, пользовательские вкладки Chrome теперь являются предпочтительным способом отображения веб-страниц в приложении.
Прочитав все ответы, я обнаружил, что в каждом из них чего-то не хватает, поэтому вот решение, которое до сих пор работало для меня. Поскольку 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. Не все действия должны расширять класс выше, как отмечено в aswer, это излишнее и не обязательно. Эта проблема также не решается заменой WebView на вкладки Google Chrome или открытием внешнего браузера.
Если вам действительно нужно настроить свои ресурсы для настройки всего списка языков, а не только одного, то вам необходимо объединить это решение с решением по адресу https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce В моем случае это был не обязательно.
Я также не нашел необходимости вызывать новый WebView(this).destroy(); как отмечено в ответе здесь.
Я хочу добавить еще один вариант использования здесь:
При возврате из действия веб-просмотра (т. Е. Отображения экрана оплаты и нажатия пользователем кнопки "назад") 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() базовой активности или по крайней мере в активности веб-просмотра.
Изменить: Если вы имеете дело с фрагментами, убедитесь, что этот метод вызывается при выходе пользователя из веб-просмотра.
Я заметил, что это происходит только в первый раз, когда вы используете веб-представление в приложении, но этого не произойдет после этого (то есть, если язык изменен изнутри приложения, и вы снова открыли веб-представление или другое веб-представление, то на этот раз веб-просмотр не изменит язык обратно, как это было в первый раз. поэтому, исходя из этого, я сделал простое решение: я добавил веб-просмотр с видимостью, перешедшей в мое самое первое действие в приложении (или активность хоста, если вы используете приложение с одним действием с фрагментами), то я применяю свой языковой стандарт в методе onCreate этого действия после вызова setContentView. Таким образом, в моем 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
}
В Android N, когда вы делаете new WebView()
, это добавит /system/app/WebViewGoogle/WebViewGoogle.apk
к пути ресурса, и если он не был добавлен к пути, это приведет к воссозданию ресурса.
Так что если вы хотите решить вопрос, просто сделайте new WebView(application)
в приложении, прежде чем менять местный.
Если вы знаете китайский язык, вы можете прочитать этот блог.
Та же проблема здесь. У меня грязное, но простое решение.
Поскольку я вижу, что языковой стандарт по-прежнему хорош в функции 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 не имеют проблем с настройками локали;-)
Я столкнулся с той же проблемой во фрагменте. Чтобы решить эту проблему, снова установите язык в функции onDestroyView.
@Override
public void onDestroyView() {
Utils.setLanguage(requireActivity());
super.onDestroyView();
}
Я исправил проблему произношения, изменив язык HTML с помощью тега lang. Доступность языка HTML
Ни один из приведенных выше ответов мне не помог, мне снова удалось сбросить локаль приложения в методе onStop() действия, содержащего Webview
Просто измените параметр для локального метода SEt с передачи BaseContext на "this" или точное действие, особенно на Android 7.0 и старше