自从有了Webview与JS交互,而我又使用过并且有了一定的理解,真心的体会到让开发者能在web页相关开发为所欲为。demo地址
image.png
比如,在一个活动页面,需要用户登陆后的userId,才能领取活动页面的奖品,怎么获取呢?这个时候,你可以通过js跳转到登录页,登陆成功带着userId回到活动页,就可以进行下一步动作了,美滋滋~
上面只是个简单的例子,本章的主角是DSbridge,不过,在这之前,我们先回顾下,在没封装的时候,Webview与JS交互的实现。(我之前一直在用的方式)
/**js调用android端的方法:**/
//主要看showLog这个方法
public void initView() {
webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(new TestEntity(),"test");
}
public classTestEntity {
@JavascriptInterface
public void showLog(String data) {
Log.i("android",data);
}
}
js调用showLog()方法:
function handleAndroidMethod() {
test.showLog('hhh');
}
android 调用js 的方法:
//js里面方法
function jsMethod(data) {
....方法实现
}
android这边这样调用:
webview.loadUrl('jsMethod('测试')')
很好,传统的是如上这样实现,我们回到DSbridge,DSbridge其实就是在这样的交互中封装了一下。那么在分析封装实现之前,我们来看看,DSbridge完成交互中的流程。(还是以分享的例子)
android需要处理的:
public void initView() {
webView= (DWebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setJavascriptInterface(newJsApiEntity(this));
}
public class JsApiEntity {
private Activity mActivity;
public JsApiEntity(Activity mActivity) {
this.mActivity= mActivity;
}
//for synchronous invocation 同步
@JavascriptInterface
String testSyn(JSONObject jsonObject)throwsJSONException {
return jsonObject.getString("msg") +"[syn call]";
}
//for asynchronous invocation 异步
//分享相关
@JavascriptInterface
void share(JSONObject jsonObject,CompletionHandler handler)throwsJSONException {
EventBus.getDefault().post(jsonObject,"SHOW_SHARE_DIALOG");
handler.complete("对应后台里面的flag");//回调数据给H5
}
}
js那边调用:
var title = "我是标题";
var content = "我是内容";
var url = "落地页的链接";
function share() {
dsBridge.call("share", {title: title,content:content,
imageUrl: "图片链接哟",url:url}, function(flag){alert(flag);})
}
很对,上面就是使用DSbridge之后的使用详情了。其实还是挺有意思的,只要调用dsBridge.call()就可以了,参数是之前定义的方法名称(比如 share)。
DSbridge做的事情,就是在js调用的原生代码时候做了封装,每次调用都是用dsBridge.call()来处理,然后具体的就根据参数来区分了。
我们来看看他的主要源码部分 DWebView.java:
void init() {
......
super.addJavascriptInterface(new Object() {
int i = 0;
@JavascriptInterface
@Keep
public String call(String methodName, String args) {
String error = "Js bridge method called, but there is not a JavascriptInterface object, please set JavascriptInterface object first!";
if(DWebView.this.jsb == null) {
Log.e("SynWebView", error);
return "";
} else {
Class cls = DWebView.this.jsb.getClass();
try {
boolean asyn = false;
JSONObject arg = new JSONObject(args);
final String callback = "";
Method e;
try {
callback = arg.getString("_dscbstub");
arg.remove("_dscbstub");
e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class, CompletionHandler.class});
asyn = true;
} catch (Exception var12) {
e = cls.getDeclaredMethod(methodName, new Class[]{JSONObject.class});
}
if(e == null) {
error = "ERROR! \n Not find method \"" + methodName + "\" implementation! ";
Log.e("SynWebView", error);
DWebView.this.evaluateJavascript(String.format("alert(decodeURIComponent(\"%s\"})", new Object[]{error}));
return "";
}
JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
if(annotation != null) {
e.setAccessible(true);
Object ret;
if(asyn) {
ret = e.invoke(DWebView.this.jsb, new Object[]{arg, new CompletionHandler() {
public void complete(String retValue) {
this.complete(retValue, true);
}
public void complete() {
this.complete("", true);
}
public void setProgressData(String value) {
this.complete(value, false);
}
private void complete(String retValue, boolean complete) {
try {
if(retValue == null) {
retValue = "";
}
retValue = URLEncoder.encode(retValue, "UTF-8").replaceAll("\\+", "%20");
String e = String.format("%s(decodeURIComponent(\"%s\"));", new Object[]{callback, retValue});
if(complete) {
e = e + "delete window." + callback;
}
DWebView.this.evaluateJavascript(e);
} catch (UnsupportedEncodingException var4) {
var4.printStackTrace();
}
}
}});
} else {
ret = e.invoke(DWebView.this.jsb, new Object[]{arg});
}
if(ret == null) {
ret = "";
}
return ret.toString();
}
error = "Method " + methodName + " is not invoked, since it is not declared with JavascriptInterface annotation! ";
DWebView.this.evaluateJavascript(String.format("alert(\'ERROR \\n%s\')", new Object[]{error}));
Log.e("SynWebView", error);
} catch (Exception var13) {
DWebView.this.evaluateJavascript(String.format("alert(\'ERROR! \\n调用失败:函数名或参数错误 [%s]\')", new Object[]{var13.getMessage()}));
var13.printStackTrace();
}
return "";
}
}
@JavascriptInterface
@Keep
public void returnValue(int id, String value) {
OnReturnValue handler = (OnReturnValue)DWebView.this.handlerMap.get(Integer.valueOf(id));
if(handler != null) {
handler.onValue(value);
DWebView.this.handlerMap.remove(Integer.valueOf(id));
}
}
@JavascriptInterface
@Keep
public void init() {
DWebView.this.injectJs();
}
}, "_dsbridge");
......
public void setJavascriptInterface(Object object) {
this.jsb = object;
}
}
很好,我们先看init方法(@Keep,其实就是为了不被混淆,不用在意),从
Class cls = DWebView.this.jsb.getClass()里面一开始就很明显的说出了意图,使用反射来处理,通过e = cls.getDeclaredMethod获取到this.jsb里面的方法,方法区分同步和异步,还有这个call方法是不是很眼熟?public String call(String methodName, String args) 就是js调用原生代码里面用到的,methodName就是需要调用的方法。
我们回到这个jsb ,其实就是通过setJavascriptInterface方法设置进来的,在上面例子里面其实就是我们定义的JsApiEntity对象,现在,通过反射,把需要调用的Method (e)获取到了。接下来肯定是通过注解来调用方法了,我们接着看:
JavascriptInterface annotation = (JavascriptInterface)e.getAnnotation(JavascriptInterface.class);
if(annotation != null) {'....''}
使用了@JavascriptInterface注解的,才会进入里面,里面其实就是通过e.invoke来执行js调用的方法了。