安卓 v8 静态库调试

本文所讲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内核能量巨大,未来如何,拭目以待。

你可能感兴趣的:(安卓 v8 静态库调试)