简体   繁体   中英

Android webview, loading javascript file in assets folder

I've seen this question has been asked a lot of times, but still can't manage to get my code working.

I want my webview to load some URL (say www.google.com) and then apply some javascript stored in assets/jstest.js , which contains the following:

function test(){
document.bgColor="#00FF00"; //turns to green the background color
}

And here's where I try to load the JS:

@Override  
public void onPageFinished(WebView view, String url){
    view.loadUrl("javascript:(function() { "
                + " document.bgColor='#FF0000';" //turns to red the background color
                + " var script=document.createElement('script'); "
                + " script.setAttribute('type','text/javascript'); "
                + " script.setAttribute('src', 'file:///android_asset/jstest.js'); "
                + " script.onload = function(){ "
                + "     test(); "
                + " }; "
                + " document.getElementsByTagName('head')[0].appendChild(script); "
                + "})()"); 
} 

I know the javascript here works because the background color actually turns to red, but for some reason it won't load jstest.js . I think the problem might be in file path (I'm certain every other line of the javascript code is correct), but it looks correct to me. And the file is in the right folder.

What am I missing?

EDIT :

Since WebResourceResponse class is available only with API Level 11, here's what I've figured out in the end.

public void onPageFinished(WebView view, String url){
        String jscontent = "";
        try{
            InputStream is = am.open("jstest.js"); //am = Activity.getAssets()
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String line;
            while (( line = br.readLine()) != null) {
                jscontent += line;
            }
            is.close(); 
        }
        catch(Exception e){}
        view.loadUrl("javascript:(" + jscontent + ")()"); 
    } 

with the jstest.js simply containing:

function() {
    document.bgColor="#00FF00";
}

I tried the same thing, loading a bookmarklet (the javascript code in your loadUrl() call) into a third-party page. My bookmarklet also depends on other assets (javascript and css files) which would not load with a file:///android_asset URL.

That's because the security context of the page is still that of, eg, http://www.google.com , and that's not allowed access to file: URLs. You should be able to see the errors if you supply/override a WebChromeClient.onConsoleMessage().

I ended up with a kludge where I changed the bookmarklet's asset references to a bogus URL scheme, like:

asset:foo/bar/baz.js

and added a WebViewClient.shouldInterceptRequest() override which looks for those and loads them from assets using AssetManager.open().

One thing I don't like about this kludge is that the asset: scheme is open to any third-party HTML/Javascript on any page my view loads, giving them access to my app's assets.

One alternative, which I didn't try, would be to embed the sub-assets in the bookmarklet using data: URLs, but that can get unwieldy.

I'd much prefer it if there was a way to manipulate the security context of just the JS bookmarklet I'm loading in loadUrl(), but I can't find anything like that.

Here's a snippet:

import android.webkit.WebResourceResponse;
...
    private final class FooViewClient extends WebViewClient
    {
    private final String bookmarklet;
    private final String scheme;

    private FooViewClient(String bookmarklet, String scheme)
        {
        this.bookmarklet = bookmarklet;
        this.scheme = scheme;
        }

    @Override
    public void onPageFinished(WebView view, String url)
        {
        view.loadUrl(bookmarklet);
        }

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url)
        {
        if (url.startsWith(scheme))
            try
                {
                return new WebResourceResponse(url.endsWith("js") ? "text/javascript" : "text/css", "utf-8",
                        Foo.this.getAssets().open(url.substring(scheme.length())));
                }
            catch (IOException e)
                {
                Log.e(getClass().getSimpleName(), e.getMessage(), e);
                }

        return null;
        }
    }

I think the iceam cream webview client of cordova does the very thing you want to do.

It would be nice if this was documented somewhere but, as far as I can see, it is not.

Take a look at cordova's android github: https://github.com/apache/incubator-cordova-android/blob/master/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java

Here is how i ended up doing it. I used the Content:// protocol and set up a contentprovider to handle returning a file descriptor to the system

Here is my fileContentProvider:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.IOUtils;


import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;

public class FileContentProvider extends ContentProvider {
    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) {

        Log.d("FileContentProvider","fetching: " + uri);

        ParcelFileDescriptor parcel = null;

        String fileNameRequested = uri.getLastPathSegment();
        String[] name=fileNameRequested.split("\\.");
        String prefix=name[0];
        String suffix=name[1];
       // String path = getContext().getFilesDir().getAbsolutePath() + "/" + uri.getPath();
        //String path=file:///android_asset/"+Consts.FILE_JAVASCRIPT+"

/*check if this is a javascript file*/

        if(suffix.equalsIgnoreCase("js")){
        InputStream is = null;
        try {
            is = getContext().getAssets().open("www/"+Consts.FILE_JAVASCRIPT);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }


        File file = stream2file(is,prefix,suffix);
        try {
            parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
        } catch (FileNotFoundException e) {
            Log.e("FileContentProvider", "uri " + uri.toString(), e);
        }
        }
        return parcel;
    }

    /*converts an inputstream to a temp file*/

    public File stream2file (InputStream in,String prefix,String suffix) {
        File tempFile = null;
        try {
            tempFile = File.createTempFile(prefix, suffix);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tempFile.deleteOnExit();

            FileOutputStream out = null;
            try {
                out = new FileOutputStream(tempFile);
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 

            try {
                IOUtils.copy(in, out);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        return tempFile;
    }


    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public int delete(Uri uri, String s, String[] as) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentvalues) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }
}

in the manifest i defined the provider:

<provider android:name="com.example.mypackage.FileContentProvider"
          android:authorities="com.example.fileprovider"
        />

Here is the javascript o inject into the webview:

webView.loadUrl("javascript:(function() { "

           + "var script=document.createElement('script'); "
           + " script.setAttribute('type','text/javascript'); "
           + " script.setAttribute('src', 'content://com.example.fileprovider/myjavascriptfile.js'); "
      /*      + " script.onload = function(){ "
           + "     test(); "
           + " }; "
      */     + "document.body.appendChild(script); "
           + "})();");

and here is the myjavascriptfile.js (as an example):

   function changeBackground(color) {
       document.body.style.backgroundColor = color;
   }

With the following two conditions given:

  • minSdkVersion 21
  • targetSdkVersion 28

I am able to successfully load any local asset (js, png, css) via the following Java code

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if (uri.getHost().equals("assets")) {
        try {
            return new WebResourceResponse(
                URLConnection.guessContentTypeFromName(uri.getPath()),
                "utf-8",
                MainActivity.this.getAssets().open(uri.toString().substring(15)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

And in the HTML code I can use

<link rel="stylesheet" href="https://assets/material.min.css">
<script src="https://assets/material.min.js"></script>
<script src="https://assets/moment-with-locales.min.js"></script> 
<img src="https://assets/stackoverflow.png">

In Java the following then also works (you'd also need to add a favicon.ico to the assets)

webView.loadUrl("https://assets/example.html");

Using https:// as the scheme allows me to load local assets from a page served via HTTPS without security issues due to mixed-content.

None of these require to be set:

webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setDomStorageEnabled(true);
webSettings.setAllowContentAccess(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);    

Maybe you could have assets as 'html/javascript templates'. You could combine different of these text sources and string logic to compose your desired html to be loaded into the WebViewer. Then, you use .loadData instead of .loadUrl

I'm using it on my own and it seems to work pretty well.

Hope it helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM