当前位置: 首页 > 工具软件 > J2V8 > 使用案例 >

J2V8入门教程

都阳辉
2023-12-01

开始使用J2V8

J2V8是一套针对谷歌的V8 Javascript引擎的java绑定。J2V8的开发为Android平台带来了高效的Javascript的执行环境,taris.js 就是基于J2V8开发的。J2V8同时也可以运行在Windows、Linux、MacOS上。在本教程中我们将展示如何使用J2V8来执行javascript脚本


尽可能的“原始”

J2V8以性能与内存消耗为设计目标,如果一段Javascript代码的执行结果是一个32位整数,那么它可以直接作为一个原始类型被访问( access,存取)而不用先为它创建一个包装类型的实例(译注:而在其他java javascript引擎,比如在Rhino中,不能直接在java与javascript间直接存取数据,而必须先把它包装成一个java对象)。这对于64位的浮点数(doubles)和布尔类型的数据来说同样是适用的。

J2V8 也使用“懒加载”技术。也就是说,只有当Javascript的执行结果被访问(被使用到)的时候,它(执行结果)才会通过JNI被拷贝到java中。举例来说,如果javascript返回了一个大型的数组,这个数组的内容直到数组中的元素的被需要的时候才会被加载到java中。


本地对象句柄

J2V8仅仅是一组将V8引擎的API暴露给Java的java本地接口(JNI)。而V8引擎本身是使用C++写成的。为了访问到V8引擎,J2V8就使用了JNI对其进行封装。因为要与V8本地库进行交互,因此由此产生的C++内存必须得到妥善的管理。J2V8为管理本地对象句柄提供了一些提供了一些帮助,但是仍然需要开发者在这些对象不再被使用时,明确地调用release()来释放这些本地对象句柄。释放一个对象并不会把它从javascript中给释放掉(这是V8引擎自带的垃圾回收器要干的事),释放仅仅是移除了本地对象句柄。释放资源的规则非常简单:
1. 如果是你自己创建的本地对象,那么你必须释放它,只有一个例外,那就是,如果一个对象是通过返回语句传回来的话,系统会替你释放它。
2. 如果一个对象是由系统创建的话,你无需担心它们,只有一种情况需要操心,那就是,如果对象作为一个方法的调用的结果返回的话,那你就必须手动的释放它。

为了帮助你管理本地对象句柄,J2V8可以被设置成在程序结束时报告任何的内存泄露(译注:即如果有任何应该被手动释放而没有被释放的对象,在程序结束时会在控制台打印出类似”xx Object(s) still exist in runtime”的信息,这同样也可能引发一个 “java.lang.IllegalStateException: 1 Object(s) still exist in runtime”异常)。


多线程模型

Javascript本身是单线程的,而J2V8则强化了这一点。使用对同一个运行时(runtime)的访问都必须是来自同一线程的,这一点确保了在对某个Javascript运行时进行控制和使用的时候不会有出现竞态条件或者死锁的潜在风险。

尽管对在J2V8中对同一个运行时而言只能由同一个线程进行访问,你仍然可以创建多个运行时,然后为每一个运行时都存在于它们自己的线程之中。在这种多线程的模型之下,你可以很容易的实现 WebWorkers


获取J2V8

J2V8可以通过Maven Centeral获得。目前最新的版本是2.2.1(译注:在译者翻译本文时J2V8版本已经迭代到了4.5,添加了不少新的特性,仍然可以通过Maven获得)。以下的代码示例可以在你的pom.xml添加对J2V8的依赖。

<dependency>
  <groupId>com.eclipsesource.j2v8</groupId>
  <artifactId>j2v8_win32_x86_64</artifactId>
  <version>2.2.1</version>
</dependency>

上面的代码将会从Maven Centeral的服务器上获取64位windows版的J2V8。
(译注:如果你想要实用较新的版本的话注意将version替换成相应的版本号)

其他特定平台的运行时环境包括:

  • j2v8_win32_x86
  • j2v8_android_x86
  • j2v8_android_armv7l
  • j2v8_macosx_x86_64

(译注:当然也包括linux平台下的j2v8_linux_x86_64可以获取)


Hello, World!

为了在实践中理解J2V8,让我们一起来创建一段Hello World,这段脚本将两个字符串连接起来并且返回了结果字符串的长度

 var hello = 'hello, ';
 var world = 'world!';
hello.concat(world).length();

(译注:经测试,上面那段脚本在最新的J2V8中已经不能正常运行,原因是length不再被实现为一个函数而是被实现为一个属性,所以应当将hello.concat(world).length();改为hello.concat(world).length; 以下所有代码以修改过的代码给出,如果对原来的代码感兴趣的请参考文末给出的原文链接。)

要使用J2V8,首先你必须创建一个运行时环境,J2V8为此提供了一个静态工厂方法。在创建一个运行时环境时,同时也会加载J2V8的本地库。

public static void main(String[] args) {
 V8 runtime = V8.createV8Runtime();
 int result = runtime.executeIntegerScript(""
  + "var hello = 'hello, ';\n"
  + "var world = 'world!';\n"
  + "hello.concat(world).length();\n");
 System.out.println(result);
 runtime.release();
}

一单运行时环境创建好了,你就可以在上面执行javascript脚本了。为了执行脚本,它提供了多个基于不同返回值的执行方法。在这个例子里,我们使用了executeIntegerScript() 这个方法,因为脚本执行的结果是一个int类型的整数,并且不需要任何的类型转换和包装。当应用结束时,运行时环境必须被释放。


在Java中访问Javascript对象

使用J2V8你可以从Java中获取javascript对象的句柄(译注:换言之你可以在java中获取到javascript中的对象,并且对其进行操作)。接下来的嵌在java中javascript脚本代码演示了这一点:

public static void main(String[] args) {
  V8 runtime = V8.createV8Runtime();
  runtime.executeVoidScript(""
    + "var person = {};\n"
    + "var hockeyTeam = {name : 'WolfPack'};\n"      
    + "person.first = 'Ian';\n"
    + "person['last'] = 'Bull';\n"
    + "person.hockeyTeam = hockeyTeam;\n");
  // TODO: Access the person object
  runtime.release();
}

在执行完上面的脚本代码之后,你就可以在java中轻易的通过名称访问(获取)到javascript中的哪些全局变量了。在这个例子中,我们可以获取到person 对象,并且以这个对象为起点,周游(walking,遍历)它的对象图(译注:object graph,个人理解就与这个对象直接或者间接关联的其他对象的集合)。

  V8Object person = runtime.getObject("person");
  V8Object hockeyTeam = person.getObject("hockeyTeam");
  System.out.println(hockeyTeam.getString("name"));
  person.release();
  hockeyTeam.release();

因为V8Object只是底层javascript对象的句柄(译注:句柄,可以理解为对象的引用,反正是可以通过它来对对象进行操作的东西),所以可以对V8Object对象进行操作。举个例子,我们可以在java中为javascript增加新的属性,比如hockeyTeam.add("captain", person); 在进行了这一步操作之后,新添加的属性captain 可以在javascript中立刻被访问到。以下代码可以验证这一点:

assertTrue(runtime.executeBooleanScript("person === hockeyTeam.captain"));

V8Object也提供了其他一些有用的方法。Object.getKeys() 会返回所有关联到对象中的键(译注:javascript对象实质上可以看成一组key-value的property集合)。而Object.getType(String key) 则会返回key所对应对象的类型。以上两种方法结合起来,你可以动态地周游( traverse,遍历)一个复杂的对象。

最后,我们访问(获取)过的V8Object对象在我们不再使用它们的时候必须被释放掉。如果javascript底层仍然处于可访问的状态(译注:即没有超出它们的javascript作用域),那么它们在javascript脚本中依然是存在的。它们之所以需要我们手动的释放,是因为它们是作为方法调用的结果返回给我们的(参见第二节)。


V8Arrays(V8数组)

正向V8Object可以从Java中被访问一样,V8数组也可以通过JNI中间层在javascript与java之间传递。V8Array继承了V8Object,因此提供了相同的存取器方法(accessor / mutator methods,相当于setter / getter方法)。除此之外,V8Array的元素也可以通过索引来进行访问。V8Object和V8Array都遵循了流式编程模型 ,这使得创建新的javascript对象变得非常简单。

V8Object player1 = new V8Object(runtime).add("name", "John");
V8Object player2 = new V8Object(runtime).add("name", "Chris");
V8Array players = new V8Array(runtime).push(player1).push(player2);
hockeyTeam.add("players", players);
player1.release();
player2.release();
players.release();

当然,再使用完V8Array之后也必须释放掉它们。


调用Javascript函数

除了执行javascript脚本,在Java中也可以使用J2V8来调用javascript函数。这些javascript函数既可以是全局的,也可以是关联到每个对象之的;既可以返回一个结果,也可以没有返回值。请看以下javascript函数:

var hockeyTeam = {
     name      : 'WolfPack',
     players   : [],                 
     addPlayer : function(player) {
                   this.players.push(player);
                   return this.players.size;
         }
}

(注意:为了在最新版本中运行这段代码,已将return this.players.size(); 修改为return this.players.size;

为了在Java中调用上述对象中的函数,我们仅仅只需要一个hockeyTeam 的句柄。通过这个对象句柄,我们可以向执行脚本一样执行函数。然而,不同与脚本的是,可以传递给函数一个V8Array作为它的参数。

V8Array parameters = new V8Array(runtime).push(player1);
int size = hockeyTeam.executeIntegerFunction("addPlayer", parameters);
parameters.release();

参数数组的元素被映射为javascript函数的参数。参数数组元素的数量和在函数中声明的参数的数量不必相符,undefined 会被作为默认的值(译注:这其实是javascript的语言特性)。最后,参数数组同样需要被手动的释放。


总结
J2V8是流行的V8 javascript引擎的一组java绑定。它为Android和其他基于java的系统带来了高效的javascript执行环境。在本教程中我们学习了如何通过J2V8来与V8引擎进行交互。特别是,我们学习了如何与V8Object,V8Array进行交互,如何执行脚本和调用javascript中的函数。

接下来我们将学习到如何注册java回调函数给javascript。


译者说明

翻译本文大多数地方都采用直译,但是中文与英文之间的语言习惯的差异不可避免,技术文章也是如此,在原文中很多概念和词句都是在一个隐含的上下文环境之中,如果直接翻译,那么就会显得非常突兀、晦涩,因此这种情况下不得不采用意译或者注释,不求“雅”,但力求“信”、“达”,同时加入了个人的理解,可能会有所偏颇,望请海涵与指正,不甚感激,也欢迎相互探讨研究。
另,以上全部代码已在Linux x86_64以及Android arm、Android x86平台上进行测试,可以正常运行。


原文作者:Ian Bull
原文连接:http://eclipsesource.com/blogs/getting-started-with-j2v8/
译者:zyzz1995
联系方式:zyzz_work@163.com
转载请保留原文以及译文出处

 类似资料: