Background
HTML-based Android apps typically load their bundled HTML files into webviews via special android_asset URLs. On Android 3.0+ and Android 4.0, if these android_asset URLs contain parameters (e.g. file:///android_asset/mypage.html?test=test) or anchors (e.g. file:///android_asset/mypage.html#test), the webview incorrectly displays a page not found error — even though the file exists. (UPDATE: This issue is finally fixed in Android 4.1).
This is known as Android Issue 17535 and it seriously breaks the webview. For app developers, this means not being able to use parameters or anchors in HTML builds. Needless to say, this is a major impediment. As of right now (May 2012), there is no workaround. Some suggest using HTML5 local storage to pass parameters, but that requires a lot of custom work and modifications to your HTML build just to support the Android platform.
If you are looking for a better way, then this article explains a workaround that fixes this issue completely and makes the webview work correctly with asset URLs that contain both parameters and anchors. The fix is provided as a JAR for easy inclusion into your projects.
How does it work?
This workaround extends the WebView and WebViewClient classes to create an assets cache in your app’s data directory and instruct the webview to load pages from this cache. This effectively side-steps the assets load issues, because the HTML files are now loaded from the cache.
With this workaround, whenever a new asset request is processed in the webview, the following things happen:
- The asset URL is translated into a corresponding cache URL. For example, file:///android_asset/test.html becomes file:///data/data/com.yourcompany.yourpackage/webviewfix/test.html.
- The asset file is copied from the assets directory to the cache directory.
- The webview gets redirected to the new file in the cache.
How do I use this in my app?
To include this workaround in your app follow these steps:
- Download a copy of the JAR from the following URL: https://github.com/bricolsoftconsulting/WebViewIssue17535Fix/raw/master/bin/webviewissue17535fix.jar
- Add the JAR to your project. How you do this will depend on the specific tools you use. For example, in Eclipse you can right click on your project in Package Explorer, select Properties from the context menu, select Java Build Path on the left hand side of the Properties dialog, click the Add External JAR button in the dialog and finally browse to and select the WebViewIssue17535Fix.jar file.
- Locate all WebView references in your XML layouts and change them to com.bricolsoftconsulting.webview.WebViewEx.
- When initializing each WebViewEx object in your code, provide a reference to a WebViewClientEx object:
mWebView = (WebViewEx) findViewById(R.id.webview); mWebView.setWebViewClient(new WebViewClientEx(WebViewActivity.this));
If you want to override the shouldOverrideUrlLoading and shouldInterceptRequest events in WebViewClient, you must ensure that your implementation calls the superclass implementation and does not interfere with its functioning. Sample code is posted below:
mWebView = (WebViewEx) findViewById(R.id.webview); mWebView.setWebViewClient(new WebViewClientEx(WebViewActivity.this) { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // Special handling for shouldOverrideUrlLoading // Make sure that we call the base class implementation and do not interfere // with the base class redirects boolean redirected = super.shouldOverrideUrlLoading(view, url); // Do your own redirects here and set the return flag if (!redirected) { // Redirect HTTP and HTTPS urls to the external browser if (url != null && URLUtil.isHttpUrl(url) || URLUtil.isHttpsUrl(url)) { view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); redirected = true; } } return redirected; } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { // Special handling for shouldInterceptRequest // Make sure that we call the base class implementation WebResourceResponse wrr = super.shouldInterceptRequest(view, url); // Do your own resource replacements here if (wrr == null) { // wrr = new WebResourceResponse... } return wrr; } });
- Make sure that your Android manifest declares a target SDK version of at least 11:
<uses-sdk android:targetSdkVersion="11" />
If using Eclipse, also make sure that you change the Android target in the project properties.
- Add the appropriate webview permissions to your manifest, as shown below:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Optionally, if you want to set the cache path to the SDCARD:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Conclusion
That’s it — once you add the fix to your project, this webview issue should be completely gone. Feel free to try our Github demo project and see for yourself.