本文所讲v8版本为3.17版本。了解此版本的同学,应该知道,这个版本被UC进行优化,在weex中进行了使用。
概述
本文所讲的内容是:在chrome端远程调试安卓端用v8加载的js。
如果要在安卓客户端进行js动态加载,通过js渲染界面,这就必须要通过v8进行解析,v8解析过js之后,就会将js的对象,包装成一个个的C++对象,可以通过v8提供的接口,在native端获取这些对象,从而对其进行使用。这种方式,基本上是所有动态化框架的基础。weex就是这种方式,reactnative(以下简称RN)也是这种方式,不过解析js使用的引擎不相同而已,RN使用的是iOS平台的javascriptcore。而weex在安卓上也是使用的v8。通过这种方式,js一次编写,就可以同时运行在web端,安卓端,iOS端。(iOS端在此不作介绍。)这种动态加载,可以应对业务变化很快的app,比如淘宝,页面需要经常变动,只是更改一下界面,业务逻辑,就要重新发布新版本,是一件很没有意思的事情。所以,这种基于v8的动态化框架,就成为了趋势。
众所周知,在web端调试js很容易,而在安卓端通过webview调试js也很容易,而直接通过v8调试js,却很困难。首先,v8协议在不断的发生改变,而v8所使用的调试协议和chrome所使用的v8调试协议完全不一样。前面有一篇文章,记录了本文所介绍的3.17版本的v8调试协议,chrome的调试协议比较稳定,google一下,就可以找到。你可以进行对比,他们是完全不一样的。这就意味着,要进行调试,就必须要做协议转换。(注意:谷歌自从5.6版本起,就自带了调试客户端,内部自己转换了协议,而且可以直接和chrome进行连接,只需要调用相关的方法就可以。可以参考v8 github上的wiki。如果使用的是5.6及以上版本,本文可以直接忽略不看。
)
安卓客户端工作
主要有以下三个重点
1、客户端首先需要注册一个handler。在v8-debug.h中有这样一个方法:
static void SetMessageHandler2(MessageHandler2 handler);
//MessageHandler2的定义如下:
typedef void (*MessageHandler2)(const Message& message);
将自己的MessageHandler2set进去之后,就可以接收到来自v8的message类型数据。
//通过这种方式,可以得到json数据。
message.GetJSON();
获得了消息之后,传递到服务端,进行协议转换。
2、那么被服务端转换之后的chrome消息怎么处理呢?在v8-debug.h文件中,有如下一个方法:
// If no isolate is provided the default isolate is used.
static void SendCommand(const uint16_t* command, int length,
ClientData* client_data = NULL,
Isolate* isolate = NULL);
通过这个方法,可以将转换之后的消息发送给v8进行处理,v8通过处理,就会将消息通过步骤1的方式传递结果。
3、有一些情况下,发送到v8的调试信息可能没有返回,可以尝试下面的方法:
/**
* Makes V8 process all pending debug messages.
*
* From V8 point of view all debug messages come asynchronously (e.g. from
* remote debugger) but they all must be handled synchronously: V8 cannot
* do 2 things at one time so normal script execution must be interrupted
* for a while.
*
* Generally when message arrives V8 may be in one of 3 states:
* 1. V8 is running script; V8 will automatically interrupt and process all
* pending messages (however auto_break flag should be enabled);
* 2. V8 is suspended on debug breakpoint; in this state V8 is dedicated
* to reading and processing debug messages;
* 3. V8 is not running at all or has called some long-working C++ function;
* by default it means that processing of all debug messages will be deferred
* until V8 gets control again; however, embedding application may improve
* this by manually calling this method.
*
* It makes sense to call this method whenever a new debug message arrived and
* V8 is not already running. Method v8::Debug::SetDebugMessageDispatchHandler
* should help with the former condition.
*
* Technically this method in many senses is equivalent to executing empty
* script:
* 1. It does nothing except for processing all pending debug messages.
* 2. It should be invoked with the same precautions and from the same context
* as V8 script would be invoked from, because:
* a. with "evaluate" command it can do whatever normal script can do,
* including all native calls;
* b. no other thread should call V8 while this method is running
* (v8::Locker may be used here).
*
* "Evaluate" debug command behavior currently is not specified in scope
* of this method.
*/
static void ProcessDebugMessages();
文档介绍很多,主要意思是如果命令是Evaluate
,有些情况下v8是不会返回的,这时可以尝试调用此方法,催促v8返回。通常情况下,此方法可以不用调用。
3、v8加载运行js很快,这就需要我们每次打开调试的时候,js都断点在第0行第0列等我们来运行。
// Schedule a debugger break to happen when JavaScript code is run
// in the given isolate. If no isolate is provided the default
// isolate is used.
static void DebugBreak(Isolate* isolate = NULL);
在比较靠前的时候,调用此方法,就可以将v8暂停在0行0列,直到给出了下一步或者中断之后,才会继续加载js。
服务端工作
所有的消息都是通过socket或者websocket进行传输。推荐websocket,实现简单,双向通信。而且和chrome的调试连接必须使用websocket。接下来看看几个步骤:
1、处理v8发送过来的消息
v8发送过来的消息是一个json格式的数据,而chrome需要的也是一个json格式的数据。举两个例子,首先看看chrome的协议:
{
"method": "Console.messageAdded",
"params": {
"message": {
"source": "console-api",
"level": "log",
"text": "a=x",
"type": "log"
}
}
}
接下来看看v8的协议:
{
"seq": 1,
"type": "request",
"command": "setexceptionbreak",
"arguments": {
"type": "all",
"enabled": false,
"maxStringLength": -1
}
}
可以看到,他们的协议格式是不相同的。在此,我们都知道nodejs,他也是基于v8进行开发的,想必他的调试也会进行协议转换,就这条线索,找到了node-inspector这个项目,发现其中确实对协议进行了转换。将其协议转换的部分,抽取出来,大部分都可以正常使用。需要更改的有几个要点:
- 脚本获取方式需要进行修改。nodejs直接在桌面,可以通过路径获取文件,而安卓的是远程调试,所以方式不同。
- 他的console输出是注入了一个库,而我们不能注入这个库,所以他的console输出模块也是不可用的。
注意点:
- 获取脚本,v8执行js时,会首先进行编译,然后运行,他会发送一个
afterCompile
事件,当服务端发送getResourceTree
命令的时候,需要将这个消息一并返回过去,接下来就是正常的流程。 - console的log输出,由于v8中并没有console这样一个对象,需要将全局的console进行一个包装,映射为一个v8对象,设置到js的执行环境中,于是,js执行环境中就会拥有console.log,console.warn等属性,设置好之后,在对应的回调函数中,需要手动编写调试后的消息,可以直接变为chrome所需要的调试协议格式,不经转换,直接发送chrome端即可。获取到消息后,可以直接在控制台上输出。
chrome端
chrome端使用的源码中的devtool文件,协议会发生改变,所以确保使用的协议是低于chrome55版本的,以上可能不支持。从这些低版本中抽取出devtool工程。当然,也可以自己修改,升级升上去。
连接起来
以上所讲都是一个个单独的部分,如果需要使用,就需要将其连接起来。我的方案是通过websocket连接起来,起一个nodejs后台服务,处理这些websocket交互。建立对应的session,就会非常简单。
结束
以上只是粗略讲了一下其中的一些重点部分,具体实践起来还是有大量的工作需要去做的。比如多v8的调试,session管理等等,都是比较复杂的。
v8内核能量巨大,未来如何,拭目以待。