【转】NativeScript的工做原理:用JavaScript调用原生API实现跨平台

原文: https://blog.csdn.net/qq_21298703/article/details/44982547javascript

------------------------------------------------------------------------php

注* NativeScript是最近推出的一个跨平台解决方案,可让你能够用JavaScript来直接写Android、iOS本地应用程序,将来还即将扩展到Windows平台。是最近比较受关注的项目。它与    nw(原名node-webkit ,用Web写winodw/linux桌面应用)和    phonegap内嵌webview写APP的实现方式有着本质的不一样,它直接用JavaScript调用系统原生API,于是有一些原生应用的特色。html

NativeScript

NativeScript是一个运行环境,可让你使用通用的JavaScript代码,打造原生的iOS,Android和 Windows(即将推出)应用程序。 NativeScript有不少很酷的功能,好比支持JavaScript对象双向绑定到原生UI组件,以及用CSS为原生应用程序写样式。但我最喜欢的 功能是NativeScript可让您直接访问本地平台的原生API。java

注* 能够理解为NativeScript是一个JavaScript V8运行环境的命令转发代理,将JavaScript调用转发给不一样平台上的原生API如Android、iOS,以及即将支持的Windows。node

例如,看看这个NativeScript写的Android应用程序的代码:linux

1
2
var  time =  new  android.text.format.Time();time.set( 1, 0, 2015 );
console.log( time.format(  "%D"  ) );

 

你只须要一两分钟来分析一下就明白了,这段JavaScript代码实例化一个Java android.text.format.Time()对象,调用其set()方法,而后打印format后的返回值,是字符串“01/01/15”。android

我知道你已经很激动了,先不要慌,让咱们再来看看iOS的代码:ios

1
2
3
4
var  alert =  new  UIAlertView();
alert.message =  "Hello world!" ;
alert.addButtonWithTitle(  "OK"  );
alert.show();

 

这段JavaScript代码实例化一个Objective-C UIAlertView类,设置它的信息属性,而后调用它的addButtonWithTitle()和show()方法。当您运行这段代码,你会看到hello word的警告框。git

NativeScript运行时

该NativeScript运行环境看起来可能像变魔术同样,无论你信不信,该架构并非那么的复杂。一切从JavaScript虚拟机开 始,NativeScript从这里开始执行JavaScript指令。具体来讲,NativeScript在Android采用v8;在iOS上采用   JavaScriptCore。因为NativeScript使用JavaScript虚拟机,你访问原生API的全部JavaScript代码,仍然须要遵照JavaScript的语法结构和规范。github

通常来讲,NativeScript会同时采用V8和JavaScriptCore的最新稳定版;所以NativeScript对 ECMAScript语言的支持在iOS的桌面Safari上面几乎是相同的,而且NativeScript在Android上面也几乎与桌面浏览器相 同。包括一些ES6的新语法。

了解NativeScript使用了JavaScript虚拟机是很重要的,但这只是实现的第一步,让咱们来看看上文示例的第一行代码:

1
var  time =  new  android.text.format.Time();

 

在NativeScript Android运行环境中,该代码会被编译(    JIT)并在V8中执行。这咱们可能会很容易理解变...:

1
var  x = 1 + 2;

 

可是接下来的问题是...V8怎么知道什么是android.text.format.Time()呢?

咱们将重点讲V8和Android的实现,基本架构模式一样适用于iOS上的JavaScriptCore。若是出现显着差异,本文会提到。

这里将不讨论NativeScript在Windows上的实现细节,由于解决方案可能还会变,可是,当前的Windows实现跟iOS上的JavaScriptCore运行原理几乎同样。

NativeScript是怎样管理JavaScript虚拟机的

V8知道android是什么,由于NativeScript在运行时进行了注入,由于V8拥有一堆让你配置JavaScript环境的 API。在JavaScript中您可使用自定义的C++代码来分析CPU使用率,管理的JavaScript垃圾收集,等等一大堆API

面对这些API的是几个“Context”类,可让你操纵全局变量,从而有可能为NativeScript注入一个全局的android对 象。这实际上使用了与Node.js的相同运行机制,使全局API可用 - 如 require() - NativeScript使用它注入可让你访问本地代码的API。 JavaScriptCore的也有相似的机制。酷吧?

让咱们回到咱们的代码:

1
var  time =  new  android.text.format.Time();

 

如今你知道这个代码在V8中运行时,而V8已经知道什么是android.text.format.Time()了,由于 NativeScript注入了必需的对象到全局范围。但仍存在着一些大的悬而未决的问题,如何让NativeScript明白那些注入的API究竟是干 什么的,而后调用?

Metadata(元数据)

该NativeScript运行环境看起来可能像变魔术同样,无论你信不信,该架构并非那么的复杂。一切从JavaScript虚拟机开 始,NativeScript从这里开始执行JavaScript指令。具体来讲,NativeScript在Android采用v8;在iOS上采用   JavaScriptCore。因为NativeScript使用JavaScript虚拟机,你访问原生API的全部JavaScript代码,仍然须要遵照JavaScript的语法结构和规范。

对于NativeScript,反射是让NativeScript能够调用每一个平台上的API的基石。包括 android.text.format.Time。由于从性能角度来看重构这些API是很困难的,NativeScript会提早作掉这些,并在 Android/iOS预编绎过程当中嵌入预先生成的元数据。

考虑到这一点,让咱们再次回到咱们的代码:

1
var  time =  new  android.text.format.Time();

 

如今你了解了这个V8代码是这样运行的,即NativeScript注入了android.text.format.Time的 JavaScript对象,经过每个单独的元数据注入。下一个问题:如何将NativeScript里的JavaScript调用Time()转发到本 机android.text.format.Time()?

调用本地代码

NativeScript如何调用本机代码的答案就在于JavaScript虚拟机的API。咱们上次使用V8的API是注入全局变量。这一次,咱们将着眼于在JavaScript回调中调用给定的C++代码。

例如,JavaScript函数调用的代码 new android.text.format.Time(),V8会产生一个回调。也就是说V8有一个回调,让NativeScript拦截函数调用,而后用自定义的C ++代码执行一些动做,并返回一 个新的结果。

在Android中的情 况下,NativeScript运行的C++代码不能直接访问Java API,如android.text.format.Time。然而,Android的    JNI,或Java本地接口,提供了C++和Java之间的桥接能力,因此NativeScript使用JNI完成转发。在iOS中这个桥梁是没必要要的,由于C++代码能够直接调用Objective-C的API。

了解了这些,让咱们再回到代码:

var time = new android.text.format.Time();

 

咱们已经知道,这个代码在V8中运行;是由于NativeScript注入过对象,它知道什么是 android.text.format.Time;而且NativeScript中有这些基于元数据生成的API。咱们如今知道,当 Timer() 执行时,会发生下面的事情:

1)V8运行回调函数。

2)NativeScript运行时经过它的元数据知道,Time()调用须要实例化一个android.text.format.Time对象。

3)NativeScript运行时使用JNI来实例化一个android.text.format.Time对象并保持对它的引用。

4)NativeScript运行时将代理的Java Time对象转化成JavaScript对象返回。

5)控制返回的JavaScript代理对象为被存储起来的本地时间变量。

代理对象是在NativeScript中保持JavaScript对象与本地平台对象的人工映射。例如,让咱们来看看前面代码的下一行:

1
2
var  time =  new  android.text.format.Time();
time.set( 1, 0, 2015 );

 

基于所生成的元数据,NativeScript知道代理对象上的全部方法。在这种状况下,代码调用Timer对象的set()方法时。此方法会再次调用V8及其功能回调; 而后NativeScript经过Android JNI转发到Java时间对象上相应的方法调用。

这就是NativeScript在部分的工做原理。酷吧?

如今,还遗留下了一些很是复杂的部分,由于将Objective-C和Java对象转换成JavaScript对象可能会很麻烦,尤为是考虑到不一样的继承类型语言时。

咱们不打算深刻探讨这些问题的细节,NativeScript的另外一个特色让你没必要深刻到本地代码,好比:TNS模块。

TNS Modules

TNS modules是Telerik NativeScript modules的简写。跟Node模块同样,它一样使用CommonJS。所以若是你已经会用require()和exports对象,那么你就已经掌握了TNS模块。

TNS模块容许你将特定的本地调用抽象成平台无关的API,NativeScript自己提供了几十个这样的模块供您使用。举个例子,假设您须要在您的iOS / Android应用程序建立的文件。你可能在Android中要写如下代码:

new java.io.File( path );

 

一样在iOS里写下面的代码:

1
2
NSFileManager.defaultManager();
fileManager.createFileAtPathContentsAttributes( path );

 

可是若是你使用TNS文件系统模块,你的代码将是同样的,而没必要担忧的iOS / Android的内部实现细节:

var fs = require( "file-system" );var file = new fs.File( path );

 

更酷的是,你能够本身写TNS模块。好比这里有一个TNS模块,检索设备的操做系统版本:

1
2
3
4
5
// device.ios.jsmodule.exports = {
     version: UIDevice.currentDevice().systemVersion
} // device.android.jsmodule.exports = {
     version: android.os.Build.VERSION.RELEASE
}

 

此代码只检索一个版本属性,但它给你多少灵感?使用自定义TNS模块是微不足道的,跟在nodejs中使用NPM模块同样:

  1.  
    var device = require( "./device" );
  2.  
    console.log( device.version );

 

若是你已经熟悉了npm的使用,NativeScript模块很是容易编写,分发和使用。就我的而言,做为一个Web开发人员,原生的iOS和 Android代码让我惧怕,尤为是当Java / Objective-C的API文档扔在一块儿的功能,它下降了咱们跨平台开发的障碍。

相关文章
相关标签/搜索