78

I want to make a synchronous call to some Java code in my Android app.

I am using this solution: https://stackoverflow.com/a/3338656

My Java code:

final class MyWebChromeClient extends WebChromeClient {
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            Log.d("LogTag", message);
            result.confirm();
            return true;
        }
    }

My JavaScript code:

<html>
<script>
function java_request(){
    alert('test');
}
</script>
<body>
<h2>Welcome</h2>
<div id="area"></div>
<form>
<input type="button" value="java_call" onclick="java_request()">
</form>
</body>
</html>

When I tap on the java_call button, the button goes to the pressed state. I can see 'test' in the console log. Everything is normal until here.

The problem is, the button never gets back to its normal state. It stays in the pressed state. Maybe the JavaScript execution is broken or something?

Why does the button never return to its normal state?

Community
  • 1
  • 1
ozkolonur
  • 1,430
  • 1
  • 15
  • 22

3 Answers3

111

I don't think this is the best solution to get the javascript to execute java code. See here:

If you want to expose native code to the HTML to be callable via javascript, do the following around your web view declaration:

JavaScriptInterface jsInterface = new JavaScriptInterface(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(jsInterface, "JSInterface");

Declare the class JavaScriptInterface:

public class JavaScriptInterface {
    private Activity activity;

    public JavaScriptInterface(Activity activity) {
        this.activity = activity;
    }

    @JavascriptInterface
    public void startVideo(String videoAddress){
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.parse(videoAddress), "video/3gpp"); 
        activity.startActivity(intent);
    }
}

I am declaring a single function for playing a video, but you can do whatever you want.

Finally you call this in the WebView contents via simple javascript call:

<video width="320" height="240" controls="controls" poster='poster.gif'
       onclick="window.JSInterface.startVideo('file:///sdcard/test.3gp');" >
   Your browser does not support the video tag.
</video>

The example is taken from another answer of mine, about playing videos, but should be explaining enough.

EDIT As per @CedricSoubrie's comment: if the target version of the application is set to 17 or higher you need to add annotation @JavascriptInterface above each method you want to export to the web view.

Pablo Cegarra
  • 20,955
  • 12
  • 92
  • 110
Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • 1
    thanks for info, I saw this solution. since there is a bug in 2.3.X for this solution, I dont want to use it. workaround was very terrific. – ozkolonur May 02 '12 at 16:22
  • @ozkolonur I have been using this with all versions of Android 2.2+, never saw any bug. Can you share what it is. A link is also very welcome. – Boris Strandjev May 03 '12 at 06:54
  • I suppose 'playVideo' and 'startVideo' should be the same? – Maarten Dec 19 '12 at 12:53
  • 2
    @Maarten: actually this was not the only error in my answer. I have corrected it now. – Boris Strandjev Dec 19 '12 at 14:28
  • 2
    @BorisStrandjev Extremely clear answer ! Note that you must add the "@JavascriptInterface" annotation to any method that you want available from your JavaScript for version 17 or higher. Source : http://developer.android.com/guide/webapps/webview.html – CedricSoubrie Apr 21 '13 at 03:39
  • @CedricSoubrie: Just to be clear: the annotation is needed if the target version is 17 or higher (not the OS version of the phone). Btw this version did not exist in the time I posted my answer :) – Boris Strandjev Apr 22 '13 at 06:54
  • @BorisStrandjev: If the security concern is of high priority, then using JavaScriptInterface bridge is not the best solution. Instead using onJsAlert() or onConsoleMessage() for communicating from javascript to Android seems more trustworthy. Also, evaluateJavascript() can be used from API level19+ for the same functionality. – r.bhardwaj May 19 '14 at 07:32
  • @r.bhardwaj can you give some more details on that? Waht are the security risks in using this approach? Can you link to somewhere? – Boris Strandjev May 19 '14 at 07:33
  • @BorisStrandjev: http://www.rapid7.com/db/modules/exploit/android/browser/webview_addjavascriptinterface http://stackoverflow.com/questions/15736660/android-app-using-webview-javascript-what-can-be-security-concern – r.bhardwaj May 19 '14 at 07:35
  • @r.bhardwaj Thanks for the link. As far as I understand the risk exists in the cases you do not own all the content, this is especially when the `WebView` might host third party content which might abuse the jave of the application. Are the cases of entirely contained content safe you think? – Boris Strandjev May 19 '14 at 07:40
  • @BorisStrandjev: You are right that the risk largely involves loading of untrusted content in webview. And there would still be other possible attacks (e.g., proxy servers, evil JS served by those pages through ad networks or other third-party sources) into your webview. – r.bhardwaj May 19 '14 at 07:49
  • nice post..help me a lot – Dhina k Oct 07 '15 at 12:05
  • @simpleBob Is there a way I can retrieve events along with url to notify the web application from my mobile application of connection state whether its online or offline ,to turn off network calls when network is offline –  Jun 23 '16 at 20:35
  • @JusticeBauer I don't entirely understand the question. `BroadcastReceiver` can register change in network state. As per my anser above this can be propagated to the `WebView` – Boris Strandjev Jun 25 '16 at 19:27
  • I had to add `import android.webkit.JavascriptInterface;` – mikep Dec 11 '20 at 14:21
1

Your function returns 'true'. That makes the 'onclick' property of your HTML code equal to true, hence the button remains 'clicked.'

R. Maynard
  • 11
  • 1
1

The methods defined in the "YourJavaScriptInterface" class, don't forget to annotate each method that you want to expose with "@JavascriptInterface", otherwise the method won't be triggered.

For example, the below code is from the Google Cloud Print JavaScript interface for calls from a webview page:

        final class PrintDialogJavaScriptInterface {
        @JavascriptInterface
        public String toString() { return JS_INTERFACE; }

        @JavascriptInterface
        public String getType() {
            return cloudPrintIntent.getType();
        }

        @JavascriptInterface
        public String getTitle() {
            return cloudPrintIntent.getExtras().getString("title");
        }

        @JavascriptInterface
        public String getContent() {
            try {
                ContentResolver contentResolver = getActivity().getContentResolver();
                InputStream is = contentResolver.openInputStream(cloudPrintIntent.getData());
                ByteArrayOutputStream baos = new ByteArrayOutputStream();

                byte[] buffer = new byte[4096];
                int n = is.read(buffer);
                while (n >= 0) {
                    baos.write(buffer, 0, n);
                    n = is.read(buffer);
                }
                is.close();
                baos.flush();

                return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "";
        }

        @JavascriptInterface
        public String getEncoding() {
            return CONTENT_TRANSFER_ENCODING;
        }

        @JavascriptInterface
        public void onPostMessage(String message) {
            if (message.startsWith(CLOSE_POST_MESSAGE_NAME)) {
                finish();
            }
        }
    }
tyolab
  • 377
  • 3
  • 11