在android hybrid app开发过程中,经常要面对的一个问题是java与js的通信。android程序是由dalvick虚拟机来运行,javascript是由webview的webkit引擎来解析执行,本质上应该是dalvick虚拟机的一部分,所以两者通信是要看android底层api留了多少口子出来。
一、js向java传递数据(js调用java)
1.android提供给开发者的是往js中注入java对象的方式,即java层调用addJavascriptInterface(Object javaObject,String name),js中使用window.name.javaObject中的方法进行调用。
官方api中给出的例子是
class JsObject {
@JavascriptInterface
public String toString() {
return "injectedObject";
}
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
这种方式需要注意有以下几点(取自官方文档):
This method can be used to allow JavaScript to control the host
application. This is a powerful feature, but also presents a
security risk for apps targeting JELLY_BEAN or earlier. Apps that
target a version later than JELLY_BEAN are still vulnerable if the
app runs on a device running Android earlier than 4.2. The most
secure way to use this method is to target JELLY_BEAN_MR1 and to
ensure the method is called only when running on Android 4.2 or
later. With these older versions, JavaScript could use reflection to
access an injected object’s public fields. Use of this method in a
WebView containing untrusted content could allow an attacker to
manipulate the host application in unintended ways, executing Java
code with the permissions of the host application. Use extreme care
when using this method in a WebView which could contain untrusted
content.
JavaScript interacts with Java object on a private, background
thread of this WebView. Care is therefore required to maintain
thread safety.
The Java object’s fields are not accessible.
For applications targeted to API level LOLLIPOP and above,
methods of injected Java objects are enumerable from JavaScript.
用汉语说,
1.JsObject.toString()是在js运行的线程(非UI线程)中同步执行,方法是可以带有返回值的(可以顺带带点java层数据回js层)。
3.对于漏洞,官方肯定是会在新的版本中做修复的。
官方对于API16以上的版本的处理是增加@JavascriptInterface注解,通过该注解标示被注入js中的java对象可以调用的方法,非被标示的方法是不可以被调用的,这样通过反射来执行恶意代码的路就走不通了。但是官方对于API16及以下的版本就不处理了。
4.那为了兼容API16及以下的版本,应该如何做呢?
通常有两种方法,
一种是js中调用iframe.src或iframe.href或window.open(‘www.example.com?body=loadurl’)等方式将数据放在url中,java层通过WebviewClient的shouldOverrideUrlLoading(Webview webview, String url)拦截url,实现js往java传递数据,github上的jsBridge项目使用的是iframe的src方式。
另一种是js中调用prompt()方法,java层通过WebChromeClient的onJsPrompt(Webview webview, String url, String message, String defaultValue, JsPromptResult result)的方式来获取数据。这两种方法都是异步方式,都是在UI线程中执行。
二、java向js传递数据(java调用js的方式)
当API小于19时使用Webview.loadUrl(“javascript:”+js),大于19时通过evaluateJavascript(String js,ValueCallback resultCallback)的方式。
三、Java和js交互有以下一些特点(取自百度经验):
1.Java 调用 js 里面的函数,速度并不令人满意,大概一次一两百毫秒吧,如果要做交互性很强的事情,这种速度会让人疯掉的。而反过来就不一样了, js 去调 java 的方法,速度很快,基本上 40-50 毫秒一次。所以尽量用 js 调用 java 方法,而不是 java 去调用 js 函数。
2.Java 调用 js 的函数,没有返回值,而 Js 调用 java 方法,可以有返回值。返回值可以是基本类型、字符串,也可以是对象。如果是字符串,有个很讨厌的问题,第 3 点我会讲的。如果是对象,这个对象会被转换为 js 的对象,直接可以访问里面的方法。但是我不推荐 java 返回给 js 的是对象,除非是必须。因为 js 收到 java 返回的对象,会产生一些交换对象,而如果这些对象的数量增加到了 500 或 600 以上,程序就会出问题。所以尽量返回基本数据类型或者字符串。
3.Js 调用 Java 的方法,返回值如果是字符串,你会发现这个字符串是 native 的,不能对它进行一些修改操作,比如想对它 substr ,取不到。怎么解决呢?转成 locale 的。使用 toLocaleString() 函数就可以了。不过这个函数的速度并不快,转化的字符串如果很多,将会很耗费时间。