J2V8的使用

js 执行引擎说明

浏览器发展历史

内核演变

Gecko(Netscape) - Trident(IE) - Gecko(firefox Mozilla) - Safari(webkit) - Chrome(Chromium) - Chrome(blink)

javascript引擎 / 什么是J2V8/为什么要使用J2V8

js引擎在android端的使用是怎样的体验?

v8 引擎是使用c++编写的,java要使用v8需要通过jni桥接,j2v8 就是起到这样的桥接作用

可用性+性能 => j2v8 > javaScrpitCore

J2v8 相对于 javaScriptCore 性能更优,具体优化了JNI的调用性能问题

J2v8对内存使用上的优化通过暴露api提供释放内存能力

内存管理

1手动管理的部分

j2v8 来说,以下对象必须手动释放:
自行创建的对象。例如 new V8Object() 创建的。
从 js 中主动获取的对象。例如 v8.getObject(xxx).
从 js 数组中提取的。例如 v8Array.getObject(0).
注意:
c++ 层作为参数传入到 java 的对象无需释放。因为它不是 java 自己创建的。
但是若传入的是数组,那么从数组中获取的对象必须释放,因为它是 java 主动获取的。
创建出的用作传给(或返回给) js 的对象必须释放,因为它是 java 创建的。

2 自动管理[MemoryManager]

使用MemoryManager 前

loDash = nodeJS.require(new File("/Users/irbull/node_modules/lodash"));
​
V8Object o1 = o("a", 1);
V8Object o2 = o("b", 2);
V8Object o3 = o("c", 3);
V8Object objects = (V8Object) loDash.executeJSFunction("assign", o1, o2, o3);
LoDashObject e1 = loDash(objects);
LoDashObject e2 = e1.e("values");
V8Function f = f((V8Object receiver, V8Array parameters) -> parameters.getInteger(0) * 3);
LoDashObject result = e2.e("map",f);
System.out.println(result);
​
loDash.release();
e1.release();
e2.release();
f.release();
o1.release();
o2.release();
o3.release();
result.release();
objects.release();</pre>

使用MemoryManager后

MemoryManager scope = new MemoryManager(v8); // 实例化 MemoryManager
loDash = nodeJS.require(new File("/Users/irbull/node_modules/lodash"));
​
V8Object objects = (V8Object) loDash.executeJSFunction("assign", o("a", 1), o("b", 2), o("c", 3));
LoDashObject result = loDash(objects).e("values").e("map",
 f((V8Object receiver, V8Array parameters) -> parameters.getInteger(0) * 3));
System.out.println(result);
​
scope.release(); // 释放

多线程

j2v8中使用runtime(v8 createV8Runtime)必须要统一线程使用(提供多线程的能力的同时保障多线程之间信息j2v8相关内容交互)

以下是AsyncTask的使用例子

 override fun onPreExecute() {
 super.onPreExecute()
 if (v8.locker.hasLock()){
 v8.locker.release() // 释放主线程的锁
 }
 }
 override fun doInBackground(vararg params: int?): int {
 v8.locker.acquire() // 子线程获得锁
 // 执行一些 v8 操作
 // ...
 v8.locker.release() // 释放子线程的锁
 return 1
 }
 override fun onPostExecute(result: int?) {
 super.onPostExecute(result)
 v8.locker.acquire() // 主线程重新获得锁
 }
}

Note: 如果不需要使用多线程的能力,可让全局处在一个线程中常驻执行

 //单线程池
 private ExecutorService executorService = new ThreadPoolExecutor(
 1,
 1,
 120,
 TimeUnit.SECONDS,
 new ArrayBlockingQueue<Runnable>(10));
​
public void commit(Runnable runnable){
 executorService.execute(runnable);
 }
​
 private JSCoreManager(){
 commit(new Runnable() {
 @Override
 public void run() {
 //j2v8相关内容包装类对象创建【j2v8 runtime(v8)】
 jsCore = new V8JSCore();
 }
 });
 }

其他渲染引擎回调的jsbridge线程采用相同策略即可,也可以使用类AsyncTask中提到的方式在切换前程前release ,进入线程时acquire,线程执行完成后 release然后重新acquire 当前的线程

关于v8线程acquire和release的源码如下 [希望后期有v8 源码走读]

//创建全局的V8 runtime 
V8 runtime = jsContext.getRuntime(); 
//jsContext 自行包装调用的 V8 静态方法
V8.createV8Runtime
​
public static V8 createV8Runtime(String globalAlias, String tempDirectory) {
 if (!nativeLibraryLoaded) {
 Object var2 = lock;
 synchronized(lock) {
 if (!nativeLibraryLoaded) {
 load(tempDirectory);
 }
 }
 }
 //确认native库有加载(j2v8包装了对应os的native库加载)com.eclipsesource.v8.LibraryLoader
 checkNativeLibraryLoaded();
 if (!initialized) {
 _setFlags(v8Flags);
 initialized = true;
 }
 //entrance to v8 reference
 V8 runtime = new V8(globalAlias);
 Object var3 = lock;
 synchronized(lock) {
 ++runtimeCounter;
 return runtime;
 }
 }

​
// V8 构造方法中会调用
 this.locker = new V8Locker(this);
​
 V8Locker(V8 runtime) {
 this.runtime = runtime;
 //这里和当前调用的线程绑定 之后执行对应runtime方法会checkThread
 this.acquire();
 }
​
 public void checkThread() {
 if (this.released && this.thread == null) {
 throw new Error("Invalid V8 thread access: the locker has been released!");
 } else if (this.thread != Thread.currentThread()) {
 throw new Error("Invalid V8 thread access: current thread is " + Thread.currentThread() + " while the locker has thread " + this.thread);
 }
 }
​
//如何在不同线程中切换参考AsyncTask中和以上描述做release 和 acquire的成对切换即可

java 调用js

1.直接执行javascript

可直接使用 executeXXXScript 相关api执行js代码得到返回值

 V8 runtime = V8.createV8Runtime(); // 创建 js 运行时
 int result = runtime.executeIntegerScript("" // 执行一段 js 代码
 + "var hello = 'hello, ';\n"
 + "var world = 'world!';\n"
 + "hello.concat(world).length;\n");
 System.out.println(result);
 runtime.release(true); // 为 true 则会检查并抛出内存泄露错误(如果存在的话)便于及时发现

2.声明js函数名方式

a)定义js全局函数

function add(a, b){
 return a + b
}

b) 方法调用执行

val arg = V8Array(v8).push(12).push(21) // 创建参数数组 arg为 V8Array[通过构造创建V8Array 根据前面规则最后需要手动释放]
val r = v8.executeIntegerFunction("add", arg) // 调用函数
arg.close() //别忘记释放对象

3.使用Function对象(v8Function)调用

使用场景:当js端传递给java 一个函数

if (v8.getType("add") == V8.V8_FUNCTION){ // 先判断 add 是不是一个函数
 val arg = V8Array(v8).push(12).push(21)
 val call = v8.getObject("add") as V8Function // 取得函数对象
 val r = call.call(null, arg) // 调用它
 arg.close()
 call.close()
}

js调用java

1.使用反射方式

public class Console {
​
 public void log(final Object message) {
 System.out.println("[INFO] " + message);
 }
​
 public void err(final Object message) {
 System.out.println("[ERROR] " + message);
 }
​
}  
​
private void registerConsoleLog() {
 //反射注入
 Console console = new Console();
 V8Object v8Console = new V8Object(runtime);
 runtime.add("console", v8Console);
 v8Console.registerJavaMethod(console, "log", "log", new Class<?>[]{Object.class});
 v8Console.registerJavaMethod(console, "err", "err", new Class<?>[]{Object.class});
 }

2.使用注册接口方式

2.1 普通无返回值接口注册
private void registerNavigate() {
 final JSCore jsCore = JSCoreManager.getInstance().getJSCore(1);
 //对应有不同的参数返回值回调接口(这里是无参返回)
 JavaVoidCallback nativesCallback = new JavaVoidCallback() {
 @Override
 public void invoke(V8Object receiver, V8Array parameters) {
 ThreadUtils.checkThread(((V8JSCore)jsCore).getRuntime(), "invoke navigate");
 if (parameters.length() > 0) {
 String method = (String) parameters.get(0);
 V8Array pathArr = (V8Array) parameters.get(1);
 String url = (String) pathArr.get(0);
 if (TextUtils.equals(method, "navigateTo")) {
 url =  "file:///storage/emulated/0/Download/mock/5d6f2af33d5e877599fdb12c/h5/index.html";
 final String finalUrl = url;
 ((V8JSCore)jsCore).getMainHandler().post(new Runnable() {
 @Override
 public void run() {
 mPageManager.navigateTo(finalUrl);
 }
 });
 }
 }
 }
 };
 V8 runtime = ((V8JSCore)jsCore).getRuntime();
 //注册 natives  方法 ,js调用该方法时 回调到nativesCallback 的 invoke 中
 runtime.registerJavaMethod(nativesCallback, "natives");
 }
2.2有参数据返回且带js返回callback到java端处理
private void testCallback() {
 JavaCallback callback = new JavaCallback() {
 @Override
 public Object invoke(V8Object receiver, V8Array parameters) {
 String value = "";
 String[] keys = receiver.getKeys();
 for (int i = 0; i < keys.length; i++) {
 Log.e("V8JSCore", "invoke(V8JSCore.java:245)" + keys[i]);
 }
//                String method = (String) parameters.get(0);
//                if (TextUtils.equals(method, "call")) {
 String first = receiver.getString("first");
 V8Function function = (V8Function) parameters.get(0);
 V8Array array = new V8Array(runtime).push("kiwi");
 Object call = function.call(null, array);
 Log.e("V8JSCore", "invoke(V8JSCore.java:241)" + first);
 value = first + "count";
//                }
 return value;
 }
 };
 runtime.registerJavaMethod(callback, "print");
//        natives('navigateTo', [url], cb);
 String str = "var array1 = [{first:'Ian'}, {first:'Jordi'}, {first:'Holger'}];\n" +
 "var cb = function(data, a, b) {" +
 " console.log(data);\n" +
 "};\n" +
 "        for ( var i = 0; i < array1.length; i++ ) {\n" +
 "            var result = print.call(array1[i], cb);\n" +
 //   "            var result = print.call(array1[i], array1[i], array1[i], cb);\n" +
 "            console.log(result);\n" +
 "        }";
 JSValue jsValue = evaluateScript(str);
 Log.e("V8JSCore", "testCallback(V8JSCore.java:259)" + jsValue.toString());
 }
2.3 不同写法对应的回调需要注意以下内容

1.js的方法写法不同,V8Object:receiver 不同
obj.func(params) 则这里的receiver是新建的这个func对应的V8Object
如果是通过func('methodName', params)写的,则receiver是全局的V8Object

2.对于V8Object可以通过keys方法查看对应挂载的元素 [在回调中测试挂载的key 如下]

String[] keys = receiver.getKeys();
 invoke(V8JSCore.java:245)global
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)log
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)console
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)natives
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)YmGlobal
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)request
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)__GLOBAL_DOCUMENT_CACHE@4
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)__INDIVIDUAL_ONE_VERSION_ev-store_ENFORCE_SINGLETON
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)__INDIVIDUAL_ONE_VERSION_ev-store
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)Base64
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)YmCore
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)Page
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)print
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)array1
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)cb
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)i
09-18 14:49:30.210 15938-15969/app.java.myapplication E/V8JSCore: invoke(V8JSCore.java:245)result
  1. parameters 为 V8Array,是j2v8封装好的数据传递参数数组,获取array中的元素使用
    getxxx(int index) [index对应参数位置, xxx为对应位置的数据类型]
    使用非function方式
val callback = V8Function(v8,
 { receiver: V8Object, parameters: V8Array -> System.out.println(parameters.getInteger(0)) })
val arg = V8Array(v8).push(1).push(2).push(callback)
v8.executeVoidFunction("add", arg)
//也可使用v8.add("print", callback) 方式注册

V8 对象和JSON转换

使用场景:js端向native端传递json object 时需要转换成String 【日志/json object 的数据解构】

public void log(final Object message) {
 if (message instanceof String) {
 System.out.println("[INFO] " + message);
 } else {
 //默认传过来的message 为 json Object 这里转换成json【通过JSON.stringify转换成js端传递过来的json object 为String】
 V8 runtime = ((V8JSCore) JSCoreManager.getInstance().getJsCore()).getRuntime();
 V8Object json = runtime.getObject("JSON");
 V8Array args = new V8Array(runtime).push(message);
 //result 为对应的jsonString
 String result = (String) json.executeFunction("stringify", args);
 Log.i("Console", "log(Console.java:22)" + result);
 args.release();
 json.release();
 }
 }
​```

//当前封装使用
```​
 public String v8ToJSON(Object v8Obj) {
 if (v8Obj == null) {
 return "";
 }
 if (v8Obj instanceof V8Object) {
 V8 runtime = ((V8JSCore) JSCoreManager.getInstance().getJsCore()).getRuntime();
 V8Object json = runtime.getObject("JSON");
 V8Array args = new V8Array(runtime).push(v8Obj);
 String result = (String) json.executeFunction("stringify", args);
 Log.i("Console", "log(Console.java:22)" + result);
 args.release();
 json.release();
 return result;
 }
 return v8Obj.toString();
 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342