I’ve been a little disappointed with my experience using Android WebViews. Perhaps naively, I expected WebViews by default to just work and behave the same way as the Webkit-based Browser app. I was wrong. Android WebViews seem to start out as very basic browser implementations and require you to manually enable or implement support for various behavior that you just expect from a browser.
It seems the default configuration of a WebView is to only support HTML rendering. JavaScript and plug-in support have to be enabled via a WebSettings object. This is easy enough to do but its something I would expect to be enabled by default.
I have encountered one bug with the order in which JavaScript and plug-ins are enabled. Enabling plug-ins before JavaScript, and and setting a WebView’s content via loadDataWithBaseURL spawned an instance of the Browser Activity for no apparent reason. The Browser displayed an error message about a missing webkit resource and you had to hit the back button to return to the Activity displaying the WebView. Enabling JavaScript before enabling plug-ins fixed the problem. So far I have only seen this bug on a Samsung Galaxy running 2.1 (Eclair). My Nexus One running 2.2 (Froyo) and emulators running 1.6 – 2.2 have not presented this bug.
The WebView has a nice feature where you can call setWebViewClient, pass it an implementation of WebViewClient and then be able to do things in response to the WebView starts and finishes loading content. The problem is that setting the WebViewClient breaks the way the WebView normally handles mailto: links and YouTube videos. I don’t know if this was intentional but it sure feels like a bug to me. Here’s how I dealt with these issues:
In your WebViewClient implementation, you must override shouldOverrideUrlLoading and examine the url passed to the method. If you detect the mailto: protocol you must then parse out the information for the email from the url’s query string. Finally create a new Intent to start an email Activity passing the information you parsed out of the query string as Bundle extras. Here’s an example:
HashMap<String,String>params = new HashMap<String,String>(); String[] arr = URLDecoder.decode(url).split("\\?"); arr = arr[1].split("&"); for(String pair : arr) { String[] arrp = pair.split("="); params.put(arrp[0], arrp[1]); } String bcc = (String)params.get("bcc"); String subject = (String)params.get("subject"); String body = (String)params.get("body"); Intent i = new Intent(Intent.ACTION_SEND); i.setType("text/html"); i.putExtra(Intent.EXTRA_CC, new String[]{bcc}); i.putExtra(Intent.EXTRA_SUBJECT, subject); i.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(body)); startActivity(Intent.createChooser(i, "Select email application."));
Just like with the mailto: links you must examine the url passed to shouldOverrideUrlLoading. If it is pointing to a YouTube video just invoke a new ACTION_VIEW intent passing it the URL. YouTube or the Browser application should handle the request.
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
In addition to urls, WebViews can load html formatted (or plain text for that matter) strings by calling either loadData or loadDataWithBaseURL. Now, word of advice: unless your content is strictly alpha-numeric don’t call loadData, use loadDataWithBaseURL instead. The loadData method has a bug with the way it encodes certain characters including including ‘ % & and @. If you call loadData with one of these characters in the string you passed you will see an error page instead of your content. The loadDataWithBaseURL method does not have this problem. For more information about this issue see: http://code.google.com/p/android/issues/detail?id=1733 and also http://code.google.com/p/android/issues/detail?id=1593.
Finally, let’s talk about Adobe Flash. Froyo users have the option of installing Adobe’s Flash Player on their phones. Yay! Flash on the Android, right? Works great in a browser but there is a big problem with the layout of Flash content in a WebView.
Flash content does not seem to respect the bounding box of its parent WebView. Suppose you have a layout where you have a
WebView positioned vertically between two other views, and all three views take up the full width of the screen. Now suppose the WebView has a lot of content and if you scroll down a bit you will see an embedded Flash movie. As you scrolling and you approach the Flash content you will see it come into view below the bounds of the WebView, overlapping and obscuring the bottom view. As you continue to scroll you will see the Flash content pass beyond the upper bounds of the WebView and overlap and obscure the top view. What you would expect to see is the Flash content to be rendered only within the bounds of the WebView.
Now I can’t say if this is an issue with Adobe’s Flash Player on the Android (I was using beta 3) or if this is a problem with the way that WebView’s handle plugins, but either way it is a problem that needs a fix.
So these are my gripes. What are some of yours?
The Flash Player going outside of the realms of the WebView is still a problem, do you know of any way to fix it? I’m on Android 2.2 using Flash 10.1 on an HTC Desire, if that helps at all.
I haven’t found a fix yet. My guess is we won’t see one until the release of Android 3.0
Do you know how can I turn the flash plugin off?
I don’t know about disabling individual plugins, but you can disable all plugins by setting the webview’s WebSettings.PluginState to OFF. Look here for more info: http://developer.android.com/reference/android/webkit/WebSettings.PluginState.html
You can kinda steal the default WebViewClient behavior from CallbackProxy.java. Make something like this:
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Taken from CallbackProxy.java
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// If another application is running a WebView and launches the
// Browser through this Intent, we want to reuse the same window if
// possible.
intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
try {
mContext.startActivity(intent);
return true;
} catch (ActivityNotFoundException ex) {
// If no application can handle the URL, assume that the
// browser can handle it.
}
return super.shouldOverrideUrlLoading(view, url);
}
Sorry the above code also needs to probably parse out http/https methods which you don’t want going to other apps such as the browser.
Ever got youtube / vimeo iframe embedding working in a webview?
Text selection… You would think The web people would implement actual text selection in their webview. After all, Apple’s iOS has the UIWebView where window.getSelection has meaning
Very frustrating.
another bug is that webviews cause activity memory leak