Android 基于 J2V8 运行 JavasScript 实践

V8 引擎是由 Google 开源的 JavaScript 引擎,Chrome 就是基于 V8 开发,V8 是跨平台的,J2V8 基于 V8 进行开发,使得 js 代码能够在 Android 平台上脱离 WebView 运行。目前,也有很多关于 Android J2V8 的文章,不过讲解不是特别细(可能也是我太菜了,看完了之后,依然遇到很多问题),自己在调研的过程中遇到很多坑,所以这里记录一下,本文主要记录整个 J2V8 框架的使用方法,以及一些坑。

一、Webpack 打包

通常业务逻辑的 js 文件是有多个的,我们需要借助一些打包工具将多个文件打包成一个 js 文件供 J2V8 使用,我们可以使用 Gulp、Webpack、Browserify,本文主要讲 Webpack 的使用。
主要流程如下:

编写基础逻辑并通过 module.exports 对外部提供

编写 index.js 入口文件

...
module.exports = {simpleFunc, complexFunc
};

** 编写webpack.config打包配置**

module.exports = {entry: './src/example/index.js',output: {library: 'libExample',                 // j2v8 加载该libpath: path.resolve(__dirname, 'dist'),filename: 'example.js',                // 导出指定命名的 js 文件 },...
};

执行 webpack 打包命令

./node_modules/.bin/webpack --config webpack.config.js

二、运行 JavaScript

到这里我们已经有一份通过 Webpack 打包好的 js 文件了,要在 j2v8 中运行 JavaScript 文件,使用以下步骤:

1、创建一个 V8 实例

V8 v8 = V8.createV8Runtime();

2、读取 JavaScript 文件

var scriptStr = String(Files.readAllBytes(Paths.get("example.js")))

3、在 V8 实例中执行 JavaScript 代码

v8.executeScript(scriptStr);

这一步已经让整个 js 文件运行起来,但我们还不能调用我们的方法

4、读取指定模块

由于是通过 Webpack 打包,在 Webpack 的 output.library 配置,选项用于将打包后的代码作为一个库(library)暴露出去,以便其他应用程序或模块可以使用它。

val rootLib =v8.getObject(libName); // 这里的 libName 就是 output.library 配置的名字

如果是访问模块的导出对象中的子对象,那么继续:

val subLib =rootLib.getObject(subLibName); // 这里的 subLibName 是 index 文件中 module.exports 里面的模块名

如果子对象还有子对象,继续.getObject 即可

5、运行指定方法

接下来就简单了,直接通过如下方法执行 js 中的指定方法

    public void executeVoidFunction(String name, V8Array parameters)public String executeStringFunction(String name, V8Array parameters) public double executeDoubleFunction(String name, V8Array parameters) public int executeIntegerFunction(String name, V8Array parameters)……

V8Object 提供了很多数据格式调用,不过都差不多,主要是在返回值那里帮你实现了数据的转化,如果不想用转化好的格式,希望自己来操作的话,使用public V8Object executeObjectFunction() 拿到返回值,自己去转化即可

6、释放资源

由于 V8 运行消耗较多的资源,执行结束的时候要将在过程中创建的所有的资源释放,避免导致内存泄漏。
V8提供了close方法,如果只使用 v8.close() 进行释放,或者未关闭过程中有用到 v8 runtime 的变量都会报如下错误,正确的做法是将所有资源进行关闭。

java.lang.IllegalStateException: 3 Object(s) still exist in runtime

三、进阶

通过以上的方式已经能执行很多逻辑了,但在实践过过程中发现:如何 js 的返回值是 Promise 的话不会等到最终的结果给我们,而是直接返回了一个 Promise 对象,以及看不到 console.log 打印的日志…… 诸如此类的问题需要解决,这里主要讲讲这两种方法的实现。

注册 Native 插件

J2V8 是一个基于 V8 引擎的 Java 库,它允许在 Java 中执行 JavaScript 代码。由于 J2V8 是在 Java 中运行的,它没有直接访问浏览器或控制台的能力,因此无法直接使用 console.log 函数来输出日志,总结 J2V8 不能实现以下功能:

  • 浏览器 API:j2v8 是在 Java 中运行的,因此无法直接访问浏览器 API,如 DOM、BOM 等。这意味着 j2v8 无法直接操作网页内容、处理事件等
  • 文件系统访问:j2v8 在 Java 中运行,无法直接访问文件系统。如果需要访问文件系统,需要使用 Java 提供的文件操作 API。
  • 定时器:JavaScript 中有多种定时器函数,如 setTimeout、setInterval 等,可以在指定时间后执行代码。但 j2v8 无法实现这些定时器函数,因为它无法直接访问系统的计时器。
  • Web Worker:Web Worker 是 JavaScript 中的一个特殊对象,可以在后台线程中执行代码,以避免阻塞主线程。但 j2v8 无法实现 Web Worker,因为它无法直接访问操作系统的线程。
  • Node.js API:j2v8 主要是为了在 Java 中执行浏览器端的 JavaScript 代码而设计的,因此无法直接访问 Node.js API。如果需要在 Java 中执行 Node.js 代码,可以考虑使用 Nashorn 等其他工具。

这里是 console.log的一个简单实现:

V8Object 是 J2V8 中的一个类,它代表了一个 JavaScript 对象,对于 console.log 我们可以将 console 看作一个对象,其有一个叫 log 的方法,要实现在 js 中打印日志到 Android Studio 控制台,如下即可:

class ConsolePlugin {fun log(message: Any) {Log.d("ConsolePlugin", message.toString())}fun register(v8: V8) {val v8Console = V8Object(v8)// 第一个 log 表示 在 Java 中该方法的名字,第二个 log 表示在 JavaScript 中调用的名字 v8Console.registerJavaMethod(this, "log", "log", arrayOf<Class<*>>(Any::class.java))v8Console.setWeak()// 将含有叫"log"方法的一个对象加到运行环境中,该对象被命名为 "console"v8.add("console", v8Console)}
}ConsolePlugin().register(v8)

具体代码可参考:J2V8_tutorial

执行返回值是 Promise 类型的方法

之前将的方法调用都是返回数据为基础类型,由于在 Java/kotlin 中没有Promise类型的方法,所以对于 Promise 方法我们需要进行一些特殊处理,我们通过使用 CountDownLatch 可以来实现一个 “异步变同步” 的操作,我们需要考虑的是如何接受到 resolve rejcet的调用,js 中 Promise 的方法使用如下:

PromiseMethod().then((result)=>{// success got result}).catch((e)=>{// error...});

在 J2V8中一样的实现

获取返回的 Promise 对象

 val promiseObj = v8.executeFunction(functionName, v8Array) as V8Object

**执行 Promise 对象的 then 和 catch 方法 **

jsPromise.apply {val onResolveParameter = V8Array(v8).push(onResolve)val onRejectParameter = V8Array(v8).push(onReject)executeVoidFunction("then", onResolveParameter)executeVoidFunction("catch", onRejectParameter)....}

其中 onResolve

val onResolve = V8Function(jsRuntime) { receiver, parameters ->……}

具体代码可参考:J2V8_tutorial

四、总结

以上基本上能解决大部分 Android 调用 js的代码逻辑了,这里对整体执行的流程进行一个总结

1、通过 webpack 对多个 .js 文件打包
2、初始化 V8 环境并加载 .js 文件
3、注册 Java 方法,供 js 进行调用
4、读取指定的模板
5、执行目标 js 方法,并释放 v8 执行过程中产生的资源

踩过的一些坑

1、java.lang.UnsupportedOperationException: StartNodeJS Not Supported.

这个库有一个 NodeJS.createNodeJS()方法,以为是完美结合 NodeJs 的,查了下不太支持 Android,不过也有人提出解决方法:https://stackoverflow.com/questions/42574824/how-to-use-nodejs-in-android-using-j2v8

2、java.lang.IllegalStateException: 3 Object(s) still exist in runtime

这是调用 `v8.close`` 总是会遇到的问题,一定需要确保使用了 v8 Runtime 过程变量有被释放掉,可能有时候不知道具体哪个变量没有被释放

3、setTimeout、setInterval 无效

这是我最开始遇到的问题,简单想着“既然能执行js代码,那 setTimeout、setInterval 这些方法都是 js 最普通的方法应该没问题吧”,如果有一些平时在 js 很常见的操作如果无法执行,最好 check 一下 J2V8 是否支持

4、Undefined 相关

虽然源码里面通过了一个 Undefined 的类,但是不能直接使用,如果方法返回的 Undefined,通过 V8ObjectisUndefined() 去判断

引用

[1]J2V8 https://eclipsesource.com/blogs/tutorials/getting-started-with-j2v8/

[2] Registering Java Callbacks with J2V8 https://eclipsesource.com/blogs/2015/06/06/registering-java-callbacks-with-j2v8/

[3] Simple JS in Node.js https://yenhuang.gitbooks.io/android-development-note/content/wrap-js-library/simple-js-with-nodejs.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/129889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java int char string互相转换和判断

java int 转 ascii码 数字-> ascii码 System.out.println(7 0);ascii码-> 数字 System.out.println(9 - 0);char ch 5; ch (char)(ch -0); //实际计算时是默认将char类型的ch转换为int类型98; 然后将 97 强转为 a System.out.println(ch); // 5 System.out.println…

@RunWith(SpringRunner.class)注解的作用

通俗点&#xff1a; RunWith(SpringRunner.class)的作用表明Test测试类要使用注入的类&#xff0c;比如Autowired注入的类&#xff0c;有了RunWith(SpringRunner.class)这些类才能实例化到spring容器中&#xff0c;自动注入才能生效 官方点&#xff1a; RunWith 注解是JUnit测…

【数据结构】深入浅出理解快速排序背后的原理 以及 版本优化【万字详解】(C语言实现)

快速排序 快速排序递归实现前言一、Hoare版本&#xff08;一&#xff09;算法运行图例&#xff08;二&#xff09;算法核心思路&#xff08;三&#xff09;算法实现步骤&#xff08;1&#xff09;单趟&#xff08;2&#xff09;多趟 &#xff08;四&#xff09;码源详解 递归实…

npm install报 ERESOLVE unable to resolve dependency tree

三四年前的一个项目&#xff0c;打开&#xff0c;npm install 一下&#xff0c;结果报 ERESOLVE unable to resolve dependency tree。 以前install都一切顺利&#xff0c;现在就不行&#xff0c;那很大的可能是npm的版本不同。 PS D:\workSpace\code\*-admin-ui-master> n…

LeetCode热题100——滑动窗口

滑动窗口 1. 无重复字符的最长序列2. 找对字符中所有字母的异位词 1. 无重复字符的最长序列 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 // 题解&#xff1a;利用set集合查询滑窗内是否存在重复字符 int lengthOfLongestSubstring(string…

单元测试反射注解

单元测试 就是针对最小的功能单元(方法)&#xff0c;编写测试代码对其进行正确性测试。 咱们之前是如何进行单元测试的&#xff1f;有啥问题 &#xff1f; Junit单元测试框架 可以用来对方法进行测试&#xff0c;它是由Junit公司开源出来的 具体步骤 Junit框架的常见注解…

『亚马逊云科技产品测评』活动征文|占了个便宜,12个月的免费云服务器

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 在群里看到有小伙伴说亚马逊可以免费试用服务器&#xff0c;这种好事不得…

Jenkins自动化部署简单配置

下载安装jenkins 安装Jenkins步骤 点击Next的时候会有jdk版本跟Jenkins版本不符合的情况 1. 看下任务管理器内Jenkins服务是否启动&#xff0c;在浏览器里面输入localhost:2023&#xff08;端口号是安装时输入的&#xff09; 2. 根据路径找到放置密码的文件&#xff08;C…

JS冒泡排序(介绍如何执行)

想必大家都多多少少了解过一点排序&#xff0c;让我为大家介绍一下冒泡排序吧&#xff01; 假设我们现在有一个数组[2&#xff0c;4&#xff0c;3&#xff0c;5&#xff0c;1] 我们来分析一下&#xff1a; 1.一共需要的趟数 我们用外层for循环 5个数据我们一共需要走4躺 长度就…

用golang实现一个基于interface的多态示例,展示其使用场景和优劣性。

以下是一个简单的基于interface的多态示例&#xff0c;该示例展示了如何通过使用interface来实现多个不同类型的结构体的共同行为。具体示例如下&#xff1a; package mainimport "fmt"type Animal interface {Speak() string }type Dog struct {Name string }func …

ngixn的指令

Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;它可以处理静态资源、动态内容、负载均衡、反向代理和HTTP缓存等任务。本文将详细介绍在CentOS上安装和配置Nginx服务器&#xff0c;并讲解Nginx常用指令。 安装Nginx 在CentOS上安装Nginx非常简单&#xff0c;只需要执行…

Yolov8改进CoTAttention注意力机制,效果秒杀CBAM、SE

1.CoTAttention 论文地址&#xff1a;2107.12292.pdf (arxiv.org) CoTAttention网络是一种用于多模态场景下的视觉问答&#xff08;Visual Question Answering&#xff0c;VQA&#xff09;任务的神经网络模型。它是在经典的注意力机制&#xff08;Attention Mechanism&#xf…

自动化测试中验证码问题如何解决?

经常会被问到如何解决验证码的问题&#xff0c;在此记录一下我所知道的几种方式。 对于web应用来说&#xff0c;大部分的系统在用户登录时都要求用户输入验证码&#xff0c;验证码的类型的很多&#xff0c;有字母数字的&#xff0c;有汉字的&#xff0c;甚至还要用户输入一条算…

让SOME/IP运转起来——SOME/IP系统设计(上)

什么是SOME/IP&#xff1f; SOME/IP&#xff08;Scalable service-Oriented MiddlewarE over IP&#xff09;是AUTOSAR应用层的协议&#xff0c;是基于IP协议的面向服务的可拓展性的中间件。 SOME/IP中主要定义了&#xff1a; 数据的序列化&#xff1a;SOME/IP支持的数据类型…

Web开发介绍详细介绍

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…

SpringBoot集成JPA实现分页和CRUD

SpringBoot集成JPA实现分页和CRUD 文章目录 SpringBoot集成JPA实现分页和CRUDpom.xmlapplication.propertiesaddCategory.jspeditCategory.jsphello.jsplistCategory.jspCategoryCategoryDAOCategoryServiceCategoryServiceImplPage4NavigatorRedisConfigCategoryControllerHel…

Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据存储系统&#xff0c;它提供了持久化、高性能和高可用性的数据存储。它支持多种数据结构&#xff0c;如字符串、哈希表、列表、集合、有序集合等。Redis可以用作数据库、缓存&#xff0c;甚至可以用…

怎么看待工信部牵头推动人形机器人发展

1&#xff0c;工信部牵头&#xff0c;而不是科技部牵头&#xff0c;有些蹊跷&#xff0c;科技部应该比工信部更了解科技发展趋势&#xff0c;工信部比科技部更了解工业发展趋势&#xff0c;这是用工业倒逼科技进步。 2&#xff0c;人的优势不是身体&#xff0c;而是精神&#…

python综合应用

猜数字游戏 # import random # 猜数字游戏 随机一个数字 提示玩家 # a random.randint(1, 100) # flag1 1 # flag2 100 # while True: # b int(input("请猜一个%d-%d的数字&#xff1a;" % (flag1,flag2))) # if b > a: # flag2 b # …

【微信小程序】自定义组件(一)

自定义组件 组件的创建与引用1、创建组件2、引用组件3、全局引用VS局部引用4、组件和页面的区别 样式1、组件样式隔离2、组件样式隔离的注意点3、stylelsolation的可选值 数据、方法和属性1、data数据2、methods方法3、properties4、data和properties区别5、使用setData修改pr…