Android - WebView language changes abruptly on Android 7.0 and above
Solution 1
Ted Hopp's answer managed to solve the problem, but he didn't address the question of why this occurs.
The reason is the changes made to the WebView
class and its support package in Android 7.0.
Background:
Android's WebView
is built using 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. It is essentially an Android system app that comes pre-installed with Android devices. It is periodically updated, just like other system apps such as Google Play Services and the Play Store app. You can see it in your list of installed system apps:
Android 7.0 changes:
Starting with Android N, the Chrome app will be used to render any/all WebView
s in third-party Android apps. In phones that have Android N out-of-the-box, the Android WebView System app is not present at all. In devices that have received an OTA update to Android N, the Android System WebView is disabled:
and
Moreover, multi-locale support has been introduced, with devices having more than one default language:
This has an important consequence for apps that have multiple languages. If your app has WebView
s, then those are rendered using the Chrome app. Because Chrome is an Android app in itself, running in its own sandboxed process, it will not be bound to the locale set by your app. Instead, Chrome will revert to the primary device locale. For example, say your app locale is set to ar-AE
, while the primary locale of the device is en-US
. In this case, the locale of the Activity
containing a WebView
will change from ar-AE
to en-US
, and strings and resources from the corresponding locale folders will be displayed. You may see a mish-mash of LTR and RTL strings/resources on those Activity
s that have WebView
s.
The Solution:
The complete solution to this problem consists of two steps:
STEP 1:
First, reset the default locale manually in every Activity
, or at least every Activity
that has a 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());
}
Call the above method before calling setContentView(...)
in the onCreate()
method of all your Activities. The locale
parameter should be the default Locale
that you wish to set. For example, if you wish to set Arabic/UAE as the default locale, you should pass new Locale("ar", "AE")
. Or if you wish to set the default locale (i.e. the Locale
that is automatically set by the operating system), you should pass Locale.US
.
STEP 2:
Additionally, you need to add the following line of code:
new WebView(this).destroy();
in the onCreate()
of your Application
class (if you have one), and wherever else the user may be changing the language. This will take care of all kinds of edge cases that may occur on app restart after changing the language (you may have noticed strings in other languages or with the opposite alignment after changing the language on Activities
that have WebView
s on Android 7.0++).
As an addendum, Chrome custom tabs are now the preferred way of rendering in-app web pages.
References:
1. Android 7.0 - changes for WebView
.
2. Understanding WebView and Android security patches.
4. WebView: From "Powered by Chrome" to straight up Chrome.
5. Nougat WebView.
7. Android N Mysteries, Part 1: Android System WebView is just "Chrome" Now?.
Solution 2
Your code seems to be setting the locale in the configuration for the app itself (MyApplication.getInstance()
). However, you need to update the configuration for the activity context before inflating the activity's content view. I've found that modifying the app's context isn't enough (and, as it turns out, isn't even necessary). If I don't update each activity context, then the behavior is inconsistent across activities.
The way I approach this is to subclass AppCompatActivity
(or Activity
, if not using the compatibility library) and then derive all my activity classes from that subclass. Here's a simplified version of my code:
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;
}
}
}
Then I make sure to call super.onCreate(savedInstanceState)
in each subclass's onCreate()
method before calling any methods (such as setContentView()
) that use the context.
Solution 3
This has an important consequence for apps that have multiple languages. If your app has WebViews, then those are rendered using the Chrome app. Because Chrome is an Android app in itself, running in its own sandboxed process, it will not be bound to the locale set by your app. Instead, Chrome will revert to the primary device locale. For example, say your app locale is set to ar-AE, while the primary locale of the device is en-US. In this case, the locale of the Activity containing a WebView will change from ar-AE to en-US, and strings and resources from the corresponding locale folders will be displayed. You may see a mish-mash of LTR and RTL strings/resources on those Activitys that have WebViews.
The Solution:
The complete solution to this problem consists of two steps:
STEP 1:
First, reset the default locale manually in every Activity, or at least every Activity that has a 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());
}
Call the above method before calling setContentView(...) in the onCreate() method of all your Activities. The locale parameter should be the default Locale that you wish to set. For example, if you wish to set Arabic/UAE as the default locale, you should pass new Locale("ar", "AE"). Or if you wish to set the default locale (i.e. the Locale that is automatically set by the operating system), you should pass Locale.US.
STEP 2:
Additionally, you need to add the following line of code:
new WebView(this).destroy();
in the onCreate() of your Application class (if you have one), and wherever else the user may be changing the language. This will take care of all kinds of edge cases that may occur on app restart after changing the language (you may have noticed strings in other languages or with the opposite alignment after changing the language on Activities that have WebViews on Android 7.0++).
As an addendum, Chrome custom tabs are now the preferred way of rendering in-app web pages.
Solution 4
After reading all answers I found out that there is something missing in each one so here is the solution that worked for me so far. Since the WebView overrides the language configuration of the activity's context and application context, you must make sure each time this happens you call a method that resets those changes back. In my case I wrote following class that my activities which present this problem extend (those showing a 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);
}
}
}
}
The idea posted by @Tobliug to save the initial configuration before the WebView overrides it worked for me, in my particular case I found this to be more easy to implement than other solutions posted. Important is that the fix method gets called after exiting the WebView, e.g. when pressing back and in onStop. If the webView is shown in a dialog you must take care the fix method is called after dismissing the dialog, mostly in onResume and/or onCreate. And if the webView is directly loaded in onCreate of the Activity and not afterwards in a new fragment the fix must also be called directly after setContentView before the activity's title is set, etc. If the WebView is loaded inside a fragment in the activity, call the activity in onViewCreated of the fragment and the activity should call the fix method. Not all activities need to extend the class above as noted in an aswer, that's an overkill and not necessary. This issue also does not get solved replacing the WebView by Google Chrome Tabs or opening an external browser.
If you really need your ressources configuratoin to have the whole list of languages set and not only one, then you would need to merge this solution with the one at https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce In my case it was not necessary.
I also did not find necessary to call new WebView(this).destroy(); as noted in an answer here.
Solution 5
In Android N, when you do new WebView()
, it will add /system/app/WebViewGoogle/WebViewGoogle.apk
to resource path. And if it was not added to the path already, it will cause Resource recreate (only the first time you use WebView in the app).
So if you want to solve the problem, just do new WebView(applicationContext)
in Application#OnCreate()
before you change the Locale.
If you know Chinese, you can read this blog.
Related videos on Youtube
Yash Sampat
"I hear and I forget. I see and I remember. I do and I understand." - Confucius "The significant problems we face cannot be solved at the same level of thinking we were at when we created them." - Albert Einstein I'm an Android & Java engineer. I entered the world of computers working on embedded systems with Linux, C & C++. I am amazed by the intersection of technologies that come together in the mobile device space: embedded Linux, Android HAL, Java, C++, SQLite and so many others. I'm awed by the talent here; some of the greatest polymaths in the world are present on StackExchange. Like them, I'm trying to give a little bit back to the world that gave me so much.
Updated on July 05, 2022Comments
-
Yash Sampat almost 2 years
I have a multilingual app with primary language English and secondary language Arabic.
As described in the documentation,
- I have added
android:supportsRtl="true"
in the manifest. - I have changed all xml properties with
left
andright
attributes tostart
andend
respectively. - I have added Arabic language strings in
strings-ar
(and similarly for other resources).
The above setup works properly. After changing the
Locale
toar-AE
, Arabic text & resources are correctly displayed in my Activities.However, every time I navigate to an
Activity
with aWebView
and/or aWebViewClient
, the locale, text and layout direction abruptly revert to the device default.Further hints:
- This is occurring only on a Nexus 6P with Android 7.0. Everything works properly on Android 6.0.1 and below.
- The abrupt shift in locale happens only when I navigate to an
Activity
that has aWebView
and/or aWebViewClient
(and I have several). It does not occur on any of the other Activities.
Android 7.0 has multi-locale support, allowing the user to set more than one default locale. So if I set the primary locale to
Locale.UK
:Then on navigating to the
WebView
, the locale changes fromar-AE
toen-GB
.Android 7.0 API changes:
As indicated in the list of API changes, new methods pertaining to locale have been added to the following classes in API 24:
Locale
:Configuration
:However, I am building my app with API 23, and am not using any of these new methods.
Furthermore ...
The problem occurs on the Nexus 6P emulator as well.
To get the default locale, I am using
Locale.getDefault()
.-
To set the default locale, I am using the following code:
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()); }
Has anyone encountered this problem before? What is the reason for it, and how do I resolve this?
References:
1. Native RTL support in Android 4.2.
-
Blackbelt over 7 yearshonestly it looks like a bug on their side. Did you check the android bug track?
-
Ted Hopp over 7 yearsCan you post the
onCreate
(or other relevant code) of the activity that inflates or instantiates the webview? -
Ezio over 7 yearsSir, I am having a similar problem in my app and this method is not working, Can you please see my question and see what can be the issue? stackoverflow.com/questions/42105938/…
- I have added
-
Tal Kanel over 7 yearsIs there any Android open issue about this buggy behavior? seems like it's bug in the system/webView component should be fixed...
-
Yash Sampat over 7 years@TalKanel: It seems to arise from the difference in how web pages are now rendered in-app. I'm not sure if it's a bug, but rather a consequence of a new background process.
-
Tal Kanel over 7 yearsconsidering there is no possible work around this "consequence" - I would say it's a bug: You cannot use webView + multiple locales, something should be trivial in android 7. the solution you proposed is not taking care of all kind of edge cases. honsetly I'm struggling as we speak to find a bug free solution to this issue
-
Yash Sampat over 7 years@TalKanel: I'm sorry to hear that. Would you care to elaborate on the situations where it is failing, possibly in another question? I'd be happy to help if I can. I believe we managed to circumvent this problem completely, and we do an enormous amount of testing for all of our new features.
-
Tal Kanel over 7 yearsFirst of all, thanks for the good exploitation you posted. it helped a lot.. I think it would be great if you could share the exact code you added in the onCreate method.. I'm not sure what I sould pass to setLocale(Locale locale) method
-
Yash Sampat over 7 years@TalKanel: the
locale
parameter should be whatever the default locale is that you wish to set. I have edited my answer and included some examples. Please let me know if you want more examples or clarifications. -
Kaizie about 7 yearsThanks for the research and nice and detailed explanation. We faced the same problem and what we did to fix it is to call Locale.setDefault(...) every time we unload a WebView, so we make sure the locale stays set to the one we want.
-
cunhan.fch almost 7 yearsThank you for saving my pool English. @Brandon Minnick
-
Ben almost 7 yearsI think it is sufficient to overwrite
protected void attachBaseContext( Context newBase )
. -
David over 6 yearsThe only problem I have with this solution is that when opening a dialog from the activity where I apply this the strings in the dialog still present the problem, strings in the activity work correctly.
-
David over 6 yearsShould this "solution" currently work? Still getting the mixed languages! Also using chrome custom tabs the problem persists.
-
David over 6 yearsNot even forwarding the URL to the Chrome App itself solves this? How come?!! This is sooo bad!
-
David over 6 yearsAt the end it all depends on the personal constellation, onResume does not get called when navigating back from a fragment to the previous one, the important thing is to make sure the fix method gets called when you exit the view containing the webView, whatever it is.
-
David over 6 yearsBut this issue with Chrome happens starting with 7.0, so I think the check isAtLeastJellyBeanMR1 is unnecessary.
-
OMArikan about 6 yearsNeither only application context configuration enough nor activity context configuration enough if you open webView in your activity. Check out stackoverflow.com/a/9475663/3270968, gunhansancar.com/change-language-programmatically-in-android
-
Yash Sampat about 6 years@David: WebView from Android 7.0 onwards utilizes the Chrome App. Its the same whether you open it in WebView or Chrome. Also, re. your dialog, you're not setting the Locale correctly there. This has nothing to do with WebViews, and you'll have the same behavior for that dialog even on older versions of the OS.
-
sanjeeb about 5 yearsThe new WebView(this).destroy() could cause issues on API 28. How can you adress that
-
Yash Sampat about 5 years@sanjeeb: What kind of issues can occur on API 28? Can you please explain?
-
sanjeeb about 5 years@YS what about creatiion of multiple instances of webview in the same process? As per the link, will it be a good option? I was using Baidu notification service for my app and if you insert the above code, some services were hampered .
-
Yash Sampat about 5 years@sanjeeb: what you can do is, destroy & recreate the
Webview
s only on API 27 and below. For API 28, first check whether you are facing issues related toWebView
language change. I'm going to do the same. And thanks for pointing this out :) If this issue persists on API 28 (i.e. they haven't fixed this bug as yet) then we may have to find another way to bypass the foreground process creation and still refresh theWebView
s. -
Muhammed Yalçın Kuru over 4 years@YS createConfigurationContext & updateConfiguration methods makes same job. So Do i really implement both of it ?
-
raiym over 4 yearsThe downside of adding
new WebView(this).destroy();
intoApplication.onCreate()
cold start time increase for about 1 second on budget or old devices. -
Yash Sampat over 4 years@raiym: that's interesting, thank you for your feedback. One second, really?? That's too much. The delay cannot be entirely due to
new WebView(this).destroy()
though .... -
Mr R over 3 yearsHi @ChinmayHohanta please update your answer with a small quote that shows what to do, include the original reference goes missing.
-
Abir Hasan Shawon about 3 yearsAnyone using ViewBinding might still face problem with this code. What worked for me is calling setLocale() method between inflating and setContentView(). Like this: <pre> ActivityXYZBinding.inflate(layoutInflater) setLocale(locale) setConentView(root)<code>