开发者

How to call javascript from Android?

How do we call javascript from Android? I have this javascript library which I would like to use, I want to call the javascript function and pass the result value to the android java code. Haven't foun开发者_StackOverflow社区d the answer from now. i managed to call android code from javascript, but I want the other way around.


There is a hack:

  1. Bind some Java object so that it can be called from Javascript with WebView:

    addJavascriptInterface(javaObjectCallback, "JavaCallback")
    
  2. Force execute javascript within an existing page by

    WebView.loadUrl("javascript: var result = window.YourJSLibrary.callSomeFunction();
        window.JavaCallback.returnResult(result)");
    

(in this case your java class JavaObjectCallback should have a method returnResult(..))

Note: this is a security risk - any JS code in this web page could access/call your binded Java object. Best to pass some one-time cookies to loadUrl() and pass them back your Java object to check that it's your code making the call.


You can use Rhino library to execute JavaScript without WebView.

Download Rhino first, unzip it, put the js.jar file under libs folder. It is very small, so you don't need to worry your apk file will be ridiculously large because of this one external jar.

Here is some simple code to execute JavaScript code.

Object[] params = new Object[] { "javaScriptParam" };

// Every Rhino VM begins with the enter()
// This Context is not Android's Context
Context rhino = Context.enter();

// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
try {
    Scriptable scope = rhino.initStandardObjects();

    // Note the forth argument is 1, which means the JavaScript source has
    // been compressed to only one line using something like YUI
    rhino.evaluateString(scope, javaScriptCode, "JavaScript", 1, null);

    // Get the functionName defined in JavaScriptCode
    Object obj = scope.get(functionNameInJavaScriptCode, scope);

    if (obj instanceof Function) {
        Function jsFunction = (Function) obj;

        // Call the function with params
        Object jsResult = jsFunction.call(rhino, scope, scope, params);
        // Parse the jsResult object to a String
        String result = Context.toString(jsResult);
    }
} finally {
    Context.exit();
}

You can see more details at my post.


In order to match the method calls of the iOS WebviewJavascriptBridge ( https://github.com/marcuswestin/WebViewJavascriptBridge ), I made some proxy for the calls of register_handle and call_handle. Please note I am not a Javascript-guru therefore there is probably a better solution.

     javascriptBridge = (function() {
        var handlers = {};
        return {
            init: function () {
            },
            getHandlers : function() {
                return handlers;
            },
            callHandler : function(name, param) {
                if(param !== null && param !== undefined) {
                    JSInterface[name](param);
                } else {
                    JSInterface[name]();
                }
            },
            registerHandler : function(name, method) {
                if(handlers === undefined) {
                    handlers = {};
                }
                if(handlers[name] === undefined) {
                    handlers[name] = method;
                }
            }
        };
    }());

This way you can send from Javascript to Java calls that can have a String parameter

javascriptBridge.callHandler("login", JSON.stringify(jsonObj));

calls down to

@JavascriptInterface
public void login(String credentialsJSON)
{
    Log.d(getClass().getName(), "Login: " + credentialsJSON);
    new Thread(new Runnable() {
         public void run() {              
             Gson gson = new Gson();
             LoginObject credentials = gson.fromJson(credentialsJSON, LoginObject.class);
             SingletonBus.INSTANCE.getBus().post(new Events.Login.LoginEvent(credentials));
         }
    }).start();
}

and you can call back to Javascript with

javascriptBridge.registerHandler('successfullAuthentication', function () {
    alert('hello');
})

and

private Handler webViewHandler = new Handler(Looper.myLooper());

webViewHandler.post(
        new Runnable()
        {
            public void run()
            {
                webView.loadUrl("javascript: javascriptBridge.getHandlers().successfullAuthentication();"
            }
        }
);

If you need to pass a parameter, serialize to JSON string then call StringEscapeUtils.escapeEcmaScript(json), otherwise you get unexpected identifier: source (1) error.

A bit tacky and hacky, but it works. You just have to remove the following.

connectWebViewJavascriptBridge(function(bridge) {
}

EDIT:

in order to change the global variable to an actual property, I changed the above code to the following:

(function(root) {
    root.bridge = (function() {
        var handlers = {};
        return {
            init: function () {
            },
            getHandlers : function() {
                return handlers;
            },
            callHandler : function(name, param) {
                if(param !== null && param !== undefined) {
                    Android[name](param);
                } else {
                    Android[name]();
                }
            },
            registerHandler : function(name, method) {
                if(handlers === undefined) {
                    handlers = {};
                }
                if(handlers[name] === undefined) {
                    handlers[name] = method;
                }
            }
        };
    }());
})(this);

I got the idea from Javascript global module or global variable .


For a full implementation of JavaScript that doesn't require using a slow WebView, please see AndroidJSCore, which is a full port of Webkit's JavaScriptCore for Android.

UPDATE 2018: AndroidJSCore is deprecated. However, its successor, LiquidCore has all of the same functionality and more.

Calling functions from Android is very simple:

JSContext context = new JSContext();
String script =
  "function factorial(x) { var f = 1; for(; x > 1; x--) f *= x; return f; }\n" +
  "var fact_a = factorial(a);\n";
context.evaluateScript("var a = 10;");
context.evaluateScript(script);
JSValue fact_a = context.property("fact_a");
System.out.println(df.format(fact_a.toNumber())); // 3628800.0
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜