Safe Java-JS WebView Bridge 是github上的一个开源库, 主要实现了对js和java调用的一些封装.
https://github.com/pedant/safe-java-js-webview-bridge
从Sample入手, 我们先看看js调用的基本例子
<button onclick="HostApp.toast('我是气泡');">测试</button>
可以看到js调用了一个简单的方法.
我们开始分析native code是如何做的, 先看看启动Activity都做了什么.
**WebActivity.java**
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
wv.setWebChromeClient(
new CustomChromeClient("HostApp", HostJsScope.class)
);
...
}
其实主要的实现就是设置了一个自定义的CustomChromeClient, 这个CustomChromeClient是继承了InjectedChromeClient, 例子中CustomChromeClient什么都没有做, 主要工作都是由InjectedChromeClient完成的.
**InjectedChromeClient.java**
public InjectedChromeClient (String injectedName, Class injectedCls) {
mJsCallJava = new JsCallJava(injectedName, injectedCls);
}
这里可以看到InjectedChromeClient初始化只是创建了一个JsCallJava对象,那么JsCallJava又是个什么鬼? 我们继续看看代码
public JsCallJava (String injectedName, Class injectedCls)
通过方法定义可以看到传的参数一个是注入名, 一个是Class, 猜想就是把Class的方法定义绑定到注入命.然后写入页面提供js调用. 来看看是不是这样
**JsCallJava.java**
public JsCallJava (String injectedName, Class injectedCls) {
try {
if (TextUtils.isEmpty(injectedName)) {
throw new Exception("injected name can not be null");
}
mInjectedName = injectedName;
mMethodsMap = new HashMap<String, Method>();
Method[] methods = injectedCls.getDeclaredMethods();
...
for (Method method : methods) {
String sign;
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(method)) == null) {
continue;
}
...
}
...
mPreloadInterfaceJS = sb.toString();
} catch(Exception e){
Log.e(TAG, "init js error:" + e.getMessage());
}
}
去掉了一些构建js的代码. 正如我们猜想就是通过遍历Class,支持public static方法. 然后把生成的js保存到mPreloadInterfaceJS变量中, 继续看下是哪里用到mPreloadInterfaceJS
**InjectedChromeClient.java**
@Override
public void onProgressChanged (WebView view, int newProgress) {
//为什么要在这里注入JS
//1 OnPageStarted中注入有可能全局注入不成功,导致页面脚本上所有接口任何时候都不可用
//2 OnPageFinished中注入,虽然最后都会全局注入成功,但是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长
//3 在进度变化时注入,刚好可以在上面两个问题中得到一个折中处理
//为什么是进度大于25%才进行注入,因为从测试看来只有进度大于这个数字页面才真正得到框架刷新加载,保证100%注入成功
if (newProgress <= 25) {
mIsInjectedJS = false;
} else if (!mIsInjectedJS) {
view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
mIsInjectedJS = true;
Log.d(TAG, " inject js interface completely on progress " + newProgress);
}
super.onProgressChanged(view, newProgress);
}
注释写的很明白了, 就不过多解释这部分了. 下面我们看看声明的java方法和生成的js代码
public static void testjs(String ssss) {}
我们声明了testjs的静态方法, 下面是生成的JS代码示例
javascript: (function(b) {
var a = {
queue: [],
callback: function() {
var d = Array.prototype.slice.call(arguments, 0);
var c = d.shift();
var e = d.shift();
this.queue[c].apply(this, d);
if (!e) {
delete this.queue[c]
}
}
};
a.testjs = function() {
var f = Array.prototype.slice.call(arguments, 0);
if (f.length < 1) {
throw "JSBridgeTest 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
}
}
var g = JSON.parse(prompt(JSON.stringify({
method: f.shift(),
types: e,
args: f
})));
if (g.code != 200) {
throw "JSBridgeTest call error, code:" + g.code + ", message:" + g.result
}
return g.result
};
Object.getOwnPropertyNames(a).forEach(function(d) {
var c = a[d];
if (typeof c === "function" && d !== "callback") {
a[d] = function() {
return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
}
}
});
b.JSBridgeTest = a;
})(window);
可以看到testjs 定义为一个function, 通过分析参数和参数类型生成e, f 两个数组, 通过调用prompt函数回调给natvie code,我们看下natvie code里的处理.
**InjectedChromeClient.java**
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(mJsCallJava.call(view, message));
return true;
}
可以看到这里就是通过前面构造函数里构造的mJsCallJava 的call 方法实际调用了java对应的方法.
未完待续--