前言
为了方便业务的扩展以及快速的更新迭代,减少更新发布新版本的频率,现在越来越多的 App 内嵌了 web 网页,比如各大平台淘宝、京东、聚划算、灵机妙算等等,那么如何更好的利用两者间的通信呢?这其中自然也就涉及到了原生和网页之间的一些交互,比如支付,钱包,媒体拓展,图片处理,活动页面,用户地理位置网络状态等都能得到原生强有力的支持。
正文
1.andriod 与 javascript 间的交互
1.1 javascript 调用 andriod 代码
- 通过 WebView 的 addJavascriptInterface()进行对象映射
- 通过 WebViewClient 的 shouldOverrideUrlLoading () 方法回调拦截 url
- 通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调拦截 JS 对话框 alert()、confirm()、prompt() 消息
a.通过 WebView 的 addJavascriptInterface()进行对象映射
需要加载 JS 代码:javascript.html
1 | <!DOCTYPE html> |
定义一个与 JS 对象映射关系的 Android 类:AndroidtoJs
WebViewActivity.java
1 | private void initJsJava1() { |
该方法使用简单,仅需要仅将 Android 对象和 JS 对象映射即可,但存在严重的漏洞问题。漏洞产生原因是:当 JS 拿到 Android 这个对象后,就可以调用这个 Android 对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。如可以执行命令获取本地设备的 SD 卡中的文件等信息从而造成信息泄露。
b.通过 WebViewClient 的 shouldOverrideUrlLoading()方法回调拦截 url
原理:
- Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading ()拦截 url;
- 解析该 url 的协议;
- 如果检测到是预先约定好的协议,就调用相应方法。
JS 代码:javascript.html
1 | <!DOCTYPE html> |
当该 JS 通过 Android 的 mWebView.loadUrl(“file:///android_asset/javascript.html”)加载后,就会回调 shouldOverrideUrlLoading ()
MainActivity.java
1 | private void initJsJava2() { |
该方法不存在方式 1 所说的漏洞,但是对于 js 获取 andriod 代码的返回值会比较复杂,需要重新通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:
1 | // Android:MainActivity.java |
c.通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截 JS 对话框 alert()、confirm()、prompt() 消息
原理:
Android 通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截 JS 对话框,得到他们的消息内容,然后解析即可。
常用的拦截是:拦截 JS 的输入框(即 prompt()方法);因为只有 prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而 alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值。
javascript.html
1 | <!DOCTYPE html> |
当使用 mWebView.loadUrl(“file:///android_asset/javascript.html”)加载了上述 JS 代码后,就会触发回调 onJsPrompt()。
MainActivity.java
1 | private void initJsJava3() { |
三种方式的对比总结
a 方式方便简洁,安卓 4.2 以下存在漏洞,安卓 4.2 以上相对简单,互调场景
b 方式不存在漏洞问题,使用比较复杂,需要协议的约束,从 Native 层往 web 层传递比较复杂,不需要返回值的时候可用。
c 方式不存在漏洞问题,使用复杂,需要协议的约束,能满足大多数情况下的互调场景。
1.2 andriod 调用 javascript 代码
- 通过 WebView 的 loadUrl()
- 通过 WebView 的 evaluateJavascript()
a.通过 WebView 的 loadUrl()
需要加载 JS 代码:javascript.html
1 | // 文本名:javascript |
Android 代码:MainActivity.java
1 | private void loadurl() { |
b.通过 WebView 的 evaluateJavascript()
相较于第一种方式,这种会效率更高,使用起来更简洁
- 因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
- Android 4.4 后才可使用
需要加载 JS 代码:javascript.html
1 | // 文本名:javascript |
MainActivity.java
1 | @TargetApi(19) |
使用建议:两种方法混合使用,即 Android 4.4 以下使用方法 1,Android 4.4 以上方法 2
1 | // Android版本变量 |
2.OC 与 javascript 间的交互
iOS 的 webview 有 2 个类,一个叫 UIWebView,另一个是 WKWebView。两者的基础方法都差不多,本文重点是后者,他是取代 UIWebView 出现的,在 app 开发者若不需要兼容 ios8 之前版本,都应该使用 WKWebVIew。
2.1 javascript 调用 OC 代码
- 通过拦截 request 的方式间接实现(url 拦截)
- 使用 JavaScriptCore 的 jsContext 注册 objc 对象或使用 JSExport 协议导出 Native 对象的方式
a.通过拦截 request 的方式间接实现(url 拦截)
比如 linghit://。方法是在 html 或者 js 中,点击某个按钮触发事件时,跳转到自定义 URL Scheme 构成的链接,而 Objective-C 中捕获该链接,从中解析必要的参数,实现 JS 到 OC 的一次交互。比如页面中一个 a 标签,链接如下:
1 | <a href="linghit://smslogin?username=xiaoming&code=123456"> |
当用户点击 a 这个标签时,会被拦截
1 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { |
优点:泛用性强,可以配合 h5 实现页面动态化。比如页面中一个活动链接到活动详情页,当 native 尚未开发完毕时,链接可以是一个 h5 链接,等到 native 开发完毕时,可以通过该方法跳转到 native 页面,实现页面动态化。且该方案适用于 Android 和 iOS ,泛用性很强。
缺点:无法直接获取本次交互的返回值,比较适合单向传参,且不关心回调的情景,比如 h5 页面跳转到 native 页面等。
b.使用 JavaScriptCore 的 jsContext 注册 objc 对象或使用 JSExport 协议导出 Native 对象的方式
该方法需要在页面加载完成时,先获取 js 上下文。获取到之后,我们就可以进行强大的方法映射了。与上面介绍的安卓通过 WebView 的 addJavascriptInterface() 进行对象映射类同。有兴趣的小火棒课后可以了解。
2.2 OC 调用 javascript 代码
- 使用 evaluateJavaScript 方法
a.使用 evaluateJavaScript 方法
WKWebView 调用 js 方法和 UIWebView 类似,一个是 evaluateJavaScript,一个是 stringByEvaluatingJavaScriptFromString。获取返回值的方式不同,WKWebView 用的是回调函数获取返回值;而 stringByEvaluatingJavaScriptFromString 直接通过函数返回值,返回调用结果。
1 | //直接调用js |
总结
上文可知,其实 iOS 和 andriod 与 js 交互原理上是差不多的,实现上也是大同小异,现在更有一些第三方库解决了一些调用上的缺陷,比如 iOS 有 WebViewJavaScriptBridge 啊,andriod 有 JSBridge 等,有兴趣的同学可以去了解研究。。