如何从Android调用javascript?

Posted

技术标签:

【中文标题】如何从Android调用javascript?【英文标题】:How to call javascript from Android? 【发布时间】:2011-11-24 13:43:50 【问题描述】:

我们如何从 android 调用 javascript?我有这个我想使用的 javascript 库,我想调用 javascript 函数并将结果值传递给 android java 代码。从现在开始还没有找到答案。我设法从 javascript 调用 android 代码,但我想要相反的方式。

【问题讨论】:

只需在 code.google.com 上查看问题 WebView has no way to call JavaScript from Java 目前的解决方案是加载一个JavaScript URL,例如:webview.loadUrl("javascript:(function() " + "document.getElementsByTagName('body')[0].style。颜色 = '红色'; " + ")()");在某些方面,一旦你看到它,它看起来就非常优雅和明显,但它仍然不如直接执行 JavaScript 的方法那么优雅。然而,这确实表明实现这样的方法很容易。 【参考方案1】:

有一个hack:

    绑定一些 Java 对象,以便可以通过 WebView 从 Javascript 调用它:

    addJavascriptInterface(javaObjectCallback, "JavaCallback")
    

    通过

    在现有页面中强制执行javascript
    WebView.loadUrl("javascript: var result = window.YourJSLibrary.callSomeFunction();
        window.JavaCallback.returnResult(result)");
    

(在这种情况下,您的 java 类 JavaObjectCallback 应该有一个方法 returnResult(..)

注意:这是一个安全风险 - 此网页中的任何 JS 代码都可以访问/调用您绑定的 Java 对象。最好将一些一次性 cookie 传递给 loadUrl() 并将它们传回您的 Java 对象,以检查是您的代码进行调用。

【讨论】:

Android 开发者页面也谈到了这种安全风险。 -- 我想如果JavascriptInterface 只包含可以从JavaScript 访问的方法,这就是为什么在JellyBean (api 17) 中引入@JavascriptInterface 注释的原因? @peter-knego returnResult(..) 中的参数应该使用什么类型?比如我想返回htmlCollection类型的document.getElementsByClassName('someclass'),但是Java中没有这种类型?如果我只使用 String 或 Object 作为参数类型,它将返回 null 和“未定义”.. 我想知道这对我来说是否有点矫枉过正。我要做的就是在我的网站上解析一个 HTML 表格。我仍在寻找方法来做到这一点。我有一个将表格转换为 json 的 javascript 函数,我只需要一种在 java 中获取它的方法。【参考方案2】:

您可以使用Rhino 库在没有 WebView 的情况下执行 JavaScript。

Download Rhino首先解压,将js.jar文件放到libs文件夹下。它非常小,因此您不必担心您的 apk 文件会因为这个外部 jar 包而变得大得离谱。

这里是一些执行 JavaScript 代码的简单代码。

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();

您可以在my post查看更多详细信息。

【讨论】:

【参考方案3】:

为了匹配ios WebviewJavascriptBridge(https://github.com/marcuswestin/WebViewJavascriptBridge)的方法调用,我为register_handlecall_handle的调用做了一些代理。请注意,我不是 Javascript 专家,因此可能有更好的解决方案。

     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;
                
            
        ;
    ());

通过这种方式,您可以从 Javascript 发送可以具有字符串参数的 Java 调用

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

调用

@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();

你可以用

回调Javascript
javascriptBridge.registerHandler('successfullAuthentication', function () 
    alert('hello');
)

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

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

如果需要传递参数,序列化为JSON字符串然后调用StringEscapeUtils.escapeEcmaScript(json),否则会得到unexpected identifier: source (1)错误。

有点俗气和hacky,但它有效。您只需删除以下内容。

connectWebViewJavascriptBridge(function(bridge) 

编辑:

为了把全局变量改成实际的属性,我把上面的代码改成如下:

(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);

我的想法来自Javascript global module or global variable。

【讨论】:

【参考方案4】:

有关不需要使用慢速 WebView 的 JavaScript 完整实现,请参阅AndroidJSCore,它是适用于 Android 的 Webkit 的 JavaScriptCore 的完整端口。

2018 年更新:AndroidJSCore 已弃用。但是,它的继任者 LiquidCore 具有所有相同的功能,甚至更多。

从 Android 调用函数非常简单:

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

【讨论】:

嗨,埃里克。您还忘了提到它现在已弃用,取而代之的是 LiquidCore。

以上是关于如何从Android调用javascript?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Objective-C 中调用的 JavaScript 函数访问 response.body?

从 javascript 适配器调用 java 代码时出错

如何从java调用javascript函数?

JavaScript 函数调用

如何将 Bridge 消息从 Java (Android) 发送到 JavaScript

从 JavaScript 调用 Android 方法