2

Using a technique borrowed from http://www.gutterbling.com/blog/synchronous-javascript-evaluation-in-android-webview/ we have successfully implemented a number of features within our app that allow our Android app to synchronously get data from a Webview.

Here's the example from gutterbling:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.util.Log;
import android.webkit.WebView;


/**
 * Provides an interface for getting synchronous javascript calls
 * @author btate
 *
 */
public class SynchronousJavascriptInterface {

/** The TAG for logging. */
private static final String TAG = "SynchronousJavascriptInterface";

/** The javascript interface name for adding to web view. */
private final String interfaceName = "SynchJS";

/** Countdown latch used to wait for result. */
private CountDownLatch latch = null;

/** Return value to wait for. */
private String returnValue;

/**
 * Base Constructor.
 */
public SynchronousJavascriptInterface() {

}


/**
 * Evaluates the expression and returns the value.
 * @param webView
 * @param expression
 * @return
 */
public String getJSValue(WebView webView, String expression)
{
    latch = new CountDownLatch(1); 
    String code = "javascript:window." + interfaceName + ".setValue((function(){try{return " + expression
        + "+\"\";}catch(js_eval_err){return '';}})());";    
    webView.loadUrl(code);

    try {   
                    // Set a 1 second timeout in case there's an error
        latch.await(1, TimeUnit.SECONDS);
        return returnValue;
    } catch (InterruptedException e) {
        Log.e(TAG, "Interrupted", e);
    }
    return null;

}


/**
 * Receives the value from the javascript.
 * @param value
 */
public void setValue(String value)
{
    returnValue = value;
    try { latch.countDown(); } catch (Exception e) {} 
}

/**
 * Gets the interface name
 * @return
 */
public String getInterfaceName(){
    return this.interfaceName;
    }
}

This JS Interface is used like this:

WebView webView = new WebView(context);
SynchronousJavascriptInterface jsInterface = new jsInterface();
webView.addJavascriptInterface(jsInterface, jsInterface.getInterfaceName());

String jsResult = jsInterface.getJSValue(webView, "2 + 5");

Despite working nicely in Android 4.0 - 4.4.4 this is not working for us with Android 5.0 (Lollipop).

It appears as though in Lollipop the JS executes after the latch countdown has completed, whereas previously it would return a value prior to the countdown completing.

Has something changed with the threads that JS in an Webview executes on? And is there any way that I can fix this without re-writing the large chunks of our app that depend on being able to call the JS synchronously?

Mike Fosker
  • 417
  • 4
  • 13

1 Answers1

6
 loadUrl("javascript:" + code)

don't work with API > 19, instead use:

 evaluateJavascript(code, null);

Or, you can improve your code to use the callback provided by evaluateJavascript, though.

Jiboo
  • 361
  • 4
  • 8
  • 1
    Hm, it doesn't work. See https://code.google.com/p/android/issues/detail?id=79924 I hope it will be fixed, but now there is no workaround. Any thoughts? – Roman Zhukov Dec 10 '14 at 09:11
  • @RomanZhukov Thanks for the link to that issue, from the changelog it appears that this issue has been fixed in Android 5.1 (and in the forthcomming Webview 40 release) I have not tested the old version of my app to verify that fact, but will do so. – Mike Fosker Mar 24 '15 at 15:29
  • @MikeFosker Did you get ever get this properly working? I'm running into a very similar sounding issue that I haven't been able to resolve. https://stackoverflow.com/questions/37475073/android-main-thread-blocking-webview-thread – KT_ Jun 27 '16 at 21:12