Issue with loading local javascript files inside a webview
Asked Answered
F

2

13

I had with an issue that has plagued me for days. It turned out that it was an Android glitch and has been submitted, confirmed, and hopefully will be fixed in a future release. Now I have found a solution that works for me, and will provide it below, however the solution is not perfect as it involves editing the phone gap source. Mainly my question is if someone can find a better solution to this issue.

The Bug:
There is a glitch when you attempt to load a page inside of a WebView on Android 3.0+. The glitch is that if that page references any local javascript files, you cannot append query data to the url. Basically

This works:
<script type="text/javascript" src="StaticJS.js"></script>

This does not work:
<script type="text/javascript" src="StaticJS.js?var=val"></script>

Why the hell would anyone want to do this since the file obviously can't do anything with the query vals? Well for me I have a phonegap application that loads a settings file via JSONP, however if a settings file is not specified it defaults to a local file. So yeah, the file can't process the query data but it would be nice to use the same file format and loading structure.


Solution 1 (Non-PhoneGap)

So there is an easy solution to this if the target android platform is 11(Honeycomb) or higher. (As long as you are careful and do not use any method that do not exists in any lower API levels this code will run on <11 apis, but you will still have to set 11 as your target)

Basically you add a WebViewClient to the WebView that utilizes the shouldInterceptRequest method to intercept the loading of local js files with query data attached.

import java.io.IOException;
import java.io.InputStream;

import android.content.res.AssetManager;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class PatchingWebViewClient extends WebViewClient{

    AssetManager am;

    public PatchingWebViewClient(AssetManager am){
        this.am = am;
    }

    @Override
    public WebResourceResponse shouldInterceptRequest (WebView view, String url){
        if(url.indexOf("file:///android_asset") == 0 && url.contains("?")){
            String filePath = url.substring(22, url.length());
            filePath = filePath.substring(0, filePath.indexOf("?"));
            try {
                InputStream is = am.open(filePath);
                WebResourceResponse wr = new WebResourceResponse("text/javascript", "UTF-8", is);
                return wr;
            } catch (IOException e) {
                return null;
            }
        }else{
            return null;
        }
    }

}

To set the WebViewClient, your code would look something like this:

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class CanWeBreakAWebViewActivity extends Activity {
    WebView mWebView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mWebView = (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setWebViewClient(new PatchingWebViewClient(this.getAssets()));
        mWebView.loadUrl("file:///android_asset/index.html");
    }
}

Solution 2 (PhoneGap)

Now for Phonegap I don't have a clean solution. My solution is to go and download the Phonegap source and edit the CordovaWebViewClient by adding the following method:

@Override
public WebResourceResponse shouldInterceptRequest (WebView view, String url){
    if(url.indexOf("file:///android_asset") == 0 && url.contains("?")){
        String filePath = url.substring(22, url.length());
        filePath = filePath.substring(0, filePath.indexOf("?"));
        try {
            InputStream is = ctx.getAssets().open(filePath);
            WebResourceResponse wr = new WebResourceResponse("text/javascript", "Cp1252", is);
            return wr;
        } catch (IOException e) {
            return null;
        }
    }else{
        return null;
    }
}

Solution 3 (Non-Existent)

This solution would hopefully be some easy to include class or tweak to the main activity so that you can use phone gap but could just use a .jar file of the code, making upgrades easier.

Fistula answered 15/2, 2012 at 22:13 Comment(0)
P
2

Thanks for this post, you clued me in to the latest solution which is simply to use IceCreamCordovaWebViewClient.

@Override
    public void init() {
    super.init(webView, new IceCreamCordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
}
Phantasy answered 5/6, 2013 at 18:36 Comment(0)
B
1

Query data is appended to javascript files (and other file types like css) to prevent browser caching. The query data is useless to the file but the browser treats it as new because the location is changed (in the eyes of the browser) and it loads a fresh copy.

I'm glad you found an answer to your problem, just thought I'd give my input as to why people use this method.

Bolter answered 15/2, 2012 at 22:19 Comment(2)
The issue here isn't browser caching, but the fact that the files don't load if you append query dataFistula
Understood. Just wanted to help out when I saw this part of your question: "Why the hell would anyone want to do this since the file obviously can't do anything with the query vals?"Bolter

© 2022 - 2024 — McMap. All rights reserved.