NativeScript工作原理
NativeScript是一个runtime,它提供一些机制可以使用JavaScript构建原生的IOS、Android甚至WP(未来会加入)应用。NativeScript有很多非常酷的功能,比如MVVM和CSS渲染原生UI。但是NativeScript最令人兴奋的是它使JavaScript可以直接调用native API。
这听起来可以会令人困惑,首先看一个例子,下面是使用NativeScript编写Android app的一段代码:
var time = new android.text.format.Time();
time.set( 1, 0, 2015 );
console.log( time.format( "%D" ) ); //01/01/15
上述的JavaScript代码实例化了一个Java对象android.text.format.Time,调用它的set方法和format方法并且在控制台输出log。
我们先不解释上述代码的实现原理,再看一个使用NativeScript编写IOS app的例子:
var alert = new UIAlertView();
alert.message = "Hello world!";
alert.addButtonWithTitle( "OK" );
alert.show();
上述的JavaScript代码实例化了一个Objective-C类UIAlertView,随后给它的message属性赋值,调用了addButtonWithTitle方法和show方法,运行效果如下图:

NativeScript并非只包含JavaScript化的Objective-C和Java代码,还集合了一系列的跨平台module,比如发送http请求、构建UI组件等等。大部分app都需要调用原生的API,NativeScript的runtime简化了原生API的调用方式。
这句话可以这么理解,Objective-C和Java也需要调用原生API并且调用方式存在差异,NativeScript削减了差异化,令原生API的调用方式更加简单统一。
下面我们看看NativeScript的工作原理。
1. NativeScript runtime
虽然NativeScript的代码看起来很神奇,但是内部的工作原理其实很简单。NativeScript本质上仍然是JavaScript,解析执行JavaScript的自然是JavaScript引擎。在不同的平台,NativeScript使用平台默认的JavaScript引擎,比如Android平台的V8引擎、IOS平台的JavaScriptCore。既然使用JavaScript引擎解析代码,那么所有的native API的调用语法必须写成规范的JavaScript语法,这样才可以被JavaScript引擎成功解析。
NativeScript使用的是最新稳定版本的V8和JavaScriptCore。因此,NativeScript对ECMAScript规范的支持情况与它使用JavaScript的引擎完全相同。也就是说,Android平台依赖V8对ECMAScript规范的实现程度,IOS依赖JavaScriptCore对ECMAScript规范的实现程度。
时刻谨记NativeScript是依赖JavaScript引擎这一点非常重要。
我们再看第一个例子中的第一行代码:
var time = new android.text.format.Time();
在Android平台,上述NativeScript代码由V8及时编译(JIT Compiled)并执行。对于简单的表达式(比如var x = 1 + 2),我们很容易理解是怎么工作的。但是V8是如何识别android.text.format.Time的呢?
2. NativeScript如何操作JavaScript引擎
V8之所以能够识别android对象是由于NativeScript runtime把它注入到了JavaScript运行环境中。V8提供了大量的API供使用者配置个性化的JavaScript运行环境,甚至可以注入C++代码用来统计JavaScript的CPU使用情况、管理JavaScript的GC等等。

在这些API当中,有些Context类可以提供操作全局作用域的API,这就是NativeScript之所以能够在全局作用域内注入android对象的原理。这种原理其实与Node.js全局方法(比如require())的实现原理相同。IOS的JavaScriptCore引擎也提供了类似的机制。
我们再回顾一下之前的代码:
var time = new android.text.format.Time();
现在我们知道了这段代码运行在V8上,并且V8可以识别android.text.format.Time()是因为NativeScript在全局作用域内注入了android对象。但是仍然有很多疑问没有解决,比如NativeScript如何知道需要注入哪些API?NativeScript如何知道调用Time()会产生什么效果?
下面我们依次解决这些疑问。
3. Metadata(元数据)
NativeScript通过reflection(反射)来构建它所运行平台的可用API。不熟悉其他编程语言的JavaScript开发者可能并不了解reflection,JavaScript是一门非常自由的语言,并不需要reflection。但是在其他编程语言中,尤其是Java,reflection是在runtime时获取某个class详细信息的唯一途径。
可以简单的把reflection理解为在runtime(运行时)而不是编译期获取某个object或class完整结构的途径。reflection的详细介绍感兴趣的可以参考这里。
NativeScript使用reflection构建了适用于各平台的API列表。从性能角度来讲,生成这些API数据是非常有必要的,NativeScript在编译之前生成这些数据,然后在Android/IOS编译阶段嵌入已生成的元数据。
了解了以上机制之后,我们再回顾一下之前的代码:
var time = new android.text.format.Time();
现在我们知道了以上代码之所以能够在V8上运行,使因为NativeScript注入了android.text.format.Time对象。NativeScript通过一个独立的元数据处理过程中明确了需要注入的API,并且在Android和IOS的编译阶段嵌入了所需的元数据。
好,我们继续解答下一个问题:NativeScript是如何将JavaScript的Time()调用映射到原生的android.text.format.Time()调用呢?
4. 原生代码的唤起机制
NativeScript唤起原生代码调用同样依赖于JavaScript引擎的API。上文提到了NativeScript如何对V8引擎注入全局变量,接下来介绍如何通过回调函数实现在JavaScript代码中调用C++代码。
比如在执行new android.text.format.Time()这段代码,V8引擎将会产生一个回调函数。利用这种机制,NativeScript可以监听JavaScript函数的调用,并且在V8回调函数里执行C++代码,从而实现原生代码的调用。
这里提到的回调函数并不是JavaScript的回调函数,而是V8引擎内部的C++函数。V8解析执行JavaScript函数时首先将JavaScript函数映射为C++函数,然后再执行。
Android平台下,NativeScript的C++代码不能直接调用Java的API(比如android.text.format.Time)。这种情况下需要借助Android平台的JNI(Java Native Interface,Java本地接口)实现C++与Java的桥接。借助于JNI,NativeScript便可以调用Android平台的原生Java API。
IOS平台并不需要类似JNI的桥接机制,因为C++可以直接唤起Objective-C的调用。
了解了以上机制,我们再回顾一下之前的代码:
var time = new android.text.format.Time();
上文的描述中,我们知道以上代码可以执行的原理是NativeScript通过单独的元数据生成过程注入了JavaScript引擎android全局对象。然后在执行Time()函数时,依次发生了以下行为:
- V8回调函数执行;
- NativeScript runtime通过元数据明确
Time()的行为是实例化native对象android.text.format.Time; - NativeScript runtime通过JNI实例化
android.text.format.Time对象并且保持对这个对象的引用; - NativeScript runtime返回一个JavaScript对象用来代理Java本地对象
android.text.format.Time; - 回到JavaScript运行环境中,第4步返回的代理对象储存在本地变了
time中。
这里提到的代理对象是NativeScript用来维持JavaScript对象和native对象的映射关系(mapping)。比如执行以下JavaScript代码:
var time = new android.text.format.Time();
time.set( 1, 0, 2015 );
根据生成的元数据,NativeScript知道代理对象time的所用API。按照上述步骤,当调用JavaScript函数Time()时,V8执行对应的回调函数,NativeScript监测到函数的调用,便通过JNI唤起Java的Time对象的调用。
以上便是NativeScript的工作原理。
至于如何将Objective-C对象和Java对象映射为JavaScript对象,这部分工作非常复杂,因为必须考虑到每种编程语言实现继承模式的差异。感兴趣的可以参考IOS的实现方案和Android的实现方案。
通过以上内容,虽然我们知道了如何使用JavaScript代码调用原生API,但是如果针对每个不同平台都分别编写对应的代码,仍然不能够实现“write once,run anywhere”。为了实现这个目标,NativeScript提供了一种非常强大的功能:NativeScript modules。
5. NativeScript modules
NativeScript modules的原理与Node Modules的原理类似,同样遵循CommonJS规范,如果你熟悉Node中require()和exports的工作原理,那么NativeScript modules对你来说便非常容易入手了。
NativeScript modules把各平台专有的API封装成与平台无关的API(类似大家熟知的JavaScript各种兼容性工厂函数)。比如现在我们需要调用各平台的file API,针对Android平台的代码如下:
new java.io.File( path );
针对IOS平台的代码 如下:
NSFileManager.defaultManager();
fileManager.createFileAtPathContentsAttributes( path );
是不是很麻烦?但是如果使用NativeScript file-system module,你只需要使用统一的API:
var fs = require( "file-system" );
var file = new fs.File( path );
如果你已经掌握了本文提到的NativeScript工作原理,便可以很容易的编写NativeScript Module。比如要编写一个module来获取设备OS的版本:
// device.ios.js
module.exports = {
version: UIDevice.currentDevice().systemVersion
}
// device.android.js
module.exports = {
version: android.os.Build.VERSION.RELEASE
}
调用上述Module的方式与调用npm模块相同,使用require()如下:
var device = require( "./device" );
console.log( device.version );
NativeScript Module降低了web开发者开发native应用的门槛,即使你不熟悉native API,也可以花费非常少的时间阅读各平台的API文档,然后编写一个NativeScript Module来增加后续的开发效率。
6. 总结
本文简单介绍了NativeScript的工作原理,总结如下:
- 通过reflection获取native API的详细结构,并生成元数据。这些行为都是在runtime中JIT编译;
- 根据生成的元数据信息,NativeScript利用JavaScript引擎的callback机制向JavaScript运行环境中注入需要的JavaScript全局对象。这些全局对象本质上是native对象的代理对象;
- 通过NativeScript Modules统一API。
深入学习资料:
NativeScript工作原理的更多相关文章
- 【转】NativeScript的工作原理:用JavaScript调用原生API实现跨平台
原文:https://blog.csdn.net/qq_21298703/article/details/44982547 -------------------------------------- ...
- 菜鸟学Struts2——Struts工作原理
在完成Struts2的HelloWorld后,对Struts2的工作原理进行学习.Struts2框架可以按照模块来划分为Servlet Filters,Struts核心模块,拦截器和用户实现部分,其中 ...
- 【夯实Nginx基础】Nginx工作原理和优化、漏洞
本文地址 原文地址 本文提纲: 1. Nginx的模块与工作原理 2. Nginx的进程模型 3 . NginxFastCGI运行原理 3.1 什么是 FastCGI ...
- HashMap的工作原理
HashMap的工作原理 HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...
- 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)
RAC 工作原理和相关组件(三) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...
- ThreadLocal 工作原理、部分源码分析
1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...
- Servlet的生命周期及工作原理
Servlet生命周期分为三个阶段: 1,初始化阶段 调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在 ...
- 代码管理工具 --- git的学习笔记二《git的工作原理》
通过几个问题来学习代码管理工具之git 一.git是什么?为什么要用它?使用它的好处?它与svn的区别,在Mac上,比较好用的git图形界面客户端有 git 是分布式的代码管理工具,使用它是因为,它便 ...
- 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL
周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...
随机推荐
- Android显示等宽图片的问题
安卓开发常遇到一个问题,就是在listView里面,在不知道图片宽高的前提下,另图片布满屏幕(图片宽度等于屏幕宽度,高度自适应).在listView中,只是设置scaleType,imageView. ...
- InstallShield 2012 Spring优惠升级到最新版本(2015.4.30之前)
InstallShield 2012 Spring即将EOF,所以仍在使用InstallShield 2012 Spring的用户请注意下面内容: InstallShield 2012 Spring升 ...
- Lowest Common Ancestor of Two Nodes in a Binary Tree
Reference: http://blog.csdn.net/v_july_v/article/details/18312089 http://leetcode.com/2011/07/lowes ...
- iis 故障导致网站无法访问
服务器使用两三个月突然,昨天无法访问,重启后正常,第二次发生这样的事情了,打开 C:\WINDOWS\system32\LogFiles\HTTPERR 下的 httperr1.txt 201 ...
- GTD时间管理(1)---捕获搜集
前一段时间感觉自己的整个思路很混乱,每一天觉得自己有很多事情很多,但是坐着做着不知道自己做了多少,做项目的时候做着做着时常东想西想.我个人觉得这种想法是不对经的. 于是在google上都出去寻找这方面 ...
- 曲率已驱动了头发——深度分析谷歌AlphaGo击败职业棋手
这篇是我们自开设星际随笔以来写得最长的一篇.我们也花了不少力气.包括把那5盘棋各打了两遍的谱,包括从Nature官网上把那篇谷歌的报告花了200元下载下来研究它的算法(后来发现谷 歌网站上可以免费下载 ...
- 超棒的 15 款 Bootstrap UI 编辑器
自从 2011 年 Mark Otto 和 Jacob Thornton 开发了 Bootstrap,我们第一次接触并熟知了 Bootstrap .这些都归功于 Twitter!从那以后,它就非常 ...
- IOS开发之代码之九宫格
通过UIScrollView展示图片的时候,如果直接向UIScrollView添加UIImageView,在图片数量比较少的时候是没有问题的,但是当我们添加图片数量非常多的时候,会占用大量的内存,我们 ...
- 怎样用UltraISO制作U盘系统安装盘
http://jingyan.baidu.com/article/d169e186800f02436711d87b.html 如今用u盘装系统成为主流,如何不被社会淘汰.跟我往下边看吧~~ 工具/原料 ...
- 转:windows下多线程通信方法
多线程知识简介 同一进程中可以包含多个线程,由于进程中的多个线程可以共享进程中的资源,所以使同一进程中的多个线程之间通信相对比较简单. 当需要有多个线程来访问一个全局变量时,通常我们会在这个全局变量前 ...