最近的APP的Webview新增功能,需要与APP进行交互,下面就记录一下其中涉及的技术以及遇到的一些问题。
一、概述
由于APP的快速迭代复杂需求,很多功能最好能够在不更新APP版本的情况下实现更新,所以就引入了WebView,WebView在APP中的大量使用已经成为了一个明显的趋势。以往Webview与APP的交互都是直接通过url让APP传参到Webview,下单时Webview再通过调用APP的方法触发下单,而现在随着页面功能越来越复杂,Webview需要接收的参数越来越多,如果一直通过url传参的话,后续难免会遇到长度超出限制和难以维护的问题,所以需要改用APP调用Webview中的方法进行传参。
二、Webview与APP交互方式
1.拦截跳转
拦截跳转是我们最常见的一种方式,也是最简单,最容易理解的一种。我们可以在UIWebView的代理方法中拦截每一个请求,如果是特殊的链接就可以做一些事情,比如跳转、执行某些方法等,嵌入页面不多的场景就比较适用。
Android就是直接用WebViewClient来实现就可以了,IOS的话UIWebView/WKWebView 都可以。采用这种方式的优点就是跨平台,Webview端的JS不需要关心是Android端还是iOS端,约定好跳转链接后就可以两端统一。还有就是简单易用。JS端只需要window.location=xxxx就好了,Android、iOS也仅仅只是拦截跳转,网上教程很多,不太容易出错。
不过拦截跳转的缺点也显而易见,就是有大量的硬编码代码,约定好的链接都是写死的。因为不支持return和callback,所以通信只能是单向的,也就是说没办法回调,只能做send操作,做不了get操作。
所以拦截跳转虽然很简单易用,但是在项目大了之后拦截跳转的代理方法中会有非常多的判断。冗余、可维护性差,硬编码重。
2.暴露Native对象供JS调用
这种方法其实就是APP端与Webview端约定好一个通用的方法,Webview通过调用这个约定好的方法向APP传参并触发相应的操作。
Android采用的是JavaScriptInterface,可以暴露一个Java的Object给WebView供JS调用。就是说JS可以调用Java对象的某些方法,也就是@JavascriptInterface注解修饰的方法。iOS的话采用的是JSContext,即JavaScriptContext。JSContext的原理就是iOS暴露出去一个遵守协议的对象给JS,JS可以直接调用该对象的public方法。
暴露Native对象供JS调用这种方法的优点是跨平台。JS端不需要关心是Android端还是iOS端,约定好跳转链接后就可以两端统一。移动端不需要再拦截跳转链接,硬编码减少。支持双向return。即JS调Native方法可以拿到return,Native调用JS也可以拿到return。JS端很容易就可以把交互相关的代码封装成一个lib,可以所有页面任意调用。
但是iOS端只支持UIWebView,而UIWebView的内存优化、加载速度等都没有WKWebView做得好,为了用这种交互方式而舍弃WKWebView,这不是一个很好的选择。Android API 16 之前还有安全性问题。只支持return,不支持callback。所以在异步调用的时候拿不到返回值,比如我要JS调起APP的登录,登录完成后把用户信息返回给JS,这是一个很常规的需求,但这种交互方式却是做不到的。JS端调用很方便,但是APP端的初始化会比较麻烦,特别是iOS。
3.JSBridge
JSBridge的基本原理就是以JavaScript引擎或Webview容器作为媒介,通过协定协议进行通信,实现Native端和Web端双向通信的一种机制。JavaScript是运行在单独的JS Context中(Webview容器、JSCore等),与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的双向通信,这种方式就是JSBridge。通过JSBridge,Web端可以调用Native端的Java接口,同样Native端也可以通过JSBridge调用Web端的JavaScript接口,实现彼此的双向调用。
其实Android和ios原生都不支持JSBridge,都需要引入第三方库来实现。在ios中UIWebView/WKWebView都可以,使用桥接的方式实现,需要引入WebViewJavascriptBridge,Android也是使用桥接的方式,需要引入JsBridge。
JSBridge这种方式在嵌入页面很多的场景同样适用,对团队的技术要求较高。同样可以跨平台。JS端不需要关心是Android端还是iOS端,约定好跳转链接后就可以两端统一。移动端不需要再拦截跳转链接,硬编码减少。支持双向callback,可以异步回调。JS端可以封装成lib,可以任意页面发起调用。安全性高。
JSBridge的缺点就是只支持callback,不支持return,使用起来会相对麻烦一些。JS、Android、iOS三端的初始化代码都很多、很复杂。出现错误排查起来比较麻烦。
三 、具体实现
在Webview往APP传参时,在IOS端我们采用的是WKWebView,这也是apple官方推荐使用的交互方式。Webview用JS调用的方式其实就是:window.webkit.messagehandlers..postMessage,使用起来比较简单,不支持callback回调。在Android端我们采用的是JavascriptInterface方式,JS调用方式:window.android.。
比如约定name为setName,则Webview端的具体代码如下:
function isAndroid() {
return userAgent.indexOf('Android') > -1 || userAgent.indexOf('Adr') > -1;
};
function isIOS() {
return !!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
};
let message = {
"aaa": 1,
"bbb": 2
};
if (isAndroid()) {
window.android.setName(message);
}
if (isIOS()) {
window.webkit.messageHandlers.setName.postMessage(message);
}
在APP往Webview传参时,IOS和Android 端采用的方法分别是WKWebView和JavascriptInterface,跟Webview往APP传参是一一对应的。先在Webview的JS中定义一个方法,然后APP去调用。
比如约定js方法为postName具体代码如下:
window.postName = function (info) {
//js获取到参数后执行xxx操作
}
IOS端代码大致为:
//jsmessage为js方法返回值,err为错误信息,postName为JS中的方法 ---- 为webview传递给JS的参数
webview.evaluateJavaScript("postName(\"xxxxx\")")
{ (jsmessage, err) in
print(jsmessage)
}
Android端代码大致为:
String js = "postName('xxxxxxx')";
mWebView.evaluateJavascript(js, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
// 这里可以处理被调用js方法的return
showNativeMessage("调用JS方法后得到的返回值是:" + s);
}
});
四、总结
Webview与APP交互方法很多,但是难度系数是不同的,我们需要根据相应的需求,结合时间成本、学习成本去选择最适合的解决方案。在Webview端,实际用到的代码并不多,关键就是要根据不同APP环境(Android/iOS)做出一些相应的适配;在传参的过程中,需要注意不同语言之间的数据类型差别,对不同的数据类型做出相应的转换。比如APP往Webview传参时,如果是参数是JSON对象,那么IOS端是可以直接传参给Webview的,而Android端传的是JSON对象的字符串,这就需要Webview端先判断传来的是字符串还是对象,并作出相应的数据类型转换。而APP中还需要注意调用JS方法的时机,如果JS还没加载完就调用,也是没有效果的,需要监听页面加载完成后才调用。还有个问题就是,由于Webview是嵌在APP中的,传统的网页调试方式比如alert和console在APP中一般都看不到,两端联调的时候比较麻烦。