V8和J2V8

V8

V8是Google开源的JavaScript和WebAssembly引擎,被用于Chrome浏览器和Node.js等。和其它JavaScript引擎把JavaScript转换成字节码或解释执行不同的是,V8在运行JavaScript之前,会将JavaScript编译成原生机器码,并且使用内联缓存等方法来提高性能,V8引擎执行JavaScript的速度可以媲美二进制程序。V8采用 C++ 编写,可以独立运行,也可以嵌入到任何 C++ 程序中。

J2V8

J2V8是对V8引擎的一层Java封装,即J2V8借助JNI来实现Java层对C++层的访问,通过Java将V8的一些关键API暴露出来供外部程序使用。J2V8旨在为Java世界带来更加高效的JavaScript运行时环境,同时J2V8也可以在Windows、Linux、Mac OS等平台上运行。

J2V8的使用

我们在Android工程中演示J2V8如何具体使用。

创建Android工程

首先,我们通过Android Studio创建一个Android工程。

依赖J2V8

创建好Android工程之后,需要依赖J2V8库,如下:

// j2v8
implementation "com.eclipsesource.j2v8:j2v8:6.2.1@aar"

创建V8运行时实例

要使用J2V8,我们首先需要创建一个V8运行时实例,通过这个V8运行时实例,我们可以执行js脚本、注入js变量、注入原生方法等。

com.eclipsesource.v8.V8提供了一系列创建V8运行时实例的静态方法,如下:

public static V8 createV8Runtime() {
return createV8Runtime((String)null, (String)null);
} public static V8 createV8Runtime(String globalAlias) {
return createV8Runtime(globalAlias, (String)null);
} public static V8 createV8Runtime(String globalAlias, String tempDirectory) {
if (!nativeLibraryLoaded) {
synchronized(lock) {
if (!nativeLibraryLoaded) {
load(tempDirectory);
}
}
} checkNativeLibraryLoaded();
if (!initialized) {
_setFlags(v8Flags);
initialized = true;
} V8 runtime = new V8(globalAlias);
synchronized(lock) {
++runtimeCounter;
return runtime;
}
}

我们用最简单的方法创建一个V8运行时实例,如下:

val v8 = V8.createV8Runtime()

执行js脚本

com.eclipsesource.v8.V8提供了一系列执行js脚本的方法,如下:



这些方法涵盖了多种不同的使用场景,例如返回不同的数据类型、执行某些具体的js函数等等,我们可以根据需要选择合适的方法来执行js脚本。

注入变量

通过com.eclipsesource.v8.V8Object提供的一系列add方法,可以给V8Object实例注入js变量,如下:

v8.add("key1", "value1")
v8.add("key2", "value2")
v8.add("key3", "value3")

变量注入之后,我们可以在js中直接使用这些变量。

注入原生对象

J2V8支持注入原生对象,向js注入原生对象之后,在js中可以访问原生对象以及原生对象内部的方法和属性。

如何注入原生对象呢?我们以在js中调用原生代码输出日志的场景为例。

首先,在原生代码中定义一个Console类,如下:

/**
* 输出日志的类
*/
class Console { fun log(tag: String, message: V8Array) {
Log.d(tag, message.toString())
}
}

Console类实现了一个log方法,用于打印日志内容。

然后,创建Console类对象,如下:

val console = Console()

接下来,我们创建一个V8Object对象,并向V8运行时对象注入这个对象,对象名命名为console,如下:

val consoleObject = V8Object(v8)
v8.add("console", consoleObject)

再接下来,我们通过consoleObject注册原生方法。

这里,我们选择通过如下方法实现原生方法的注册:

public V8Object registerJavaMethod(Object object, String methodName, String jsFunctionName, Class<?>[] parameterTypes) {
return this.registerJavaMethod(object, methodName, jsFunctionName, parameterTypes, false);
} public V8Object registerJavaMethod(Object object, String methodName, String jsFunctionName, Class<?>[] parameterTypes, boolean includeReceiver) {
this.v8.checkThread();
this.checkReleased(); try {
Method method = object.getClass().getMethod(methodName, parameterTypes);
method.setAccessible(true);
this.v8.registerCallback(object, method, this.getHandle(), jsFunctionName, includeReceiver);
return this;
} catch (NoSuchMethodException var7) {
throw new IllegalStateException(var7);
} catch (SecurityException var8) {
throw new IllegalStateException(var8);
}
}

这是一种基于反射的方法,其中,第一个参数传原生对象即console,第二个参数传原生方法名称即log,第三个参数传js方法的名称,这里和原生方法名称保持一致(当然也可以不一致),最后一个参数传Class类型的数组,表示方法参数的类型,数组元素和方法参数类型必须一一对应。如下:

consoleObject.registerJavaMethod(
console, "log", "log", arrayOf(
String::class.java, V8Array::class.java
)
)

最后,一定要记得关闭手动创建的V8Object对象,释放native层的内存,否则会报内存泄露方面的错误。如下:

consoleObject.close()

以上步骤完成之后,我们就可以在js中愉快地打印日志了,我们使用V8运行时对象执行打印日志的js脚本,将此前注入的js变量打印出来,如下:

v8.executeScript("console.log('myConsole', [key1, key2, key3]);")

在Android Studio的Logcat中将会输出如下信息:

2022-12-25 13:21:48.932  4556-4556  myConsole               com.xy.j2v8                          D  value1,value2,value3

注入原生方法

上面在讲注入原生对象的过程当中,其实也包含了原生方法的注入,在注入原生方法的时候,除了上面讲到的方法,还有更简单的方法。

com.eclipsesource.v8.V8Object还提供了如下方法:

public V8Object registerJavaMethod(JavaCallback callback, String jsFunctionName) {
this.v8.checkThread();
this.checkReleased();
this.v8.registerCallback(callback, this.getHandle(), jsFunctionName);
return this;
} public V8Object registerJavaMethod(JavaVoidCallback callback, String jsFunctionName) {
this.v8.checkThread();
this.checkReleased();
this.v8.registerVoidCallback(callback, this.getHandle(), jsFunctionName);
return this;
}

两个方法只有第一个参数不一样,com.eclipsesource.v8.JavaVoidCallback表示原生方法无返回值,com.eclipsesource.v8.JavaCallback表示原生方法有返回值,返回类型是java.lang.Object类型的。

两个方法的第二个参数表示要注入的方法的名称。

我们使用第一个方法,来注入一个在js中弹Toast的方法,代码如下:

v8.registerJavaMethod({ v8Object, v8Array ->
Toast.makeText(this, "$v8Object, $v8Array", Toast.LENGTH_SHORT).show()
}, "toast")

接下来,我们执行调用toast的js脚本:

v8.executeJSFunction("toast", "Hello, I am a toast!")

脚本执行之后,屏幕中将弹出文本内容为"Hello, I am a toast!"的toast。

线程模型

JavaScript本身是单线程的,J2V8也严格遵循这一点,即对单个V8运行时的所有访问必须来自同一线程,换句话说就是:V8运行时实例在哪个线程被创建,就只能在哪个线程被使用。

如果在一个线程创建了V8运行时实例,在另外一个线程中直接访问这个实例,会抛异常,如下:

Process: com.xy.j2v8, PID: 27274
java.lang.Error: Invalid V8 thread access: current thread is Thread[Thread-3,5,main] while the locker has thread Thread[main,5,main]
at com.eclipsesource.v8.V8Locker.checkThread(V8Locker.java:99)
at com.eclipsesource.v8.V8.checkThread(V8.java:840)
at com.eclipsesource.v8.V8.executeScript(V8.java:715)
at com.eclipsesource.v8.V8.executeScript(V8.java:685)
at com.xy.j2v8.MainActivity.onCreate$lambda$1$lambda$0(MainActivity.kt:27)
at com.xy.j2v8.MainActivity.$r8$lambda$jWdwVCxaGZnvyZS9q138fD71tFk(Unknown Source:0)
at com.xy.j2v8.MainActivity$$ExternalSyntheticLambda2.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:929)

J2V8单线程模型确保了在使用单个V8运行时时不存在多线程问题,例如线程之间资源竞争,出现死锁等。

GitHub

XyJ2V8

初学J2V8的更多相关文章

  1. DDD初学指南

    去年就打算总结一下,结果新换的工作特别忙,就迟迟没有认真动手.主要内容是很多初学DDD甚至于学习很长时间的同学没有弄明白DDD是什么,适合什么情况.这世界上没有银弹,抛开了适合的场景孤立的去研究DDD ...

  2. gulp初学

    原文地址:gulp初学 至于gulp与grunt的区别,用过的人都略知一二,总的来说就是2点: 1.gulp的gulpfile.js  配置简单而且更容易阅读和维护.之所以如此,是因为它们的工作方式不 ...

  3. 初学seaJs模块化开发,利用grunt打包,减少http请求

    原文地址:初学seaJs模块化开发,利用grunt打包,减少http请求 未压缩合并的演示地址:demo2 学习seaJs的模块化开发,适合对seajs基础有所了解的同学看,目录结构 js — —di ...

  4. 初学Vue2.0--基础篇

    概述: 鉴于本人初学,使用的编译器是webStorm,需添加对VUE的支持,添加方法可以参考 http://www.jianshu.com/p/142dae4f8b51. 起步: 1. 扎实的 Jav ...

  5. 初学Python

    初学Python 1.Python初识 life is short you need python--龟叔名言 Python是一种简洁优美语法接近自然语言的一种全栈开发语言,由"龟叔&quo ...

  6. Javascript初学篇章_5(对象)

    对象 Javascript是一种面向对象的语言,因此可以使用面向对象的思想来进行javascript程序设计对象就是由一些彼此相关的属性和方法集合在一起而构成的一个数据实体.举个例子,一只猫是个对象, ...

  7. 初学Objective-C语言需要了解的星星点点

             其实大多数开发初学者都有一些相同的特点,可以说是一种“职业病”.Most有其他平台开发基础的初学者,看到Xcode就想摩拳擦掌:看到Interface Builder就想跃跃欲试:而 ...

  8. matlab初学之句柄

    文章出处:http://www.cnblogs.com/CBDoctor/archive/2012/04/06/2434072.html 在matlab中,每一个对象都有一个数字来标识,叫做句柄.当每 ...

  9. 初学HTML5、初入前端

    学习HTML5是一个漫长的过程,当中会遇到很多技术与心态上的变化.刚开始学习,我们不能发力过猛,需要一个相对稳定的状态去面对.多关注一些自己感兴趣的网站和技术知识,建立自己的信心与好奇心,为以后的学习 ...

  10. Python初学的易犯错误

    当初学 Python 时,想要弄懂 Python 的错误信息的含义可能有点复杂.这里列出了常见的的一些让你程序 crash 的运行时错误. 1)忘记在 if , elif , else , for , ...

随机推荐

  1. spider_使用request库进行get传参

    """使用requests库 在这里爬取百度搜索的端午节页面(使用request库进行get传参)"""import requestsimp ...

  2. Java-AES256加密Util

    1 public class AES256Util { 2 3 /** 4 * 密钥, 256位32个字节 5 */ 6 public static final String DEFAULT_SECR ...

  3. flutter List使用

    _tabbarTitile.map((e){ return Tab( text: e, ); }).toList(),

  4. 树莓派4B的Node-Red编程(一)

    一.树莓派烧写 二.Node-Red 环境搭建 (一)安装Node.js (二)安装Node-Red (三)启动服务:win+R输入CMD:输入Node-red. (四)进入浏览器127.0.0.1: ...

  5. el-table 固定列错位问题

    1. 问题描述:el-table使用固定列时,使用keep-alive后页面切换导致该列错位. 2. 解决方法:使用el-table的doLayout方法对表格进行重新布局 activated() { ...

  6. 【Hive】数据倾斜原因及解决方法汇总

    1)数据倾斜根本原因:由于数据分布不均匀,导致map端读取的数据分布不均匀(数据长尾分布),从而使得map处理的数据量差异过大. (2)解决思路:Hive是分阶段执行的,map处理数据量的差异取决于上 ...

  7. android studio真垃圾

    开发人员写代码就行了,想用你写代码,安装配置费死个劲! 我不是针对你,除了visual studio ,所有的IDE都是垃圾.

  8. 20200924--图像相似度(奥赛一本通P92 5多维数组)

    给出两幅相同大小的黑白图像(用0-1矩阵)表示,求它们的相似度.说明:若两幅图像在相同位置上的像素点颜色相同,则称它们在该位置具有相同的像素点.两幅图像的相似度定义为相同像素点数占总像素点数的百分比. ...

  9. uniapp的子组件,当父组件下来刷新时,子组件一同刷新。

    最近做uniapp项目的时候,使用给父组件一个刷新属性,父组件有效果,但是子组件没有反应,网上查找了很多方法,最终采取通过刷新时,函数传值,子组件监听的方式.具体做法可以参照:https://blog ...

  10. 2月28日Android开发学习

    界面显示与逻辑处理 Android Studio利用XML标记描绘应用界面,使用java代码书写程序逻辑. 把App界面设计与代码逻辑分开的好处 使用XML文件描述App界面,可以很方便地在Adroi ...