At the last meeting, AndroidDevs Meetup were made by several developers from the ICQ messenger team. My report was dedicated to Android WebView. For everyone who could not come to the meeting, I publish an article here based on the speech. I will go on tops, large strokes. Deep technical details and a lot of code will not give. If you are interested in the details, click the link at the end of the post to download the application, specially written as an illustration, and see everything with examples.
What is webview?
WebView is a component of the Android platform that allows you to embed web pages into Android applications. In essence, this is an embedded browser. Using WebView about a year ago, we decided to create an
ICQ Web API for integrating web applications into our instant messenger. What is a web application? In essence, this is an HTML page that contains JavaScript and runs inside ICQ. Using the ICQ Web API, web pages via JavaScript can give ICQ different commands, for example, to send messages, open a chat, etc.

Here is how it looks in ICQ. From the Applications item you can go to the list of applications. This is not yet a WebView, to get into it, you need to select one of the applications. Then we go directly to WebView, where the web application is downloaded from the network.
')
How does it work technically? WebView has the ability to inject Java code in JavaScript in a certain way. JavaScript can invoke the code that we wrote and provided to it. This is the opportunity on which the entire ICQ Web API is based.

Here it is shown that WebView is working inside ICQ, there is an injected Java class between them, and applications from the network are loaded into WebView.
So, JavaScript from WebView makes calls to ICQ ICQ code. There are a large number of different challenges, and during the development process, there were many problems associated with the operation of this mechanism, which I will discuss later.
Problems with WebView
After starting the download, it is usually necessary to monitor this process: find out whether the download was successful, whether there were redirects, track download time and other things. We will also talk about streams that use JavaScript and calls in Java, the mismatch between Java and JavaScript types, the behavior of Alerts in JavaScript, and the size of the transmitted data. The solution for these problems will also be described further.
WebView Basics
Briefly about the basics of WebView. Consider four lines of code:
WebView webView = (WebView) findViewById(R.id.web_view); webView.loadUrl("http://www.example.com"); webView.setWebViewClient(new WebViewClient() {…} ); webView.setWebChromeClient(new WebChromeClient() {…} );
Here we see that we get a WebView and load example.com into it by calling WebView.loadURL (). There are two important classes in Android: WebViewClient and WebChromeClient, which interact with WebView. What are they needed for? WebViewClient is required to control the page loading process, and WebChromeClient is required to interact with this page after it has been successfully loaded. Before the page is finished loading, WebViewClient works, and after it, WebChromeClient. As you can see in the code, in order to interact with the WebView, we need to create our own instances of these classes and pass them to WebView. Further, WebView, under certain conditions, causes different methods that we redefine in our instances, and so we will learn about events in the system.
The most important methods that WebView calls the WebViewClient and WebChromeClient instances we created:
WebViewClient | WebChromeClient |
---|
onPageStarted () | openFileChooser (), onShowFileChooser () |
shouldOverrideUrlLoading () | onShowCustomView (), onHideCustomView () |
onPageFinished (), onReceivedError () | onJsAlert () |
I will talk about the purpose of all these methods a little later, although much is already clear from the names themselves.
Control of page loading in WebView
After we give the WebView command to load the page, the next step is to find out the result of the execution: whether the page has loaded. From the point of view of official Android documentation, everything is simple. We have a WebViewClient.onPageStarted () method that is called when the page starts loading. In the case of a redirect, WebViewClient.shouldOverrideUrlLoading () is called, if the page is loaded - WebViewClient.onPageFinished (), if not loaded - WebViewClient.onReceivedError (). Everything seems logical. How does this actually happen?
EXPECTATION:
- onPageStarted → shouldOverrideUrlLoading (if redirect) → onPageFinished / onReceivedError
REALITY:
- onPageStarted → onPageStarted → onPageFinished
- onPageStarted → onPageFinished → onPageFinished
- onPageFinished → onPageStarted
- onReceivedError → onPageStarted → onPageFinished
- onReceivedError → onPageFinished (no onPageStarted)
- onPageFinished (no onPageStarted)
- shouldOverrideUrlLoading → shouldOverrideUrlLoading
In fact, everything is always different and depends on the specific device: onPageStarted (), onPageFinished () and other methods may be called twice, all methods may be called in a different order, and some may not be called at all. Especially often similar problems arise on Samsung and Google Nexus. This problem has to be solved by adding additional checks to our instance of the WebViewClient class. When it starts to work, we save the URL and then check that the download is made from that URL. If it is completed, then we check for errors. Since the code is large, I will not give it. I propose to look independently in the example, the link to which will be at the end.
Injecting Java code into javascript
Sample Java code: WebView webView = (WebView) findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new MyJavaInterface(), "test"); private class MyJavaInterface { @android.webkit.JavascriptInterface public String getGreeting() { return "Hello JavaScript!"; } }
JavaScript code example: <input value="Click" onclick="javascript:alert(test.getGreeting());"/>
Here is an example of injecting Java code into JavaScript. A short Java class, MyJavaInterface, is created, and it has a single getGreeting () method. Please note that this method is marked with the @JavaScriptInterface marking interface - this is important. By calling the WebView.addJavascriptInterface () method, we are forwarding this class to the WebView. Below we see how it can be accessed from JavaScript by calling test.getGreeting (). The important point here is the name test, which later in JavaScript will be used as an object through which you can make calls to our Java code.
If we set breakpoint on the return string “Hello JavaStript!” And see the name of the thread in which the call was received, what kind of stream will it be? This is not a UI stream, but a special Java Bridge thread. Therefore, if we want to manipulate the UI when calling some Java methods, then we need to ensure that these operations are passed to the UI stream — use handlers or any other method.
The second point: the Java Bridge stream cannot be blocked, otherwise JavaScript in WebView will simply stop working, and no user actions will have a response. Therefore, if you need to do a lot of work, tasks should also be sent to other threads or services.
Java type mismatch in javascript
When we call some methods written in Java and injected into JavaScript, as shown above, the problem of inconsistencies between Java and JavaScript types arises. This table lists the basic rules for mapping between type systems:
Java -> JavaScript | Javascript -> java |
---|
byte, short, char, int, long, float, double | Number | Number | Byte, short, int, long, float, double (not Integer, Byte, Short, Long, Float, Double and not char) |
boolean | Boolean | Boolean | boolean (not boolean) |
Boolean, Integer, Long, Character, Object | Object | Array, Object, Function | null |
String | String (Object) | String | String (not char []) |
char [], int [], Integer [], Object [] | undefined | undefined | null |
null | undefined | null | null |
The most basic thing to notice here is that object wrappers are not transmitted. And of all Java objects in JavaScript, only String is mapped. Arrays and null in java are converted to undefined in javascript.
With the transfer in the opposite direction, from JavaScript to Java, there are also nuances. If you call a method that has elementary types as parameters, then you can pass number there. And if among the parameters of the method there are not elementary types, but, say, object wrappers, such as Integer, then this method will not be called. Therefore, you only need to use Java elementary types.
Sizes of data transferred between java and javascript
Another major problem is the amount of data transferred between Java and JavaScript. If a sufficiently large amount of data is transferred (for example, pictures) from JavaScript to Java, then if an OutOfMemory error occurs, you will not be able to catch it. The app just crashes. Here is an example of what can be seen in logcat in this case:
Process com.estrongs.android.pop (pid 6941) has died Process com.google.android.youtube (pid 24613) has died Process kik.android (pid 3022) has died Process com.doeasyapps.optimize (pid 30743) has died Process com.sandisk.mz (pid 31340) has died WIN DEATH: Window{3fc4769b u0 mc.test/mc.test.MainActivity} WIN DEATH: Window{17a5c850 u0 mc.test/mc.test.DataSizeTestActivity} Process mc.test (pid 16794) has died Force removing ActivityRecord{215171e4 u0 mc.test/.DataSizeTestActivity t406}: app died, no saved state
As you can see, if OutOfMemory occurs in the application, then various other applications running on the device will crash. In the end, closing all that is possible, Android comes to our application, and, since it is in the foreground, closes it last. Once again I want to remind you that we will not receive any exception, the application will simply fall. To prevent this, it is necessary to limit the size of the transmitted data. Much depends on the device. On some gadgets, it turns out to transfer 6 megabytes, on some 2-3. For ourselves, we chose a limit of 1 megabyte, and this is enough for most devices. If you need to transfer more, then the data will have to be cut into chunks and transferred in parts.
Javascript alerts
By default, the Alert dialog in WebView does not work. If you load an HTML page with JavaScript and perform alert ('Hello'), then nothing happens. To make it work, you need to define your WebChromeClient instance, override the WebChromeClient.onJSAlert () method and call super.onJSAlert () in it. This is enough for the Alerts to work.
WebView webView = (WebView) findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true); webView.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(....) { return super.onJsAlert(view, url, message, result); } }
Handling device orientation changes
Another serious problem is related to portrait and landscape orientation. If you change the orientation of the device, then by default the Activity will be recreated. In this case, all the views that are attached to it will also be recreated. Imagine the situation: there is a WebView, which is loaded with some kind of game. The user reaches level 99, turns the device, and the WebView instance with the game is recreated, the page loads again, and it is again at the first level. To avoid this, we use manual processing to change the device configuration. In principle, this thing is known and described in the
official documentation . To do this, it is enough to register the parameter configChanges in the section Activate in AndroidManifest.xml.
<activity android:name=".WebViewActivity" android:configChanges="orientation|screenSize"> </activity>
This will mean that we ourselves handle the change of orientation in the activity. If the orientation changes, we receive a call to Activity.onConfigurationChange () and can change some resources programmatically. But usually only WebView itself stretched across the entire screen has activity with WebView, and there’s nothing to do there. It just redraws and everything continues to work normally. Thus, the installation of configChanges allows you to not recreate the Activity, and all the Views that are present in it will retain their state.
Fullscreen Media Player
If a media player is embedded in a web page, then often there is a need to ensure that it can work in full screen mode. For example, the
youtube media player
can work inside a web page in an iframe html tag , and it has a button for switching to full-screen mode. Unfortunately, in WebView by default it does not work. To make it work, you need to do a few manipulations. In the xml layout in which the WebView is located, we place an additional FrameLayout. This is a container that is stretched to full screen and in which the View with the player will be located:
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent"/> <FrameLayout android:id="@+id/fullscreen_container" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> </FrameLayout>
And then in our WebChromeClient instance we will override several methods:
public void onShowCustomView(View v, CustomViewCallback c) { mWebView.setVisibility(View.GONE); mFullScreenContainer.setVisibility(View.VISIBLE); mFullScreenContainer.addView(view); mFullScreenView = v; mFullscreenViewCallback = c; } public void onHideCustomView() { mFullScreenContainer.removeView(mFullScreenView); mFullscreenViewCallback.onCustomViewHidden(); mFullScreenView = null; mWebView.setVisibility(View.VISIBLE); mFullScreenContainer.setVisibility(View.GONE); }
The system calls WebChromeClient.onShowCustomView () when the user presses the full screen button on the player. onShowCustomView () takes a View, which the player itself represents. This View is inserted into the FullScreenContainer and is made visible, and the WebView is hidden. When the user wants to return from full-screen mode, the WebChromeClient.onHideCustimView () method is called and the opposite operation is performed: display the WebView and hide FullScreenContainer.
Input type = ”file”
Web developers know that this container is used on web pages so that the user can select a file and upload it to the server, or show it on the screen. For this container to work in WebView, we need to override the WebChromeClient.openFileChooser () method. In this method there is a certain callback to which you need to transfer the file selected by the user. There is no additional functionality by itself
/>
. The file selection dialog we need to provide. That is, we can open any standard Android picker in which the user selects the desired file, get it, for example, via onActivityResult (), and pass it to the callback of the openFileChooser () method.
JavaScript code example: <input type="file" onchange="onFaileSelected(event)"/>
Sample Java code: WebChromeClient myClient = new WebChromeClient() { @SuppressWarnings("unused") public void openFileChooser(ValueCallback<Uri> callback, String accept, String capture) { callback.onReceiveValue(Uri.parse("file://" + getFileFromSomeProvider())); } }; WebView webView = (WebView) findViewById(R.id.web_view); webView.setWebChromeClient(myClient);
Detecting network status in javascript
JavaScript has a useful
Navigator object. It has an onLine field showing the status of the network connection. If we have a network connection, in the browser this field is true, otherwise it is false. For it to work correctly inside WebView, you need to use the WebView.setNetworkAvailable () method. With it, we transmit the current network status, which can be obtained using a network broadcast receiver or in any other way that you track the network status in Android. Do it all the time. If the network status has changed, then you need to call WebView.setNetworkAvailable () again and transfer the actual data. In JavaScript, we will get the current value of this property through Navigator.onLine.
Code examples
github.com/mc-android-developer/mc.presentation.webviewQuestions and answers
Question: There is a
CrossWalk project - this is a third-party WebView implementation that allows using fresh Chrome on older devices. Do you have any experience, have you tried to embed it?
Answer: I have not tried. At the moment we support Android since the 14th version and no longer focus on older devices.
Q: How do you deal with artifacts that remain when drawing a WebView?
Answer: We are not fighting with them, we tried - it didn't work out. This does not happen on all devices. We decided that this is not so blatant a problem in order to spend more resources on it.
Question: Sometimes you need a WebView to attach to ScrollView. It is ugly, but sometimes it is required by task. This is discouraged, even banned somewhere, and after that shortcomings in work arise. But still, sometimes it has to be done. For example, if you draw WebView from above, and below it draw some kind of native component (which should be native according to the requirement), and all this should be done as a single ScrollView. That is, first the user would look at the whole page, and then, if he wanted to, he would add up to these native components.
Answer: Unfortunately, I cannot answer you, because I did not come across such a situation. It is quite specific, and it’s hard for me to imagine when to put a WebView in a ScrollView.
Question: There is a mail application. There is a cap on top with recipients and everything else. Even in this case, not everything will be smooth. WebView has big problems when it tries to determine its size inside ScrollView.
Answer: You can try to draw the designated part of the UI inside the WebView.
Question: That is, to completely transfer all the logic from the native part to the WebView and leave these containers?
Answer: Even, perhaps, the logic does not need to be transferred, it means the injection of Java classes. Logic can be left and invoked through the injected class. Only UI can be transferred to WebView.
Question: You mentioned the games in the messenger. Are they web applications?
Answer: Yes, these are web pages with javascript inside the webview.
Question: Do you do all this just to not rewrite the game natively?
Answer: And for that too. But the main idea is to allow third-party developers to create applications that can be integrated into ICQ, and with the help of this ICQ Web API to interact with the messenger.
Question: So, these games can also be played via a web browser on a laptop?
Answer: Yes. It can be opened in a web browser, and we sometimes even debug them right in it.
Question: And if Intent, let's say, throw this toy in Chrome, what problems will it have? If you do not write your WebView, and use the services?
Answer: The problem is that in our WebView we can provide an API through injecting a Java class, and using this API, the application can directly interact with ICQ, send it various commands. Suppose a command to receive a username, to receive chats that are open for him, to send chat messages directly from ICQ. That is, from Chrome to send messages directly to ICQ will not work. In our case, all this is possible.
Question: You mentioned that you are cutting data into pieces of one megabyte. How do you collect them then?
Answer: We are not doing this now, because we have no such need.
Question: Enough one megabyte?
Answer: Yes. If the pictures are bigger, then we try to squeeze them. I said that if such a need exists, then it may be a solution to cut and assemble later in Java.
Question: How do you ensure the safety of applications in the sandbox? Did I understand correctly that you need to call injected Java classes from a JavaScript application?
Answer: Yes.
Question: How will security be provided in this case, is access to some system functions prohibited?
Answer: Right now, since the system is still quite young, we mainly use our own web applications and we completely trust them. In the future, all applications that will come to us will be administered, the code will be viewed, for this a special Security Team is allocated. In addition, a special system of permissions will be created, without which applications will not be able to access any information that is critical for the user.