深挖一:移动端混合开发之hybrid
一、序言
滚滚长江东逝水,奔流到海不复回。
不要问我为为啥要废这么一句话,其实还是感叹前端发展的迅猛,很多技术和工具都是一闪而逝。如长江之水一样,转瞬即逝。
移动端开发的方式就是如此:
从2010年后的安卓和ios活了之后,移动端app开发的程序员可谓是如过江之鲫,源源不绝。
每年从培训班出来的程序员多达几万。笔者当时还想去参加ios培训呢。
然而过了几年后,考虑到开发成本、维护成本等各方面因素。分端开发已经越来越被当做一个开发效率的瓶颈。
现在让我看看解决这类问题的历程吧。
二、混合框架的两种模式
- hybrid :运行时联系沙盒。
- native : 编译时原生ui。
今天主要是讲解hybrid的发展历程。而native会在下期跟大家见面。
三、hybrid
首先大家要明白deepLink的概念,不懂的推荐去看一篇文章链接:
点我去看deepLink。
deepLink主要有url schema 和Universal Links 两种。本文准备深层次去讲解下 url schema的工作原理。
给出一个url schema的样例。
- dinglei://
- dinglei://getUserInfo
是不是看起来很眼熟,对了url schema本身就是一种协议,而且其工作原理和http的ajax还有些类似,上个别人的图。
我们可以看到ajax的url一改变,就会被浏览器所监测到,然后发送给服务器。这一过程会开启一个线程。当服务器完成相应和返回数据后,线程会开始把回调压入到宏任务中,等待执行。
Attention Please!!!
如果说url schema 本身也是一个 同种类型的处理方式,你会不会偷着乐。
原谅我再次盗图:
url shcema 配合沙盒的工作原理图
请读者先记住以下几点:
- url schema 是app自身注册到手机系统里面的。其配置方式是工程项目下的info.plist里面配置的。在安装完成后,就已经保存在手机系统的协议列表中了。
- 当浏览器挂起这个url schema的请求时,第一接收方式系统本身,不是盒子!!!!
- 默认的链接如 weixin:// 盒子的操作都是发送给手机系统,然后手机系统回去列表上查,查到了会去通知对应的盒子做处理,一般是直接打开盒子。如果需要做其他操作,需要传参。
- 回调,依然是盒子获取盒子本身webview的全局对象,进行执行。
好吧!让我先上第一部分的干货吧。如何通过url schema去跟native实现一些业务场景吧。
window.testUrlSchemaGlobal = window.testUrlSchemaGlobal || {};
// 建立链接 ios 和安卓不同
function doConnection (url) {
console.log(url);
if (window.$plt.isMatch('ios')) {
window.location = url;
} else {
setUrl(url);
}
};
// 建立 安卓的 链接
function setUrl(url) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.setAttribute('src', url);
const d = () => {
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
};
document.body.appendChild(iframe);
}
// 构造url
function getUrlByParams (params) {
debugger
var paramStr = '', url = 'scheme://';
url += params.funcName + '?t=' + new Date().getTime(); //时间戳,防止url不起效
if (params.callback) {
url += '&callback=' + params.callback;
delete params.callback;
}
if (params.param) {
paramStr = typeof params.param == 'object' ? JSON.stringify(params.param) : params.param;
url += '¶m=' + encodeURIComponent(paramStr);
}
return url;
};
/**
*
* @param {*} params
* funName
* callback 传递名称 存放到 全局对象 testUrlSchemaGlobal 共bridge 去调用
*/
function requestHybrid (params) {
//生成唯一执行函数,执行后销毁
var tt = (new Date().getTime());
var t = 'hybrid_' + tt;
var tmpFn;
//处理有回调的情况
if (params.callback) {
tmpFn = params.callback;
params.callback = t;
window.testUrlSchemaGlobal[t] = function (data) {
tmpFn(data);
delete window.testUrlSchemaGlobal[t];
}
}
// 直接连接
doConnection(getUrlByParams(params));
};
//获取版本信息,约定APP的navigator.userAgent版本包含版本信息:scheme/xx.xx.xx
function getHybridInfo() {
var platform_version = {};
var na = navigator.userAgent;
var info = na.match(/scheme\/\d\.\d\.\d/);
if (info && info[0]) {
info = info[0].split('/');
if (info && info.length == 2) {
platform_version.platform = info[0];
platform_version.version = info[1];
}
}
return platform_version;
};
举个例子:
requestHybrid({funcName:"dinglei",param:{isWeb:false},callback(){ console.log('callback') }});
其流程是
1.生成链接 scheme://dinglei?t=1545898661620¶m=%7B%22isWeb%22%3Afalse%7D
2.然后注册全局的callback时间撮到去全局对象 testUrlSchemaGlobal中。
3.iframe去建立链接,传递信息。
4.系统处理这个链接,并向盒子发送。
5.盒子去处理相关任务,完成后调用全局的callback。
但是肯定会有同学,疑惑每次都经过手机系统的调度中心,做个中间转换。不是很浪费资源吗。
回答是yes啦。
看到这里是不是有种豁然开朗的感觉,喜欢的同学给个赞,因为作为新人,您的点赞会是我持续写作的动力。
但是你以为已经结束了吗?
chaoxiao
这只是最原始的交互方式,换而言之,在hybrid的方式中,这种事最low的。也是最直接的。
先给大家介绍ios关于交互方式的发展流程。让大家有个直接的印象。
1.有很多的app直接使用在webview的代理中通过拦截的方式与native进行交互,通常是通过拦截url scheme判断是否是我们需要拦截处理的url及其所对应的要处理的功能是什么。任意版本都支持。也即是我们上面所说的方案。
2.iOS7之后出了JavaScriptCore.framework用于与JS交互,但是不支持iOS6,对于还需要支持iOS6的app,就不能考虑这个了。若需要了解,看最后的推荐阅读。
-
3.WebViewJavascriptBridge开源库使用,本质上,它也是通过webview的代理拦截scheme,然后注入相应的JS。
我们再来看看第三种交互方式。jsbridge。相信用过微信或者支付宝早起版本进行开发的同学。应该对这个东西很眼熟。让我们来看看它是怎么使用的。
首先我们回忆下我们上面的链接方式,是每次都会建立一次链接,给手机系统的调度中心,手机调度中心传给app盒子,盒子来决定做什么事情。这样是耗性能的。这是上面大致的情况。
让我思考下,什么时候是一定需要传给手机调度中心的?
很简单在盒子外的h5中打开链接的时候,是希望进行跳转到某个页面,这个时候因为不在沙盒内,是必须要传递给手机调度中心的。而页面本身在盒子里面的时候,那直接传递给沙盒不是更好点?
jsbridge就是干这件事情的,它除了第一次建立链接是传递给手机的调度中心,后面都是功过bridge直接监听url schema的。
直接上代码来讲解:
-
第一步建立链接
function setupWebViewJavascriptBridge(callback) {
// WebViewJavascriptBridge 传递信息
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 = 'scheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
-
第二步注册方法
WebViewJavascriptBridge.registerHandler(handlerName,
function(data, callback) {
//alert('handlerName callback' + data);
responseCallback(data),
callback && callback({
errorCode: "0",
errorMessage: "成功"
})
})
-
第三步使用
WebViewJavascriptBridge.callHandler(path, params);
因为水平有限,暂时如此。后面等研究够了,会继续更新。