js 执行引擎说明
内核演变
Gecko(Netscape) - Trident(IE) - Gecko(firefox Mozilla) - Safari(webkit) - Chrome(Chromium) - Chrome(blink)
javascript引擎 / 什么是J2V8/为什么要使用J2V8
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();
使用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(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
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();
}