JavascriptInterface与WebChromeClient.onJsPrompt()实现的Android Webview与js交互

在Android开发中,能实现Js调用Nactive,有4种方法:

1
2
3
4
JavascriptInterface
WebViewClient.shouldOverrideUrlLoading()
WebChromeClient.onConsoleMessage()
WebChromeClient.onJsPrompt()

这里我们讨论下第一种@JavascriptInterface注解实现,和第四种就是WebChromeClient.onJsPrompt() js注入的实现。下面来详细讲解一下二者的使用方式,原理,其他请阅读扩展JsBridge实现JavaScript和Java的互相调用-QQ音乐技术团队

@JavascriptInterface实现

实现步骤:

  • 设置WebView支持js脚本
  • 为提供给js调用的方法加上@JavascriptInterface注解
  • 给WebView添加js接口
1
2
3
4
5
6
7
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
  • 这种方式不安全,因为这个接口允许JavaScript控制宿主应用程序,在4.2的版本前存在重大安全隐患,JavaScript 可以使用反射访问注入webview的java对象的public fields,在一个包含不信任内容的WebView中使用这个方法,会允许攻击者去篡改宿主应用程序,使用宿主应用程序的权限执行java代码。因此4.2以后,任何为JS暴露的接口,都需要加JavascriptInterface。
  • 这种方式当有js回调函数需要android端执行时,都需要将匿名回调函数赋值给全局函数才能供android端回调,增加了js和android端通信的封装层的低效代码量。

Javascript注入实现

先来说说原理吧,当js调用prompt()方法时,WebChromeClient.onJsPrompt()方法会被触发,当js触发Android提供的接口方法时,将该方法的方法名称、参数类型、参数值转成json,然后通过prompt方法传递给android端,android端解析json并通过反射执行对应的方法,同时也支持执行匿名回调。
整个流程比较复杂,看图:

image
为WebView绑定WebChormeClient监听,在Html加载进度25%时进行js注入(注入的js是根据android提供给js的对象类名动态生成);
动态注入的js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
javascript: (function(b) {  
console.log("HostApp initialization begin");
var a = {
queue: [],
callback: function() {

var d = Array.prototype.slice.call(arguments, 0);//获取该函数参数并转换为Array数组
var c = d.shift();//取得数组第一个元素
var e = d.shift();
this.queue[c].apply(this, d);//新建一个对象 属性名称为取得的c,并将d数组作为他的值。然后将这个对象push到queue数组
if(!e) {//e为空的时候,将queue数组属性名称为c的对象删除
delete this.queue[c]
}
}
};
//各种赋值,最后都等于同一个函数
a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod = a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function() {
var f = Array.prototype.slice.call(arguments, 0);
if(f.length < 1) {
throw "HostApp call error, message:miss method name"
}
var e = [];
//此段判断,然后赋值
for(var h = 1; h < f.length; h++) {
var c = f[h];
var j = typeof c;
e[e.length] = j;
if(j == "function") {
var d = a.queue.length;
a.queue[d] = c;
f[h] = d
}
}
//将匿名对象{method: f.shift(),types: e,args: f}转换成json字符串并用浏览器弹出确认可输入框,然后取得输入框的值json序列化为js对象
var g = JSON.parse(prompt(JSON.stringify({
method: f.shift(),
types: e,
args: f
})));
if(g.code != 200) {
throw "HostApp call error, code:" + g.code + ", message:" + g.result
}
return g.result
};
//获取a的属性值,然后循环
Object.getOwnPropertyNames(a).forEach(function(d) {
var c = a[d];
//判断赋值
if(typeof c === "function" && d !== "callback") {
a[d] = function() {
//concat 连接两个数组
return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
}
}
});
b.HostApp = a;
console.log("HostApp initialization end")
})(window);//闭包函数默认执行,然后赋给window。这样window.b就可以执行了 b.HostApp就是执行a的内容,但是a具体处理逻辑不对外开放,避免外部污染a内部逻辑

代码不难,可以自行理解,其中回调函数被封装在了a对象里面,确保android端可以通过webview.loadUrl()执行回调。
android端回调js代码如下:

1
javascript:HostApp.callback(0, 0 ,"call back haha");

Android提供的每一个js方法都对应一个JsCallback对象,Android就可以通过JsCallback对象来生成并执行回调js的代码。
这种方式通过动态注入js的方式则非常方便,但也有一定限制,比如Android提供的方法必须是static修饰的,且方法第一个参数必须为WebView,不过这不影响使用。