里面只规定着如何实现JSBridge和原生交互部分,本

1. 无图言屌

优酷录像——有录制有JB!

图片 1图片 2

图片 3

图片 4

图片 5

图片 6

图片 7

图片 8

图片 9

JSBridge剖析规则

近来的小说中有提到JSBridge的落到实处,但当场其实越多的是关切原理层面,那么实际上,定义的竞相深入分析法则是什么的吧?如下

// 以ui.toast实际调用的示例
// `${CUSTOM_PROTOCOL_SCHEME}://${module}:${callbackId}/${method}?${params}`
const uri = 'QuickHybridJSBridge://ui:9527/toast?{"message":"hello"}';

if (os.quick) {
    // 依赖于os判断
    if (os.ios) {
        // ios采用
        window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(uri);
    } else {
        window.top.prompt(uri, '');
    }
} else {
    // 浏览器
    warn(`浏览器中jsbridge无效, 对应scheme: ${uri}`);
}

原生容器中摄取到对于的uri后反解析即可见道调用了些什么,上述中:

  • QuickHybridJSBridge是本框架交互的scheme标志

  • modulemethod个别代表API的模块名和艺术名

  • params是对于措施传递的附加参数,原生容器会剖判成JSONObject

  • callbackId是此番API调用在H5端的回调id,原生容器奉行完后,文告H5时会传递回调id,然后H5端找到呼应的回调函数并施行

怎么要用uri的点子,因为这种方法得以宽容从前的scheme形式,假如方案切换,变动代价下(自个儿即是那般升级上来的,所以并没有替换的不可缺少)

有关代码标准与单元测量试验

种类中应用的Airbnb代码规范并不是100%顺应原版,而是基于项指标景观定制了下,可是全部上95%以上是相符的

再有一块正是单元测量试验,那是很轻便忽视的一块,可是也挺难做好的。那一个项目中,基于Karma + Mocha进展单元测试,并且实际不是测量检验驱动,而是在鲜明好内容后,对宗旨部分的代码都开展单测。
其中对此API的调用基本都以靠JS来模拟,对于部分优秀的不二秘籍,还需Object.defineProperty(window.navigator, name, prop)来改动window本身的习性来效仿。
本项目中的宗旨代码已经高达了100%的代码覆盖率。

具体的代码这里不赘述,能够参照他事他说加以考察源码

摘录

只是要小心一点的是,在你的 web.config 里会有一段配置允许 RazorJS 使用的目录,就是说你的JS文件务必置于此目录里才足以动用此措施来援引:

仙剑奇侠传的web移植版

2015/10/06 · HTML5 · 1 评论 · 仙剑奇侠传

原来的小说出处: 刘骥(@刘骥-JimLiu)   

源码

github上那几个框架的贯彻

quickhybrid/quickhybrid

种类的结构

在前期的版本中,其实整个前端库就唯有多个文本,里面只明确着哪些兑现JSBridge和原生交互部分。不过到新型的本子中,由于效果稳步增添,单一文件难以满意需要和护卫,由此重构成了一整个项目。

全副项目基于ES6Airbnb代码规范,使用gulp + rollup构建,部分首要代码进行了Karma + Mocha单元测验

全部目录结构如下:

quickhybrid
    |- dist             // 发布目录
    |   |- quick.js
    |   |- quick.h5.js
    |- build            // 构建项目的相关代码
    |   |- gulpfile.js
    |   |- rollupbuild.js
    |- src              // 核心源码
    |   |- api          // 各个环境下的api实现 
    |   |   |- h5       // h5下的api
    |   |   |- native   // quick下的api
    |   |- core         // 核心控制
    |   |   |- ...      // 将核心代码切割为多个文件
    |   |- inner        // 内部用到的代码
    |   |- util         // 用到的工具类
    |- test             // 单元测试相关
    |   |- unit         
    |   |   |- karma.xxx.config.js
    |   |- xxx.spec.js
    |   |- ...

图片 10

?

<script>
 var currDate = '@DateTime.Now'; //直接调用.NET的方法

 console.log(currDate)
</script>

2.4. 施用了什么游戏引擎/框架/库/本事

从思路上看的话,能够说采用了The-Best-JS-Game-Framework。

最要紧的,这几个顺序首要利用了co,使用co/yield/generator来革新异步开荒的体验,让整个庞大的程序落成成为了说不定——前言中说的2018年的一次大重构正是干这几个——那是三个十一分首要的重构,过去的话三个异步的update/render loop就足以令人抓狂,乃至于笔者前日一贯不想再写异步的JS了T_T,也是有时机笔者会再写一篇文章来介绍JS“同步”编制程序以及js-csp那一个可怜风趣的东西。但您领会co其实是二个分外非常轻巧的库,所以纵然未有co的话,温馨造一个堪堪一用的车轮也特别轻易,所以想消除这一个依赖是很简短的。

在这几个坑之初,原生Promise还没普遍,所以引进了q,但实际在全路项目中实现了co之后,非常少用得着Promise,而且也得以很轻易的向原生Promise迁移,当然因为懒小编是没那样干的。

其余方面可以说大约从未借助第三方的库了,恐怕还会有jQuery啊那类的事物,只是用了一丁丁点,极其轻易解除依赖。

仙剑是三个很古老的游乐,使用当代娱乐引擎重新实现仙剑的主程序并不曾太直接的帮手。今世的2D娱乐引擎围绕Coca Cola和情景管理为主,纵然在SDLPAL和h5pal中也可以有Sprite和风貌模块,但现实到手艺层面和现代娱乐引擎里的要么距离非常的大。再加上技(xīn)术(lǐ)洁(biàn)癖(tài)的原因,笔者并未有用别样今世的三十日游引擎,然则等到车轮造得几近的时候,开掘游戏引擎的想想果然是几十年未有太大变化……

出于音乐和音响效果系统通透到底坑了(原因见后文),所以Web奥迪(Audi)o前段时间不关乎。图形方面只涉及到canvas 2D,况且因为仙剑本人的财富都以像素级的,所以图形这一层也大都都以在getImageData/putImageData的等级次序直接操作像素,并不曾选择任何canvas的绘图API。由此只要继续把绘图层迁移到WebGL也会很轻便,但是当下看来完全未有这几个供给。

h5pal使用GPLv3发表,作者对开源交涉差不离不懂,只晓得GPL是相比严刻的一种左券,而且SDLPAL是用GPLv3的,思考到本人抄了她重重代码,于是用了那几个最少比不上他宽松的磋商,况兼再度向SDLPAL表示敬意。

前言

API达成阶段之JS端的落成,入眼描述这些类型的JS端皆有个别什么内容,是什么样贯彻的。

不一致于平常混合框架的只包蕴JSBridge部分的前端达成,本框架的前端实现蕴含JSBridge部分、多平台支持,统一预管理等等。

源码

github上这些框架的落到实处

quickhybrid/quickhybrid

1
2
3
4
5
<razorJSSettings handlerPath="~/razorjs.axd">
 <allowedPaths>
 <add path="~/Scripts" />
 </allowedPaths>
 </razorJSSettings>

提及底要说一下的是其范围。有好的地点本来也许有倒霉的一派,由于其利用的是 RazorEngine ,所以你不可以在 JS 里使用 MVC 的 HTML Helper 方法,即具备@Html 发轫的办法。另四个标题正是其不能够鉴定分别 JS 里的笺注代码,正是说借使您在讲明里使用了 .NET 的主意也同等会奉行,如若您的秘诀准确就没难点,不然就能够停顿 JS 的实践间接报错了,所以不用以为没用的措施注释掉就能够了啊。

2.7. 现行反革命看起来皆以dev状态,什么日期会成为成品游戏?

兴许长久不会,因为没引力再把种种BUG还会有音频部分的坑填了……

假若有生之年真的能填,那么也许能够用node-webkit这类的事物打包成产品游戏,但是……有趣么……

API内部做了些什么

API内部只做与本身遵循逻辑相关的操作,这里有多少个示范

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, 'message', );
        const options = args[0];
        const resolve = args[1];

        // 实际的toast实现
        toast(options);
        options.success && options.success();
        resolve && resolve();
    },
}, ...]);

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['quick'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, 'message');

        quick.callInner.apply(this, args);
    },
}, ...]);

上述是toast功效在h5和quick情状下的贯彻,其中,在quick意况下独一做的就是合营了多个字符串格局的调用,在h5情形下则是全然的达成了h5下相应的效用(promise也需自行兼容)

为什么h第55中学更复杂?因为quick景况中,只需求拼凑成二个JSBridge命令发送给原生就可以,具体效果由原生实现,而h5的达成是须求自身全然落到实处的。

别的,其实在quick情况中,上述还不是起码的代码(上述加了一个男才女貌调用成效,所以多了几行),起码代码如下

quick.extendModule('ui', [{
    namespace: 'confirm',
    os: ['quick'],
    defaultParams: {
        title: '',
        message: '',
        buttonLabels: ['取消', '确定'],
    },
}, ...]);

能够见到,只固然相符标准的API定义,在quick蒙受下的贯彻只要求定义些暗中同意参数就足以了,别的的框架自动辅助达成了(同样promise的完结也在里边暗许处理掉了)

那样的话,就终黄浩然式quick境况下的API数量多,实际上扩充的代码也并十分的少。

合并的预管理

在上一篇API多平台的支撑中有关联怎么着依照Object.defineProperty福如东海一个援助多平台调用的API,完毕起来的API差十分少是那样子的

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 确保get得到的函数一定是能执行的
        const nameSpaceApi = proxysApis[finalNameSpace];

        // 得到当前是哪一个环境,获得对应环境下的代理对象
        return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
    },
    set: function proxySetter() {
        alert('不允许修改quick API');
    },
});

...

quick.extendModule('ui', [{
    namespace: 'alert',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(message) {
        alert('h5-' + message);
    },
}]);

其中nameSpaceApi.h5的值是api.runCode,约等于说直接试行runCode(...)中的代码

偏偏那样是缺乏的,大家必要对调用方法的输入等做统一预管理,因而在此处,大家依照实际的境况,在此基础上极其周密,加上统一预处理机制,也就是

const newProxy = new Proxy(api, apiRuncode);

Object.defineProperty(apiParent, apiName, {
    ...
    get: function proxyGetter() {
        ...
        return newProxy.walk();
    }
});

大家将新的运维代码变为三个代理对象Proxy,代理api.runCode,然后在get时再次回到代理过后的莫过于措施(.walk()主意表示代理对象内部会进行叁遍联合的预管理)

代理对象的代码如下

function Proxy(api, callback) {
    this.api = api;
    this.callback = callback;
}

Proxy.prototype.walk = function walk() {
    // 实时获取promise
    const Promise = hybridJs.getPromise();

    // 返回一个闭包函数
    return (...rest) = >{
        let args = rest;

        args[0] = args[0] || {};
        // 默认参数的处理
        if (this.api.defaultParams && (args[0] instanceof Object)) {
            Object.keys(this.api.defaultParams).forEach((item) = >{
                if (args[0][item] === undefined) {
                    args[0][item] = this.api.defaultParams[item];
                }
            });
        }

        // 决定是否使用Promise
        let finallyCallback;

        if (this.callback) {
            // 将this指针修正为proxy内部,方便直接使用一些api关键参数
            finallyCallback = this.callback;
        }

        if (Promise) {
            return finallyCallback && new Promise((resolve, reject) = >{
                // 拓展 args
                args = args.concat([resolve, reject]);
                finallyCallback.apply(this, args);
            });
        }

        return finallyCallback && finallyCallback.apply(this, args);
    };
};

从源码中可以看来,这一个代理对象统一预管理了两件专门的学问:

  • 1.对此合法的输入参数,进行暗许参数的协作

  • 2.纵然条件中协助Promise,那么重回Promise对象並且参数的尾声加上resolvereject

再者,后续倘使有新的相会预管理(调用API前的预管理),只需在那几个代理对象的这几个法子中加进就可以

1
2
3
<p>
 @Html.RazorJSInline("~/Scripts/Example.js")
</p>

但另一种境况是,假设小编想在贰个单身的 JS 文件里接纳Razor,那以上的点子可不行,因为MVC不会平素申明JS文件,唯有放到 Razor view里本领够。但是在此作者向大家推荐三个第三方类库,就可令你一直在单独的 JS 文件里应用 Razor

2.9. 所以总的完毕度?

直接搬GitHub上给(胡邹)的吧:

模块 进度
资源 90%
读档 99%
存档 40%
Surface 90%
位图 99%
Sprite 99%
地图 90%
场景 90%
调色盘 90%
文本 99%
脚本(天坑) 70%
平常UI 90%
战斗UI 90%
战斗(天坑) 70%
播片 90%
结局 95%
音乐 0%
音效 0%

UA约定

混合开拓容器中,需求有一个UA标志位来判别当前系统。

此处Android和iOS原生容器统一在webview中拉长如下UA标志(也正是说,借使容器UA中有这几个标记位,就象征是quick意况-这也是os决断的贯彻原理)

String ua = webview.getSettings().getUserAgentString();

ua += " QuickHybridJs/" + getVersion();

// 设置浏览器UA,JS端通过UA判断是否属于quick环境
webview.getSettings().setUserAgentString(ua);

// 获取默认UA
NSString *defaultUA = [[UIWebView new] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

NSString *version = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleShortVersionString"];

NSString *customerUA = [defaultUA stringByAppendingString:[NSString stringWithFormat:@" QuickHybridJs/%@", version]];

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":customerUA}];

如上述代码中分别在Android和iOS容器的UA中增多重心的标志位。

API内部做了些什么

API内部只做与自己效劳逻辑相关的操作,这里有多少个示范

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, 'message', );
        const options = args[0];
        const resolve = args[1];

        // 实际的toast实现
        toast(options);
        options.success && options.success();
        resolve && resolve();
    },
}, ...]);

quick.extendModule('ui', [{
    namespace: 'toast',
    os: ['quick'],
    defaultParams: {
        message: '',
    },
    runCode(...rest) {
        // 兼容字符串形式
        const args = innerUtil.compatibleStringParamsToObject.call(this, rest, 'message');

        quick.callInner.apply(this, args);
    },
}, ...]);

上述是toast功效在h5和quick情状下的兑现,当中,在quick环境下独一做的正是协作了贰个字符串情势的调用,在h5情状下则是一心的落到实处了h5下相应的效果与利益(promise也需自行包容)

缘何h5中更眼花缭乱?因为quick境遇中,只须要拼凑成一个JSBridge命令发送给原生就能够,具体效果由原生达成,而h5的落到实处是急需自个儿完全完毕的。

其余,其实在quick意况中,上述还不是起码的代码(上述加了三个相当调用功能,所以多了几行),起码代码如下

quick.extendModule('ui', [{
    namespace: 'confirm',
    os: ['quick'],
    defaultParams: {
        title: '',
        message: '',
        buttonLabels: ['取消', '确定'],
    },
}, ...]);

能够看来,只如若切合标准的API定义,在quick情形下的完成只须要定义些暗中认可参数就足以了,其余的框架自动援助实现了(同样promise的落实也在中间私下认可管理掉了)

如此那般的话,就终陈岚式quick情形下的API数量多,实际上扩张的代码也并十分的少。

?

此类库名字就叫 RazorJS,那是多个开源的品类,可到以下地点下载源码:

2.1. 能玩吗?

。但在GitHub repo里并不会包括游戏的财富文件,于是要求团结去找(嘿嘿mq2x)。由于不散发游戏财富文件,且思虑到体积,作者也不会提供一个在线娱乐的本子。所以基本上唯有开荒者可能动手技能强的同班才具玩上它了(如若您确实想玩……)

不惦念碰到BUG(无数个)形成游戏一贯罢工的情况下(当然就是小编的自家是足以耳熟能详地避过这个BUG的233333),一度得以从新开游戏一贯玩到大结局了,况且自身曾经过关两一回了XD

联合的预处理

在上一篇API多平台的支撑中有关系如何依照Object.defineProperty完结八个支撑多平台调用的API,达成起来的API大约是那样子的

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 确保get得到的函数一定是能执行的
        const nameSpaceApi = proxysApis[finalNameSpace];

        // 得到当前是哪一个环境,获得对应环境下的代理对象
        return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
    },
    set: function proxySetter() {
        alert('不允许修改quick API');
    },
});

...

quick.extendModule('ui', [{
    namespace: 'alert',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(message) {
        alert('h5-' + message);
    },
}]);

其中nameSpaceApi.h5的值是api.runCode,也正是说直接推行runCode(...)中的代码

一味那样是相当不足的,大家必要对调用方法的输入等做联合预处理,因而在这里,大家依据实际的景观,在此基础上更为健全,加上统一预处理机制,也就是

const newProxy = new Proxy(api, apiRuncode);

Object.defineProperty(apiParent, apiName, {
    ...
    get: function proxyGetter() {
        ...
        return newProxy.walk();
    }
});

咱俩将新的周转代码变为一个代理对象Proxy,代理api.runCode,然后在get时回来代理过后的莫过于措施(.walk()主意表示代理对象内部会开展一遍联合的预管理)

代理对象的代码如下

function Proxy(api, callback) {
    this.api = api;
    this.callback = callback;
}

Proxy.prototype.walk = function walk() {
    // 实时获取promise
    const Promise = hybridJs.getPromise();

    // 返回一个闭包函数
    return (...rest) = >{
        let args = rest;

        args[0] = args[0] || {};
        // 默认参数的处理
        if (this.api.defaultParams && (args[0] instanceof Object)) {
            Object.keys(this.api.defaultParams).forEach((item) = >{
                if (args[0][item] === undefined) {
                    args[0][item] = this.api.defaultParams[item];
                }
            });
        }

        // 决定是否使用Promise
        let finallyCallback;

        if (this.callback) {
            // 将this指针修正为proxy内部,方便直接使用一些api关键参数
            finallyCallback = this.callback;
        }

        if (Promise) {
            return finallyCallback && new Promise((resolve, reject) = >{
                // 拓展 args
                args = args.concat([resolve, reject]);
                finallyCallback.apply(this, args);
            });
        }

        return finallyCallback && finallyCallback.apply(this, args);
    };
};

从源码中能够观看,那么些代理对象统一预管理了两件职业:

  • 1.对此官方的输入参数,进行私下认可参数的相称

  • 2.只要条件中协助Promise,那么再次回到Promise对象并且参数的末尾加上resolvereject

并且,后续假如有新的集结预处理(调用API前的预管理),只需在那些代理对象的这一个艺术中追加就能够

JSBridge深入分析准绳

前面的篇章中有关系JSBridge的实现,但那时其实更加的多的是关爱原理层面,那么实际上,定义的相互分析准绳是何等的啊?如下

// 以ui.toast实际调用的示例
// `${CUSTOM_PROTOCOL_SCHEME}://${module}:${callbackId}/${method}?${params}`
const uri = 'QuickHybridJSBridge://ui:9527/toast?{"message":"hello"}';

if (os.quick) {
    // 依赖于os判断
    if (os.ios) {
        // ios采用
        window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(uri);
    } else {
        window.top.prompt(uri, '');
    }
} else {
    // 浏览器
    warn(`浏览器中jsbridge无效, 对应scheme: ${uri}`);
}

原生容器中收受到对于的uri后反深入分析就可以见道调用了些什么,上述中:

  • QuickHybridJSBridge是本框架交互的scheme标志

  • modulemethod分级表示API的模块名和艺术名

  • params是对于措施传递的附加参数,原生容器会深入分析成JSONObject

  • callbackId是本次API调用在H5端的回调id,原生容器实行完后,布告H5时会传递回调id,然后H5端找到相应的回调函数并实践

缘何要用uri的方法,因为这种艺术能够相配在此在此之前的scheme情势,假如方案切换,变动代价下(本人正是那般晋级上来的,所以未有替换的不能缺少)

但另一种景况是,假使本身想在二个独立的 JS 文件里使用 Razor,那以上的方法可不行,因为MVC不会直接表明JS文件,唯有放到 Razor view里能力够。不过在此我向大家推荐贰个第三方类库,就可让你平昔在独立的 JS 文件里应用 Razor

哦,不错,有了此引擎,就足以完全部独用立了 web 蒙受去采用 MVC 的 Razor ,那些效应可令你可怜有帮助地制订一些心灵手巧的模板,如有的 email 模板,你可直接在模板里应用各类.NET 方法还是自定义的靶子,然后动态变化想要的开始和结果。OK,但以此引擎而不是这一次小编要介绍的事物,在此只是顺便说说啊

本文由必威发布于必威-前端,转载请注明出处:里面只规定着如何实现JSBridge和原生交互部分,本

相关阅读