@qinyun
2018-04-23T09:45:39.000000Z
字数 6952
阅读 1930
未分类
本文介绍了借助Jasonette将Web视图和原生组件融合构建真正“混合”应用的做法。
如果我告诉你,只需要上述7行橙色的JSON代码就可以将一个网站变成移动应用,你相信吗?完全不需要使用某种框架API重写网站,就可以获得与移动应用相同的行为。如果你已经有一个现成的网站,只需要简单地引用URL就可以将其“打包”为原生应用。
而如果在此基础上,只需要略微调整JSON代码内容,就可以直接访问所有原生API、原生UI组件以及原生视图切换(View Transition)。
最简化的范例效果如下图所示:
从中可以看出,我嵌入了一个GitHub.com的Web页面,但界面上其余布局均为原生UI组件,例如导航条以及底部的标签栏。而我们并不需要使用任何API重写网站,就可以自动获得原生的切换效果。
在介绍具体做法前你可能会问:“看着挺酷,但除了在原生应用框架内展示Web页面之外,这种技术还有什么意义?”
问得好!这也是本文要讲的重点。我们只需要创建一个无缝的Web视图与应用间双向通信,借此,父应用就可以触发Web视图内的任何JavaScript函数,随后Web视图即可从外部调用原生API。
例如:
请注意,这个视图包含:
上述所有这一切只需要略微调整JSON代码的属性即可实现。
最后请注意,随着在文字输入区输入不同内容,二维码也会产生相应变化。输入的文字可触发二维码生成器Web应用内部的JavaScript函数重新生成二维码图像。
目前还没有任何一个开发框架曾试图从根本上解决“Web视图与原生应用无缝集成”的问题,因为这些框架都专注于完全原生,或完全HTML5的做法。
无论什么时候当我们听到有人讨论移动应用的未来时,很可能会听到类似“到底是HTML5还是原生方法会最终胜出呢?”这样的说法。
似乎没人觉得native
和html
可以共存,而且二者的协同和最终实现似乎也并不容易。
本文我将要介绍:
在进一步介绍前,首先一起看看这样做是好是坏,以及什么时候适合使用这种方法。这种做法的一些潜在用例如下:
1. 使用Web原生功能
应用中的部分内容使用Web引擎来实现也许是一种更适合的做法。例如WebSocket是一种原生的Web功能,主要面向Web环境而设计。这种情况下就更适合使用内建的Web引擎(iOS的WKWebView以及Android的WebView),而非安装某些只能“模拟”WebSocket的第三方库。
无需额外安装任何代码,使用免费工具即可实现目标,这样岂不是更好。同时这也催生了下一个原因。
2. 避免二进制文件体积过大
有些功能也许需要借助庞大的第三方库,而你可能希望能快速用上这样的功能。
例如,为了以原生方式包含二维码图像生成器,可能需要安装某些第三方库,这会导致二进制文件体积增大。但如果使用Web视图引擎并通过一个简单的<script src>
调用JavaScript库,就可以免费实现这一切,并且避免了使用第三方原生库。
3. 缺乏可靠的移动库
对于一些前沿技术,可能暂时并不具备稳定可靠的移动端实现。
好在大部分此类技术都具备Web实现,因此最高效的集成方法就是使用JavaScript库。
4. 构建部分原生,部分基于Web的应用
很多新手开发者想要将自己的网站移植为移动应用,但在发现自己现有网站的部分功能过于复杂,无法面向每种移动平台快速重写时,往往会感到沮丧或受挫。
例如你可能有一个非常复杂的Web页面无法快速转换为移动应用,但网站的其他内容可以很容易地转换。
面对这种情况,如果通过某种方法将应用的大部分内容以原生方式构建,对于特别复杂的页面直接将其以HTML的形式无缝集成到应用中,是不是很棒啊。
Jasonette是一种基于标记语言,构建跨平台原生应用的开源方法。
该技术看似Web浏览器,但并不会将HTML标记语言解释为Web页面,而是会将JSON标记解释为iOS和Android上的原生应用。
正如所有Web浏览器都有完全相同的代码,但只要按需解释不同类型的HTML标记,即可为用户提供所有不同类型的Web应用,所有Jasonette应用也有着完全相同的库,可按需解释不同类型的JSON标记并创建出你的应用。开发者完全无需触及代码本身,只需要编写标记,将代码实时“翻译”为原生应用,即可开发出自己的应用来。
有关Jasonette的详细介绍可以参阅这里。
虽然Jasonette的核心作用在于构建原生应用,但本文的重点在于介绍如何将HTML集成到核心原生引擎中,接下来就一起了解一下吧。
原生应用很棒,但有时候我们依然需要使用Web功能。
但Web视图与原生应用的集成是个麻烦的过程。无缝的集成要求:
这是一个非常繁重的工作,因此先从第一个环节着手介绍:直接将Web容器嵌入原生布局 —并将其作为第1版发布:
JSON Web容器,JSON中的HTML将变为原生应用组件。
仅这一点就已经很实用了,但由于无法交互,依然存在一定的局限。
父应用无法控制子Web容器,子容器无法向父应用发送任何事件通知,这导致Web容器与外界完全隔离。
发布第1版之后,我们开始处理第二个问题:为Web容器添加交互能力。
下文将介绍如何为之前创建的静态Web容器添加交互能力,让它变得更强大。
之前在第1版中,为了使用Web容器作为后台视图组件,我们首先需要将$jason.body.background.type
设置为"html"
,随后在$jason.body.background.text
属性下添加硬编码的HTML文本,例如这样:
一般来说,人们往往更希望直接使用Web URL对容器进行实例化,而不希望将整个HTML代码以硬编码的方式作为一行代码加入。
Web容器2.0增加了url
属性,我们可以嵌入file://
形式的本地HTML,例如这样(可以从伴随应用发布的本地HTML文件加载):
或者也可以嵌入远程的http[s]://
URL,例如这样(可以从远程HTML加载):
之前,Web容器只能用于展示内容,无法交互。这意味着下列做法全部无法实现:
此时我们只能展示Web容器的内容。这就像网页中嵌入的iframe框架,主页面完全无法访问iframe框架中的内容。
Jasonette最大的目标在于设计一种可以描述跨平台移动应用的标准化标记语言。因此我们需要这个标记语言能够全面地描述父应用和子Web容器之间的双向通信。
为此我在父应用和子Web容器之间使用了一种基于JSON-RPC的通信管道。由于Jasonette中的一切都是通过JSON对象表达的,因此使用JSON-RPC标准格式作为通信协议就成了一种非常自然合理的方式。
为了让JavaScript函数能够调用Web容器,需要声明一个名为$agent.request
的操作:
$agent.request是一种原生API,可触发JSON-RPC请求并发送给Web容器。为了使用该API,必须将options
对象作为参数传递。
options
对象实际上是发送给Web容器的JSON-RPC请求。每个属性的含义如下:
id
:Web容器构建在一种名为Agent的底层架构基础上,通常来说,我们可以为一个视图使用多个Agent,每个Agent可以有自己的唯一ID。但Web容器是一种特殊类型的Agent,只能使用$webcontainer作为ID,因此这里需要使用ID。method
:要调用的JavaScript函数名称。params
:传递给JavaScript函数的参数数组。因此完整来看,所用的标记应该是类似这样的:
这串标记实际上是在说:
当视图加载(load)时,向Web容器Agent发送一个JSON-RPC请求($agent.request),而具体的请求是通过options
指定的。
Web容器在$jason.body.background下定义,本例中将会加载一个名为file://index.html
的本地文件。
随后会查找一个名为login的JavaScript函数并传递params
下的两个参数("alice"
和"1234"
)。
上文介绍了父应用如何触发子Web容器的JavaScript函数调用,我们还可以反着来,让Web容器触发父应用的原生API。
详情请参阅Agent文档。
继续回到上文介绍的二维码生成器范例:
$agent.request
操作,进而调用JavaScript函数“qr”。具体示例可以参阅这里。
有时候我们可能需要在Web容器完成初始HTML加载后,动态地将JavaScript代码注入Web容器。
假设要构建一个自定义的Web浏览器应用,我们可能希望将自己的自定义JavaScript注入到每个Web视图,借此定制Web视图的行为,这有点类似于Web浏览器的扩展。
就算不需要构建Web浏览器,当希望为所包含的内容不由我们控制的URL实现自定义行为时,同样需要使用脚本注入的方法。原生应用和Web容器只能通过$agent
API通信,但如果无法更改HTML内容,只能通过动态注入的方式将$agent
接口加入Web容器。
正如上文所述,$jason.body.background
这个Web容器也是一个agent
,这意味着我们可以使用与普通Agent完全相同的$agent.inject方法。
以往,Web容器只能通过两种方式处理链接点击操作:
"type": "$default"
设置为action
属性。两者均为“全无或全有(All or nothing)”解决方案。
通过使用新的Web容器,可以将任何action
附加到$jason.body.background
这个Web容器,进而处理链接点击之类的事件。
一起看一个例子:
在这里我们为Web容器附加了"trigger": "displayBanner"
,这意味着当用户点击Web容器内的任何链接后,将触发displayBanner
操作,而非直接交由Web视图处理。
此外如果查看displayBanner
操作会发现,这里出现了变量$jason
。在本例中,点击的链接将通过$jason
变量传递。例如,如果点击一个名为"https://google.com"
的URL,$jason
将获得下列值:
这意味着我们可以检查$jason.url的值进而选择性地触发不同操作。
用自定义Web浏览器的实现作为另一个例子一起来看看:
我们会检查URL是否包含字符串signin
,并根据结果执行两个不同操作。
signin
,打开一个新视图并以原生方式完成登录操作。signin
,则直接运行"type": "$default"
操作,实现类似普通浏览器的行为。利用新版Web容器的下列特性,可以实现很多有趣的操作:
url
属性实现自我加载,并充当一个功能齐备的浏览器。我们甚至可以通过几十行JSON代码构建一个自定义的Web浏览器。由于现在可以劫持每个链接点击,因此可以检查$jason.url
,并根据结果运行我们需要的任何操作。
例如下面的例子:
从左图可以看到,点击链接后的行为与普通浏览器无异("type": "$default"
)。
从右图可以看到,点击链接后可以用原生方式转换至另一个JASON视图。
这一切都可以根据$jason.url
的值选择性地触发实现。
第1步:向Web容器附加一个名为visit
的操作:
第2步:根据$jason.url
的值运行visit
内部的相关操作
在下列代码中,我们会检查$jason.url
是否与newest
、show
、ask
等内容(均为顶级菜单项链接)相符。如果相符,设置"type": "$default"
即可让Web容器做出与普通浏览器一样的行为。
如果模式不符,则可通过原生的$href
转换打开一个新视图,并将点击的链接作为参数传递过去。
该Web浏览器的完整JSON标记请参阅这里(仅48行!)。
人们通常在说“混合”应用时,主要是指封装在原生应用框架内部的HTML Web应用。
但此处说的并不是这种应用。这里所谓的“混合”是指真正的混合应用,也就是可以同时包含多个原生视图以及多个基于Web的视图的应用。在这种应用中,一个视图可以有多个原生UI组件,以及一个用相同原生布局渲染的Web容器。
Web视图与原生视图的交织应当尽可能无缝,使得用户完全无法分辨。
在这个例子中,我创建了一个可以在Web容器中显示jasonbase.com的内容,并将其作为主页视图的应用。
Jasonbase是我开发的免费JSON托管服务,该服务可以很简单地用于托管Jasonette应用所用到的JSON标记。
当然,这本身是个网站,但我将其嵌入到Jasonette中,因此在点击链接后并不会打开网页,而是会通过原生的$href
转换展示原生的JASON视图。
完全无需触及Jasonbase.com的代码就可以构建出这个应用。
只需要将网站作为Web容器嵌入Jasonette,随后劫持链接点击操作的原生处理方式,这样就可以实现原生应用所具备的各类功能,例如触发原生API以及进行原生转换。
完整代码可参阅这里。
在我看来,让这一切如此令人赞叹的原因在于,在框架层面上即可妥善处理好一切。所有最困难的工作都是在后台完成的。
应用开发者并不需要自行费时费力从零开始实现下列这一切:
整个解决方案创建了下列内容组成的抽象:
我并不觉得这种方法可以解决所有问题,但从自己的用例来看,至少可以说这是个不错的解决方案。
我试着以非常前沿的技术来构建应用,而这些技术已经前沿到在移动端还没有任何稳定可靠的实现(由于协议的一些本质,甚至不清楚最终是否会有移动端的实现)。好在这些技术都有JavaScript实现,因此不费什么事就可以轻松地将其与应用相集成。
总的来说,这种技术很棒,我对目前的效果非常满意。最新版文档已经包含了所有新功能,欢迎大家深入研究并尝试。
声明:能力越大,需要担负的责任也就越大
最后我想说:虽然这种新技术确实很强大,但我觉得大家在开发应用时都应该在用户体验方面进行更全面的权衡。
有些人可能会借助这种技术构建完全由Web视图组成的应用,但说到底这样的做法,你的应用实际上就只是一个网站,已经与开发专属应用的本意背道而驰了。
需要强调的是,我并不认为你的每个应用都应同时包含HTML和原生组件。我只是认为,这样的做法对很多面临某些具体状况的人会显得较为有用。只不过别过火就好。
本文最初发布于Ethan的博客,经原作者授权由InfoQ中文站翻译并分享。英文原文请看:How to Turn Your Website into a Mobile App with 7 Lines of JSON