1.前言
优美的js与原生的交互应该是像微信那样的,简洁方便,通俗易懂,网上看了很多博客,发现很多人都没说清楚明白,说的明白的又太抽象,又是画图又是理论的,弄到后面就作者一个人懂,一点也不适合小白,所以我特地写一篇,就这样。
2.效果说明
微信JSDK:比如一个选择图片的交互,人家是这么写的。
wx.chooseImage({
success: function (res) {
var tempFilePaths = res.tempFilePaths // tempFilePaths 的每一项是一个本地临时文件路径
}
})
那么我们也要做跟人家微信差不多的,至少给到前端同学调用的时候要跟微信一样,这样才叫优美。
需求:需要这么一个方法 scan 调用之后可以打开原生摄像头,扫描完成之后,返回给结果。
最终要求实现如下代码:
tommy.scan({
onSuccess: function(res) {
alert("扫描结果" + res);
}
});
如果你也想做成这样,请继续往下看,OR现在就可以离开,大家节约时间。用原生安卓为例子写,如果是IOS开发的同学你可以考虑要不要继续。
3.交互方式
具体的交互方式有两种,一种是注入,一种的loadurl的时候解析出来,不过网上一大堆,说的五花八门,那么我就用最简单的方式给小白们讲一下。
这是我早期的文章,大家可以喵喵,就跟网上那些乱七八糟的差不多,原理就那样,地址:链接
当然了这篇文章我觉得还可以,虽然看到后面蒙圈了,而且也没有实现优美的js调用方式,但是!阔以稍微借鉴一下,用来了解js跟原生是如何交互的。地址:链接
3.1)注入的方式
原生端的代码如下:
// 原生步骤很简单
// 写一个function注入到前端的js页面
// 1.定义一个scan的扫描方法
public class JavascriptInterface {
@android.webkit.JavascriptInterface
public void scan() {
// 这是一个 scan 方法
}
@android.webkit.JavascriptInterface
public void print() {
// 这是一个 print 方法
}
// ... 方法都在这里写
}
// 2.把这个方法注入到webView中的js里面,两部完成Native的注入定义
// 这里要注意,最好等页面加载完成,再注入
webView.addJavascriptInterface(new JavascriptInterface(), "tommy");
// 3.回调
// 当处理好 scan 的操作,原生再调用事先说好的方法 scanCall 这样,js端就能收到回调
mWebView.loadUrl("javascript:scanCallBack("+ res +")");
Js端调用方法如下:
<script>
window.tommy.scan();
// 事先定义好一个方法,给Native回调
// 比如说,摄像头拍好了,Native就调用这个方法,然后js就知道了
scanCallBack function(res) {
}
</script>
3.2)LoadUrl的方式
原生端的代码如下:
// 1.
// 这里代码我抄别人的了,懒得写了,因为Demo我用上面的方式
// 复写WebViewClient类的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
// 裁剪出来,拿到 method 判断是否是 scan,如果是,执行scan操作
// 按照预先设定好的协议解析url
// 执行JS所需要调用的逻辑
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// 通知前端,网页js加载完成
}
});
// 2.回调
// 当处理好 scan 的操作,原生再调用事先说好的方法 scanCall 这样,js端就能收到回调
mWebView.loadUrl("javascript:scanCallBack("+ res +")");
Js端调用方法如下:
<script>
// 调用
document.location = "js://webview?method=scan";
// 事先定义好一个方法,给Native回调
// 比如说,摄像头拍好了,Native就调用这个方法,然后js就知道了
scanCallBack function(res) {
}
</script>
4.机制
同步异步
我已经尽量把图画简单了 ^ - ^,下面上代码,让你们进一步理解。
同步:比如说调用打印方法,打完直接返回通知js
/**
* H5 访问 Android 回调 打印
*
* @param json
*/
@Override
public void print(String json) {
Log.e(TAG, "接收到js的数据:" + json);
try {
Toast.makeText(this, json, Toast.LENGTH_LONG).show();
mAgentWeb.getJsAccessEntrace().quickCallJs("window.tommy.printCallBack", "ok");
} catch (Exception e) {
mAgentWeb.getJsAccessEntrace().quickCallJs("window.tommy.printCallBack", "fail");
e.printStackTrace();
Log.e(TAG, "打印报错:" + e.getMessage());
}
}
异步:比如调用扫描方法,调用打印方法之后,没有马上能回调,连java这边都要异步去处理,什么时候返回取决于用户什么时候扫描,由startActivityForResult方法可以看出,回调到js的方法要写在Activity返回的监听里面。
@Override
public void scan() {
AndPermission.with(this)
.runtime()
.permission(Permission.CAMERA)
.onGranted(permissions -> {
Log.e(TAG, "成功获取到摄像头权限");
startActivityForResult(new Intent(this, QRCodeActivity.class), QRCodeActivity.REQUEST_CODE_QRCODE_RESULT);
})
.onDenied(permissions -> {
Log.e(TAG, "用户拒绝获取到摄像头权限");
})
.start();
}
// ...等Activity回调
mAgentWeb.getJsAccessEntrace().quickCallJs("window.tommy.scanCallBack", result);
5.开始讲解
其实前面讲了一大堆都是基础,现在才是正题,上面的简单实现,也就是网上一大堆人说的,至于怎么封装成优雅的调用方式,网上很少有人讲,讲的也是一大堆图,各种注册监听Handler,重点都漏完,竟讲那些老生长谈……Ok,回归正题。
原js代码:
<script>
window.tommy.scan();
// 事先定义好一个方法,给Native回调
// 比如说,摄像头拍好了,Native就调用这个方法,然后js就知道了
scanCallBack function(res) {
}
</script>
这代码我们是已经可以实现,Native扫描,然后扫描完成也能接收到扫描结果res,只是这样前端看上去不太好看,也不怎么友好。那么现在我们的问题是,把scanCallBack放进去到scan里面回调就行了,是其实聪明你也是这么想的,只是网上没人说而已,特别是js基础不太好的安卓同学 ^ - ^
啥也不说,直接上代码:
HTML代码:
<!DOCTYPE HTML><html lang="en">
<head>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="0">
<meta charset="UTF-8">
<title>Apache Tomcat Examples</title>
</head>
<button id="scan" onclick="scanQRC()" style="width: 600px;height: 100px;font-size: 60px;"> 扫描二维码 </button>
<button id="print" onclick="xReLoad()" style="width: 600px;height: 100px;font-size: 60px;margin-top: 60px;"> 重新加载 </button>
<script src="./tommy.js"></script>
<script>
function xReLoad() {
location.reload();
}
// 扫码
function scanQRC() {
tommy.scan({
onSuccess: function(res) {
alert("打印结果" + res);
}
});
}
</script>
</html>
Js代码:这一步网上鲜为人写,不知道为啥,这才是关键,下面这个类似于微信的Jsdk,你只要给你们前端的同学作为js引入,那么他的调用就可以非常的优美。 html引入: <script src="./tommy.js"></script>
// js 于 native 交互模块
window.tommy = {
isReady: false,
scallback: {},
onReady: function () {
tommy.isReady = true;
tommy.ready();
},
ready: function() {
},
scanCallBack: function(r) {
tommy.scallback(r);
},
scan: function(object) {
if (!tommy.isReady) return;
window.tommyApp.callFunction("scan","");
tommy.scallback = object.onSuccess;
},
};
这个时候小白跳出来说了,怎么是 window.tommyApp.callFunction("scan", ""); 跟上面不一样了,嗯。。是不一样,稍微改了点,我源码是这么写的懒得改了因为总不能每个方法都写一个,肯定要统一一下滴。
Android代码稍微改了下:根据传进来的方法名,去那啥,这样就不用写很多方法了。。。。
//定义h5要调用的本地方法
@JavascriptInterface
public void callFunction(String methodName, String json) {
switch (methodName) {
//回调方法名
case "scan":
if (interfaceCallback != null) {
interfaceCallback.scan();
}
break;
case "print":
if (interfaceCallback != null) {
interfaceCallback.print(json);
}
break;
}
}
6.总结
到此,讲解完毕,为表示诚意,附上所有源码:下载地址