v8 引擎,不必多说,大名鼎鼎,迄今最快的js引擎。
这么好的东西,如果尽在浏览器中使用,太浪费了,也希望用到其他地方,比如一些客户端开发中。
闲言少讲,这就开始
下载源码
去官方网站:https://code.google.com/p/v8/
我喜欢用git,所以从这里下载
git clone git://github.com/v8/v8.git v8 && cd v8
编译
我为android平台编译的,折腾了好久,记录一些重点。你首先要看这个:http://code.google.com/p/v8/wiki/BuildingWithGYP
其实关键是
make dependencies
这需要花很长时间(呃,没办法,国内网速不给力,我下载了好几次)。。。
然后,在android上编译(其他平台?so easy,看文档), 你可以先看看D8OnAndroid (是android平台的一个js命令行吧,不明觉厉)的编译,不过没什么用处。
我自己的编译方法,是用交叉编译的
Cross-compiling
Similar to building with Clang, you can also use a cross-compiler. Just export your toolchain (CXX/LINK environment variables should be enough) and compile. For example:
export CXX=/path/to/cross-compile-g++ export LINK=/path/to/cross-compile-g++ make arm.release
(编译等待中.....)
完了之后,你就可以使用了。
在android上使用
首先,创建一个工程(怎么创建?这个。。。还是自己找吧),一个Activity工程就行,然后在工程目录下创建一个jni目录。
具体怎么使用ndk,我就不说,自己找吧,我说重点的:
1. 拷贝头文件和库文件
为了方便,我把头文件和库文件放在jni下的include和lib下。头文件直接从v8/include文件夹下拷贝就可以,库文件比较麻烦一些,必须拷贝这些:
libicudata.a libicui18n.a libicuuc.a libv8.a libv8_snapshot.a
你可以从v8/out目录下查找(用find吧,反正记不住具体在什么位置了) 少了编译时就会报找不到符合的错误(说多了都是泪啊)
2. 注意Android.mk的写法
在写Android.mk的时候,必须注意:
1. 库的引用顺序很重要,顺序错了,一样找不到符号
2. 要用stl,
Android.mk和Application.mk(STL需要的)的代码如下,给大家和参考,省得走弯路
Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_CFLAGS += -I$(LOCAL_PATH)/include LOCAL_LDFLAGS += $(LOCAL_PATH)/lib/libv8.a $(LOCAL_PATH)/lib/libv8_snapshot.a $(LOCAL_PATH)/lib/libicui18n.a $(LOCAL_PATH)/lib/libicuuc.a $(LOCAL_PATH)/lib/libicudata.a /<path>/android-ndk-r9d//sources/cxx-stl/stlport/libs/armeabi/libstlport_static.a LOCAL_MODULE := jswrapper LOCAL_LDLIBS := -llog LOCAL_SRC_FILES := registers.cpp JavaScriptContextWrap.cpp javautils.cpp JsJavaClassProxy.cpp javawrap.cpp #sample.cpp sample-jni.cpp LOCAL_ALLOW_UNDEFINED_SYMBOLS := true LOCAL_WHOLE_STATIC_LIBRARIES += android_support include $(BUILD_SHARED_LIBRARY) $(call import-module, android/support)
(我很笨啊,stl不全路径链接就找不到,呃,难道上天又一次中意我,要给我磨练吗......)
记得lib的顺序和stl噢。。。
Application.mk
APP_CFLAGS += -fexceptions APP_CFLAGS += -frtti APP_STL := stlport_static #gnustl_static
编译用ndk-build就好了。
代码编写
至于jni部分的代码,我就不写了,不懂的baidu和google,你懂的。
头文件?用一个 "v8.h"就好了。
1. 初始化
v8::V8::InitializeICU();
(完了?是的,就这么简单)
2. 创建Isolate和Context
isolate_ = v8::Isolate::New(); isolate_->Enter(); v8::HandleScope handle_scope(isolate_); v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_); //create Log object v8::Handle<v8::ObjectTemplate> log = v8::ObjectTemplate::New(isolate_); log->Set(v8::String::NewFromUtf8(isolate_, "e"), v8::FunctionTemplate::New(isolate_, js_func_log_e)); log->Set(v8::String::NewFromUtf8(isolate_, "i"), v8::FunctionTemplate::New(isolate_, js_func_log_i)); log->Set(v8::String::NewFromUtf8(isolate_, "v"), v8::FunctionTemplate::New(isolate_, js_func_log_v)); log->Set(v8::String::NewFromUtf8(isolate_, "d"), v8::FunctionTemplate::New(isolate_, js_func_log_d)); global->Set(v8::String::NewFromUtf8(isolate_, "Log"), log); //TODO others global->Set(v8::String::NewFromUtf8(isolate_, "import_java_class"), v8::FunctionTemplate::New(isolate_, js_func_import_java_class)); context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
内容有点多哈。别着急,听我慢慢道来。
首先,创建一个v8::Isolate, 用v8::Isolate::New()。 isolate是什么东东?这个嘛,其实我也不太清楚,大概类似一个虚拟机,所有的javascript对象都在这个isolate上,不太的isolate老死不相往来。
然后需要调用一下isolate_->Enter(),当然要销毁时,调用一下isolate_->Leave()。
对了,v8是c++的,成员方法首字母都是大写的。
第二,我们可以创建一个ObjectTemplate了。ObjectTemplate望文生义,是对象的模版,怎么说呢,对应到C++和java里面,你可以认为是静态类,有一堆静态方法。
各位请先看最后一句:v8::Context::New(isolate_, NULL, global)。这句话注册一个全局的模版,也就是说,如果我在global中注册一个print函数,那么可以直接在js中这样写:
print ("Hi, I\'m from native");
我们的例子里面,是注册了一个Log的子ObjectTempl,Log又包含e, i, v, d几个方法,其实就是对应的android.util.Log.e, i, v, d。
注册方法是调用Set,C++函数指针需要用
v8::FunctionTemplate::New
来包装。这些知道就行了,不必深究(其实我也不懂)
还有一点必须说明:上面的代码实际上是一个类的一部分,这里有两个成员变量:
v8::Persistent<v8::Context> context_; v8::Isolate* isolate_;
isolate直接使用指针,而Context必须用Persistent,这是一个可以持久保持对象的指针(防止被v8当垃圾回收了),使用方法是用 Rest:
context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
3. 定义一个函数
凡事先看代码:(我用js_func_log_x来实现js_func_log_i等函数的公共部分)
void js_func_log_x(int level, const v8::FunctionCallbackInfo<v8::Value>& args) { if (args.Length() == 0) { return ; } //get tag char szTag[1024] = ""; { v8::HandleScope handle_scope(args.GetIsolate()); v8::String::Utf8Value str(args[0]); const char* cstr = ToCString(str); strcpy(szTag, cstr); } char szLog[1024*8] = ""; bool first = true; int len = 0; for (int i = 1; i < args.Length(); i++) { v8::HandleScope handle_scope(args.GetIsolate()); if (first) { first = false; } else { len += sprintf(szLog + len, " "); } v8::String::Utf8Value str(args[i]); const char* cstr = ToCString(str); len += sprintf(szLog + len, "%s", cstr); } __android_log_print(level, szTag, "%s", szLog); }
args 包含了参数和运行时。在每次取参数处理参数时,必须调用
v8::HandleScope handle_scope(args.GetIsolate());
(why?I don‘t known 。。。。)
从Value中取字符串,则需要用ToCString,他也很简单:
static const char* ToCString(const v8::String::Utf8Value& value) { return *value ? *value : "<string conversion failed>"; }
正经的给v8使用的函数是这样的:
void js_func_log_i(const v8::FunctionCallbackInfo<v8::Value>& args) { js_func_log_x(ANDROID_LOG_INFO, args); } void js_func_log_v(const v8::FunctionCallbackInfo<v8::Value>& args) { js_func_log_x(ANDROID_LOG_VERBOSE, args); } void js_func_log_d(const v8::FunctionCallbackInfo<v8::Value>& args) { js_func_log_x(ANDROID_LOG_DEBUG, args); } void js_func_log_e(const v8::FunctionCallbackInfo<v8::Value>& args) { js_func_log_x(ANDROID_LOG_ERROR, args); }
好了,初体验到此结束
一些重要的知识点
1. 关联一个对象到Isolate上
Isolate可以有多个,如果有一个对象关联到Isolate,该如何从Isolate找到该对象呢?
使用Isolate的GetData/SetData
比如
JavaWrapper* JavaWrapper::fromIsolate(Isolate* isolate) { if (isolate == NULL) { return NULL; } void *data = isolate->GetData(ISOLATE_SLOT_JAVA_WRAPPER); JavaWrapper* javawrap = reinterpret_cast<JavaWrapper*>(data); return javawrap && javawrap->isolate_ == isolate ? javawrap : NULL; } JavaWrapper::JavaWrapper(Isolate* isolate) :isolate_(isolate) { isolate_->SetData(ISOLATE_SLOT_JAVA_WRAPPER, (void*)this); }
2. 怎样从v8::Value中得到其他类型的参数
v8::Value是个复合对象,我们可以这样做:
for (int i = 0; i < count; i++) { HandleScope handle_scope(args.GetIsolate()); Local<Value> v = args[i]; if (v->IsUndefined() || v->IsNull()) { types[i] = JsJavaClassProxy::NullClass(); values[i] = JsJavaClassProxy::NullObject(); LOGV(" arg(%d) is null\n", i); } else if (v->IsTrue() || v->IsFalse()) { types[i] = JsJavaClassProxy::BooleanClass(); jboolean jv = (jboolean)(v->IsTrue() ? true : false); values[i] = jreflect::ValueOf(env, jv); LOGV(" arg(%d) is boolean:%d", i, jv); } else if (v->IsString()) { types[i] = JsJavaClassProxy::StringClass(); String::Utf8Value str(v); const char* cstr = ToCString(str); values[i] = env->NewStringUTF(cstr); LOGV(" arg(%d) is String:%s", i, cstr); } else if (v->IsBoolean()) { types[i] = JsJavaClassProxy::BooleanClass(); jboolean jv = v->ToBoolean()->Value(); values[i] = jreflect::ValueOf(env, jv); LOGV(" arg(%d) is boolean:%d", i, jv); } else if (v->IsInt32() || v->IsUint32()) { types[i] = JsJavaClassProxy::IntClass(); jint jv = v->ToInt32()->Value(); values[i] = jreflect::ValueOf(env, jv); LOGV(" arg(%d) is int:%d", i, jv); } else if (v->IsNumber()) { types[i] = JsJavaClassProxy::DoubleClass(); jdouble jv = v->ToNumber()->Value(); values[i] = jreflect::ValueOf(env, jv); LOGV(" arg(%d) is double:%f", i, jv); } /** TODO MORE **/ else { LOGE("Invalidate Arg type!"); ASSERT(false); } }
更多的,从v8.h中看就可以了。
好了,先到这里吧。
V8 javascript引擎初步体验