注:记录整理自https://www.cnblogs.com/yexiaochai/p/5813248.html https://www.cnblogs.com/dailc/p/5930231.html等文章中的内容。
app开发技术的对比和选择
当进入移动互联时代后,移动端设备开始成为流量的主要入口,app的重要性很快被凸显出来。多平台多终端的特点使得app的开发技术五花八门。多个方案的对比:
作为前端开发人员最容易上手的是第二、第三种方式,RN的方式也算hybird方式,但是和大部分的hybrid有区别,这里展开记录一些hybird开发的原理和方案。在我们使用Hybrid技术前要注意一个边界问题,什么项目适合Hybrid什么项目不适合,这个要搞清楚.
适合Hybrid的项目为:
1.有60%以上的业务为H5. | 2.对更新(开发效率)有一定要求的APP
不适合使用Hybrid技术的项目有以下特点:
1.只有20%不到的业务使用H5做. | 2.交互效果要求较高(动画多)
Hybrid技术的实现和选型
h5与Native交互有几种交互原理:
1.原生方法WebView.loadUrl等 | 2.URL Schema | 3.JavaScriptCore
而几种方式在使用上各有利弊,首先来说URL Schema是比较稳定而成熟的,会比较灵活;而从设计的角度来说JavaScriptCore似乎更加合理,但是我们在实际使用中却发现,注入的时机得不到保障。原生方法则有很多版本兼容问题。
大部分JSBridge的实现就是利用URL Schema实现的,基本原理是:
H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调H5的JSBridge对象传递回调。如下图
为什么要用URL Schema来实现JSBridge.
Native和原生之间有基本通信方式,既然Native和原生已经能够实现通信了,那为什么还要这种通过url scheme的JSBridge方式呢,原因大致如下:
1.Android4.4之前Native通过loadUrl来调用JS方法,只能让某个JS方法执行,但是无法获取该方法的返回值
2.Android4.2以下,addJavascriptInterface方式有安全漏掉
3.iOS7以下,JS无法调用Native
url scheme交互方式是一套现有的成熟方案,可以完美兼容各种版本,不存在上述问题.
另外,请注意,可以理解为JSBridge是一种交互理念,而上述的url scheme则是其中的一种实现,所以也就是说,就算后面实现变为了addJavascriptInterface,JavaScriptCore,也一样是JSBridge交互
url scheme实现JSBridge的原理
我们可以进行关键步骤分析:
第一步:设计出一个Native与JS交互的全局桥对象
第二步:JS如何调用Native
第三步:Native如何得知api被调用
第四步:分析url-参数和回调的格式
第五步:Native如何调用JS
第六步:H5中api方法的注册以及格式
JS部分的实现代码如下:
(function() { (function() { var hasOwnProperty = Object.prototype.hasOwnProperty; var JSBridge = window.JSBridge || (window.JSBridge = {}); //jsbridge协议定义的名称 var CUSTOM_PROTOCOL_SCHEME = 'CustomJSBridge'; //最外层的api名称 var API_Name = 'namespace_bridge'; //进行url scheme传值的iframe var messagingIframe = document.createElement('iframe'); messagingIframe.style.display = 'none'; messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + API_Name; document.documentElement.appendChild(messagingIframe); //定义的回调函数集合,在原生调用完对应的方法后,会执行对应的回调函数id var responseCallbacks = {}; //唯一id,用来确保每一个回调函数的唯一性 var uniqueId = 1; //本地注册的方法集合,原生只能调用本地注册的方法,否则会提示错误 var messageHandlers = {}; //实际暴露给原生调用的对象 var Inner = { /** * @description 注册本地JS方法通过JSBridge给原生调用 * 我们规定,原生必须通过JSBridge来调用H5的方法 * 注意,这里一般对本地函数有一些要求,要求第一个参数是data,第二个参数是callback * @param {String} handlerName 方法名 * @param {Function} handler 对应的方法 */ registerHandler: function(handlerName, handler) { messageHandlers[handlerName] = handler; }, /** * @description 调用原生开放的方法 * @param {String} handlerName 方法名 * @param {JSON} data 参数 * @param {Function} callback 回调函数 */ callHandler: function(handlerName, data, callback) { //如果没有 data if(arguments.length == 3 && typeof data == 'function') { callback = data; data = null; } _doSend({ handlerName: handlerName, data: data }, callback); }, /** * @description 原生调用H5页面注册的方法,或者调用回调方法 * @param {String} messageJSON 对应的方法的详情,需要手动转为json */ _handleMessageFromNative: function(messageJSON) { setTimeout(_doDispatchMessageFromNative); /** * @description 处理原生过来的方法 */ function _doDispatchMessageFromNative() { var message; try { if(typeof messageJSON === 'string'){ message = JSON.parse(messageJSON); }else{ message = messageJSON; } } catch(e) { //TODO handle the exception console.error("原生调用H5方法出错,传入参数错误"); return; } //回调函数 var responseCallback; if(message.responseId) { //这里规定,原生执行方法完毕后准备通知h5执行回调时,回调函数id是responseId responseCallback = responseCallbacks[message.responseId]; if(!responseCallback) { return; } //执行本地的回调函数 responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else { //否则,代表原生主动执行h5本地的函数 if(message.callbackId) { //先判断是否需要本地H5执行回调函数 //如果需要本地函数执行回调通知原生,那么在本地注册回调函数,然后再调用原生 //回调数据有h5函数执行完毕后传入 var callbackResponseId = message.callbackId; responseCallback = function(responseData) { //默认是调用EJS api上面的函数 //然后接下来原生知道scheme被调用后主动获取这个信息 //所以原生这时候应该会进行判断,判断对于函数是否成功执行,并接收数据 //这时候通讯完毕(由于h5不会对回调添加回调,所以接下来没有通信了) _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData }); }; } //从本地注册的函数中获取 var handler = messageHandlers[message.handlerName]; if(!handler) { //本地没有注册这个函数 } else { //执行本地函数,按照要求传入数据和回调 handler(message.data, responseCallback); } } } } }; /** * @description JS调用原生方法前,会先send到这里进行处理 * @param {JSON} message 调用的方法详情,包括方法名,参数 * @param {Function} responseCallback 调用完方法后的回调 */ function _doSend(message, responseCallback) { if(responseCallback) { //取到一个唯一的callbackid var callbackId = Util.getCallbackId(); //回调函数添加到集合中 responseCallbacks[callbackId] = responseCallback; //方法的详情添加回调函数的关键标识 message['callbackId'] = callbackId; } //获取 触发方法的url scheme var uri = Util.getUri(message); //采用iframe跳转scheme的方法 messagingIframe.src = uri; } var Util = { getCallbackId: function() { //如果无法解析端口,可以换为Math.floor(Math.random() * (1 << 30)); return 'cb_' + (uniqueId++) + '_' + new Date().getTime(); }, //获取url scheme //第二个参数是兼容android中的做法 //android中由于原生不能获取JS函数的返回值,所以得通过协议传输 getUri: function(message) { var uri = CUSTOM_PROTOCOL_SCHEME + '://' + API_Name; if(message) { //回调id作为端口存在 var callbackId, method, params; if(message.callbackId) { //第一种:h5主动调用原生 callbackId = message.callbackId; method = message.handlerName; params = message.data; } else if(message.responseId) { //第二种:原生调用h5后,h5回调 //这种情况下需要原生自行分析传过去的port是否是它定义的回调 callbackId = message.responseId; method = message.handlerName; params = message.responseData; } //参数转为字符串 params = this.getParam(params); //uri 补充 uri += ':' + callbackId + '/' + method + '?' + params; } return uri; }, getParam: function(obj) { if(obj && typeof obj === 'object') { return JSON.stringify(obj); } else { return obj || ''; } } }; for(var key in Inner) { if(!hasOwnProperty.call(JSBridge, key)) { JSBridge[key] = Inner[key]; } } })(); //注册一个测试函数 JSBridge.registerHandler('testH5Func', function(data, callback) { alert('测试函数接收到数据:' + JSON.stringify(data)); callback && callback('测试回传数据...'); }); /* ***************************API******************************************** * 开放给外界调用的api * */ window.jsapi = {}; /** ***app 模块 * 一些特殊操作 */ jsapi.app = { /** * @description 测试函数 */ testNativeFunc: function() { //调用一个测试函数 JSBridge.callHandler('testNativeFunc', {}, function(res) { callback && callback(res); }); } }; })();
方便快捷的cordova
cordova是hybird开发技术中对前端最友好的技术栈。自从cordova从PhoneGap分离出来后,它更加灵活,不管你用什么js框架开发h5,都可以和cordova集成开发app。国内也有很多基于cordova封装的hybrid技术栈,如ionic就是angular和cordova的组合技术栈。
cordova的插件非常强大,用户可以用到什么原生方法就去下载对应的插件。项目的h5部分(html,css,js)可以打包在app包内,也可以将h5部署在服务端。h5在调用本地cordova方法时用url scheme来实现.
包括的app的唤醒功能,也用url scheme来实现。如果两个app的url scheme重复,一般情况下后安装的会覆盖之前的。
how about flutter?