最近一直在准备找工作,看了很多公司的招聘介绍,有相当一部分直接写:熟悉 Hybrid App 开发加分!正好,我司开发的就有这种 Hybrid App——使用 WebViewJavascriptBridge 通信,前端封装一些常用方法调用。

      现在的 app 开发,已经不在是以前一样所以页面都是有原生开发,基于应用的更新上线繁琐,由于 H5 的易更新,易维护性, 现在很多应用都采用同 H5 页面混合开发模式,例如:淘宝、QQ、京东等等。下面就来看看 Native 和 H5 如何实现通信:

JSBridge是个啥

      直接来重点,记住:JSBridge 是一个很简单的东西,更多的是一种形式、一种思想,为了解决 H5 和 Native 的双向通信

      就像我们刚接触 ajax 时,也很懵逼。其实,他们俩个差不多,ajax 是浏览器和服务器通信的规范(暂且叫规范,像 CMD 规范一样,SeaJS 是它的一种实现方式), JSBridge 是 H5 和 Native 通信的规范。axios 是 ajax 通信的一种实现方式,WebViewJavascriptBridge(下文要说) 是 JSBridge 的一种实现方式。明白了这些,下面就很好理解了。

H5 和 Native 的双向通信通用方法

      H5通信方式和兼容性如下表所示。指的是借助 Native 的 webview 加载H5页面,H5 和 Native 之间通过注入API、URL拦截、全局调用等形式,实现消息通信。站在大厂的角度考虑,在实战的时候,会选择更兼容的方式。

H5调用Native方法

平台 方法 备注
Android shouldOverrideUrlLoading scheme拦截方法
Android addJavascriptInterface API
Android onJsAlert()、onJsConfirm()、onJsPrompt()
IOS 拦截URL
IOS JavaScriptCore API方法,IOS7+ 支持
IOS window.webkit.messageHandlers APi方法,IOS8+支持

1.注入 API 方式的主要原理:通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。

说白了就是,Native 往 window 对象挂对象或方法,让 H5 可以调 Native 的方法。具体挂的对象或方法是 Native 定义的,比如人家挂了个getName(arg),H5 调用就是window.getName(arg),当然调用时可以向 Native 传数据。

2.拦截 url scheme原理:先解释一下 url scheme: url scheme 是一种类似于 url 的链接,是为了方便app直接互相调用设计的,形式和普通的 url 近似,主要区别是 protocol 和 host 一般是自定义的,例如: httpsss://bridge_loaded/url?url=http://ymfe.tech,protocol 是 httpsss,host 则是 bridge_loaded。

拦截 url scheme 的主要流程是:**Web 端通过某种方式(例如 iframe.src)发送 url scheme 请求,之后 Native 拦截到请求并根据 url scheme(包括所带的参数)进行相关操作。 **

Native调用H5方法

平台 方法 备注
Android loadurl()
Android evaluateJavascript() Android 4.4 +
IOS(UIwebview) stringByEvaluatingJavaScriptFromString
IOS(UIwebview) JavaScriptCore IOS7+ 支持
IOS(Wkwebview) evaluateJavaScript:javaScriptString IOS8+支持

      相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。

Native 调用 JavaScript,其实就是执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。(闭包里的方法,JavaScript 自己都调用不了,更不用想让 Native 去调用了)

通信原理总结

      通信原理是 JSBridge 实现的核心,实现方式可以各种各样,但是万变不离其宗。这里,推荐的实现方式如下:

  • JavaScript 调用 Native 推荐使用 注入 API 的方式(iOS6 忽略,Android 4.2以下使用 WebViewClient 的 onJsPrompt 方式)。
  • Native 调用 JavaScript 则直接执行拼接好的 JavaScript 代码即可。

      说实话,作为一个前端开发,刚开始看了上面这些方法啥的,我是一脸懵*。毕竟是嵌套在人家 Native 里面,规则都是他们实现的,我们H5只能遵循这个规则去玩。但一定要理清他们约定的这套规则是如何通信的,才能保证我们的愉快的交流。

      通信的原理大概介绍完了,下面介绍实战中如何使用,帮我们更好的理解概念。补充一句,通信的实现方式有很多种,下面只是我司的实现方式:(没有 Native 代码,纯web前端角度)

H5 和 Native 通信实战

      因为很多地方需要用同样的方法,比如:关闭H5页面并吐司、上传图片、预览图片、右上角的“增加”(╋)按钮、...所以,我们把和原生通信的方法写在一个js 文件里,直接 export 导出,方便代码的复用。

// mob.js
// 判断是什么平台(设备)
var browser = {
versions:function(){
var u = navigator.userAgent, app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, // IE
presto: u.indexOf('Presto') > -1, // opera
webKit: u.indexOf('AppleWebKit') > -1, // webkit
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, // firefox
mobile: !!u.match(/AppleWebKit.*Mobile.*/), // mobile
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // iOS
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, // android or uc
iPhone: u.indexOf('iPhone') > -1 , // iPhone QQHD
iPad: u.indexOf('iPad') > -1, // iPad
webApp: u.indexOf('Safari') == -1,
teacherApp: u.indexOf('XRJ-Admin') > -1 // 教师端 原生在userAgent放不同的字符串代表不同app
guardianApp: u.indexOf('XRJ-Edu') > -1 // 家长端
};
}(),
language: (navigator.browserLanguage || navigator.language).toLowerCase()
} // 判断是否是移动设备打开。browser代码在下面
if(browser.versions.mobile) {
var ua = navigator.userAgent.toLowerCase();//获取判断用的对象
if (ua.match(/MicroMessenger/i) == "micromessenger") {
//在微信中打开
}
if (ua.match(/WeiBo/i) == "weibo") {
//在新浪微博客户端打开
}
if (ua.match(/QQ/i) == "qq") {
//在QQ空间打开
}
if (browser.versions.ios) {
//是否在IOS浏览器打开
}
if(browser.versions.android){
//是否在安卓浏览器打开
}
} else {
//是PC浏览器打开
} /**
* 我司通信实现方案:
* iOS端注入 WebViewJavascriptBridge 对象或者拦截 url scheme,下面的setupWebViewJavascriptBridge是固定写法
* 这是我司iOS的做法,也是iOS的通用做法。
*
* Android当然也可以这么做,如果他们这样实现,后面H5封装的函数只用写一套就可以适配2端了。
* 我司Android不是这么做的,是通过注入对象或方法实现的,所以下面要针对2端写不同代码。
*/
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
} // 关闭页面并吐丝
// 调用:pageClose(1, '操作成功)
function pageClose(code, msg = ''){
setupWebViewJavascriptBridge(function(bridge) {
bridge.callHandler('iOS_RESPONSE_CALL_BACK', {"code":code,"msg":msg}, function responseCallback(responseData) {
// 关闭页面完毕的回调函数,类似 ajax() 的 success: function(result) {}
// responseData:回调函数返回的数据,类似 success 里面的 result
});
});
if(browser.versions.android){
if(code == 1){
todo.closeWindow(0);
}else if(code == 401){
todo.refreshWebView;
return false;
};
if(msg) todo.showToast(msg);
}
} // 图片上传
// 调用:load()
function load(){
setupWebViewJavascriptBridge(function(bridge) {
bridge.callHandler('iOS_UPLOAD_PHOTO', [], function responseCallback(responseData) {
// 上传完毕回调函数
imgload(responseData,true); // H5 页面中定义的全局方法 window.imgload = function(data,flag) {}
})
});
if(browser.versions.android){
picture.showPictureDialog(); // webView.loadUrl("javascript:imgload");
}
} // 图片浏览 - data数据格式和原生商量好
// 调用:var imgData = {"position":position,"list":[imgsrc0,imgsrc1,imgsrc2]}; imgSee(imgData);
function imgSee(data){
setupWebViewJavascriptBridge(function(bridge) {
bridge.callHandler('iOS_PHOTO_BROWSER',data); // 调用 iOS 的 'iOS_PHOTO_BROWSER' 方法,同时传数据data
});
if(browser.versions.android){
data = JSON.stringify(data);
todo.startGallery(data); // 调用 Android 的 startGallery 方法,同时传数据data
}
} // 单个头部菜单,右上角的“新增”按钮。因为是原生组件,我们操作不到,所以需要初始化页面时往这个按钮上绑定js方法,以便我们之后操作。
// 调用: topMenu('╋',0) || topMenu('新增',0)
function topMenu(title, index){
setupWebViewJavascriptBridge(function(bridge) {
// js注册方法 'JS_MENU_ACTION' 给 iOS 调用 - 方法名 H5 决定
bridge.registerHandler('JS_MENU_ACTION', function(data, responseCallback) {
topMenuHandle(data,true); // H5 页面中定义的全局方法 window.topMenuHandle = function(data,flag) {}
responseCallback(data); // 做完后,告诉 iOS 一声
}) // 调 iOS 的 'iOS_MENU_JSON' 并传参
bridge.callHandler('iOS_MENU_JSON', [{"action":index,"title":title}]); // - 方法名 iOS 决定
});
if(browser.versions.android){
// topMenuHandle是往“新增”按钮绑定的js方法,
// index是触发这个函数时回传给js的数据,用来判定点击个哪个按钮,title是这个按钮的名字
// H5 把一切安排的明明白白的
var jsonStr = '[{"action":"javascript:topMenuHandle('+index+')","title":'+title+'}]'
menu.inflateMenu(jsonStr); // jsonStr必须是字符串
}
} // 多个头部菜单
// 调用:var titleData = [{'title':"添加行为",'src':base.config.imgHost+"/cs/img/icon_bullet_behavior_add.png"},{'title':"审核行为",'src':base.config.imgHost+"/cs/img/icon_bullet_behavior_review.png"}]
// topMenus(titleData)
function topMenus(data){
var dataList = [];
$.each(data,function(index,item){
dataList.push('{"title":"'+item.title+'","src":"'+item.src+'"'+(browser.versions.android?',"action":"javascript:topMenusHandle('+index+')"':'')+'}');
})
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('JS_MORE_MENU_ACTION', function(responseData) {
topMenusHandle(responseData,true);
})
bridge.callHandler('iOS_MORE_MENU', dataList)
});
if(browser.versions.android){
menu.inflateCustomMenu('['+dataList+']');
}
} // 导出
export {
browser,
load,
imgSee,
topMenu,
topMenus,
pageClose,
}

总结

      Hybrid是一种连接 H5 跟 Native 的思路,即可以快速迭代H5功能,又可以有NA的体验,是混合开发的典型开发模式。实践过程中需要根据业务形态模型来定制代码实现,注入时机也不是一成不变的可以根据业务形态来选择。

参考链接:

JSBridge实战

移动混合开发中的 JSBridge

WebViewJavascriptBridge详细使用

Android混合开发之WebViewJavascriptBridge实现JS与java安全交互

Hybrid App从概念到实战的更多相关文章

  1. hybrid app初体验,和react-native一起飞

    第一次启动了react-native的示例,今天主要把其中遇到的坑与解决的办法分享给大家.如有疏漏.错误还望指正. 首先还是要从hybrid app这个概念说起(如果对于这个过程不感兴趣的同学,可以直 ...

  2. Hybrid App开发实战

    Hybrid App开发实战 作者 李秉骏 发布于 九月 04, 2013 | [引言]近年来随着移动设备类型的变多,操作系统的变多,用户需求的增加,对于每个项目启动前,大家都会考虑到的成本,团队成员 ...

  3. 【Hybrid App】Hybrid App开发实战

    [引言]近年来随着移动设备类型的变多,操作系统的变多,用户需求的增加,对于每个项目启动前,大家都会考虑到的成本,团队成员, 技术成熟度,时间,项目需求等一堆的因素.因此,开发App的方案已经变得越来越 ...

  4. Android Hybrid App自动化测试实战讲解(基于python)

    1.Hybrid App自动化测试概要 什么是Hybrid App? Hybrid App(混合模式移动应用)是指介于web-app.native-app这两者之间的app,兼具“Native App ...

  5. [目录]hybrid app 开发实战(基于ionic,cordova,angularjs)

    序:为什么要写这个系列 第一章:hybrid app开发之技术选型 第二章:ionic,cordova,phonegap关系浅析 第三章:ionic环境搭建之windows篇 第四章:ionic环境搭 ...

  6. Hybrid App开发者一定不要错过的框架和工具

    最近开始给网站的移动版本做技术选型,发现了很多好玩的东西,写出来给大家分享下. ionicFramework 我是hybrid app的忠实粉丝和大力倡导者,从 新浪移动云开始就不断的寻找能帮助Web ...

  7. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  8. Hybrid App移动应用开发初探

    一.移动App类型及其优缺点 1.1 Native App Native App(原生App)是用原生语言(Object-C/Java/C#/....)开发,用户需要下载安装的手机应用. 优点是 可以 ...

  9. Hybrid App开发者一定不要错过的框架和工具///////////z

    ionicFramework 我是hybrid app的忠实粉丝和大力倡导者,从 新浪移动云开始就不断的寻找能帮助Web程序员开发出漂亮又好用的UI层框架.在历经了jqmobile.sencha to ...

随机推荐

  1. Qt实现长文件名(字符串)在QLabel中自适应缩短

    一.应用场景简述 当在有限宽度的QLable中显示很长的文件名/字符串时,超出QLabel宽度部分将不会显示,此时采取缩短文件名策略(也可实现为字符串滚动动画)可以缓解这一问题.在实现这一想法的过程中 ...

  2. uniDAC使用sqlite3作为 IOS APP 的数据库存在的问题

    前面有写,可以利用uniConnection的ForceCreateDatabase参数,强制建立sqlite本地数据库, uniConnection1.SpecificOptions.Values[ ...

  3. RocketMQ配置

    安装&配置 1.Clone&Build git clone -b develop https://github.com/apache/incubator-rocketmq.git cd ...

  4. SIP:用Riverbank的SIP创建C++库的Python模块(把自己的C++库包装成Python模块)

    我们发现PyQt做的Python版的PyQt是如此好用,如果想把自己的C++库包装成Python模块该如何实现呢? 这里介绍下用SIP包装C++库时值得参考的功能实现: 需要Python模块中实现C+ ...

  5. 很幽默的讲解六种Socket IO模型 Delphi版本(自己Select查看,WM_SOCKET消息通知,WSAEventSelect自动收取,Overlapped I/O 事件通知模型,Overlapped I/O 完成例程模型,IOCP模型机器人)

    很幽默的讲解六种Socket IO模型(转)本文简单介绍了当前Windows支持的各种Socket I/O模型,如果你发现其中存在什么错误请务必赐教. 一:select模型 二:WSAAsyncSel ...

  6. c#实现类似数据的行锁

    当我们有一些这样的需求,比如某个订单中下单,修改等等这些是单例执行的,不能同步操作,当然这样的情况你可以使用数据库的行锁来实现,但是我们代码里面实现的话 ,我们也要用到锁,大部分情况下我们使用lock ...

  7. Qt给应用程序添加版本信息(对rc文件的设置,可利用QT内置变量)

    作者:daodaoliang 时间:2016年7月11日16:12:09 版本:V 0.0.4 邮箱:daodaoliang@yeah.net 0. 环境说明 系统环境: win10 64位 Qt环境 ...

  8. Spring Boot从入门到实战:集成AOPLog来记录接口访问日志

    日志是一个Web项目中必不可少的部分,借助它我们可以做许多事情,比如问题排查.访问统计.监控告警等.一般通过引入slf4j的一些实现框架来做日志功能,如log4j,logback,log4j2,其性能 ...

  9. 【linux杂谈】跟随大牛进行一次服务器间通讯问题的排查

    发现应用记录日志内,出现网络访问延迟较大的情况. 此类问题较为常见,特别是之前参与辅助一个朋友项目运维的过程中,经常因为网络访问延迟较大,朋友认为是遭到了ddos攻击或者是cc攻击.网络访问延迟较大常 ...

  10. pytorch实现yolov3(3) 实现forward

    之前的文章里https://www.cnblogs.com/sdu20112013/p/11099244.html实现了网络的各个layer. 本篇来实现网络的forward的过程. 定义网络 cla ...