V8Android

Project Url: cstsinghua/V8Android
Introduction: A demo APP for embedding V8 engine in Android APP
More: Author   ReportBugs   
Tags:

A demo APP for embedding V8 engine in Android APP

1.git clone https://github.com/cstsinghua/V8Android.git;

2.(Optional)enter app/src/main/cpp/static_lib directory,unzip v8_arm_arm64.zip,then copy arm64-v8a and armeabi-v7a subdirs to app/src/main/jniLibs directory(overwrite the old files);

3.open Android studio(version 3.1 is recommended),open and load this project;

4.run it and view the result.(you can edit Java and native code if you will)

背景

最近公司的移动引擎(自研,用于公司的游戏 APP 开发,引擎核心采用 C++开发,而游戏的 UI 和业务逻辑采用 Lua 语言开发)需要支持 Javascript 和 Lua 互相调用(支持 Android 和 IOS 两大平台)。刚开始的时候,没有什么头绪。由于之前实现过 Lua 和 Android/IOS 原生语言(API)即 Java/Object-C 的互调,其中 Android 平台交互原理大致如下图(本文主要基于 Android 平台讲解。IOS 下,OC 调用 C++更简单,这里暂不赘述):

由此,大概思考了下,如果要实现 JavaScript 和业务层的 Lua 互调,那么应该与上面的原理类似。这基于以下几点分析:

  1. Javascript 和 java 一样,都是解释性语言,需要类似 VM 的虚拟机(一般采用 C 或 C++语言实现)来执行。java 世界里,JVM 是我们所熟知的。而 JS 领域,在移动平台方面,除了大名鼎鼎的 google 的 v8 引擎,还有其他的一些 JS 引擎(相当于 VM),比如 JavaScriptCore、SpiderMonkey 和 Rhino(java 实现,从这个角度看,Rhino 似乎直接在 Android 上可以无缝对接)。关于 JS 引擎介绍,可以参见维基百科JavaScript_engine 介绍
  2. 既然 JS 引擎是 C/C++实现,在 Android 中,嵌入进引擎的 so 库中,便可实现 JS 与 C++互调,而 Lua 语言天生就是 C/C++的寄生语言,从而就可以建立 JS 和 Lua 互调的纽带。

基于性能和平台适配考虑,最终选择 v8 作为 Android 平台的嵌入 JS 引擎,而 JavaScriptCore 作为 IOS 平台的嵌入引擎。本文主要讲解,如果从 v8 的源码构建出 Android 平台的嵌入库,然后通过 Android NDK 开发,进而实现在 Android APP(非 Hybrid 应用,即不会通过 Webview 来运行 JS 代码)中运行 JS 代码,JS 与 C/C++、Lua 互调。

平台和环境的选择

如果有过大型开源 C++项目编译的经验,就知道,选择开发系统和环境是非常重要的,一般而言,在 Windows 下编译真的是非常艰难。所以文本先以 Linux 系统(这里选择 Ubuntu)为例,讲解如何构建出适用于 Android ARM 架构的 JS 引擎嵌入静态库。再讲解 Windows 平台下的编译构建。

注:v8 的构建真的是非常复杂和繁琐的过程,各种坑,各种层出不穷的问题。另外一篇文章编译及嵌入 v8 遇到的错误汇总详细记录了学习 v8 构建过程中遇到的一些问题,以及分析和解决途径、链接等,以供探讨。

Linux(Ubuntu 16.04)下构建用于 Linux/Mac x64 版本的 v8(可执行的二进制文件)

v8 项目在 Github 的官方镜像地址为https://github.com/v8/v8。而其官方的wiki 文档也在这个仓库中。但是如果直接按照这个 wiki 去构建,失败几乎是不可避免。因为 wiki 的步骤非常简化(当然先总体阅览一下 wiki 还是大有必要,而且当回过头来对照的时候将会发现"原来是这样,Soga^_^"),有些关键的环境配置和要点被省略掉,所以如果不跳过这些坑,将浪费大量的时间,甚至有很严重的挫败感。本文的目的就是为了对 v8 的构建做一个记录,总结遇到的问题,便于以后查阅,也为他人提供一个便捷的指引。

这里,构建主机(Host)是 Ubuntu 16.04 系统,而目标平台(Target)是 Linux/Mac x64。

如果没有单独的 Ubuntu 主机,那么可以在 Windows 主机下安装virtual box或者VMware,建议安装 virtualbox,相对来说更轻量级,这里足够满足需求。然后利用 virtualbox 加载 Ubuntu 16.04 的 ISO 镜像(可网上下载,或找 CoulsonChen 索取),创建虚拟机环境,即可在虚拟机环境下实现编译。

具体步骤

  1. 安装 Git,在终端里面输入下面命令:

    apt-get install git

  2. 安装 depot_tools(详细可参见安装 depot-tools),在终端输入下面命令:

    git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

    将 depot_tools 的目录添加到系统的 PATH 路径,可以将 depot_tools 所在的目录加入到~/.bashrc~/.zshrc(~代表当前用户目录,一般为/home/yourName/,即打开终端时候默认进入的目录),这样对当前用户的环境变量都生效了)。如果只是在当前终端环境下添加(假定 depot_tools 所在的目录为/path/to/depot_tools),则执行:

    export PATH=/path/to/depot_tools:$PATH

    depot_tools 已经自带了 GN 工具(GYP 从 V8 6.5 版本开始就不再使用。请使用 GN 代替)。V8 的构建需要借助于 GN。GN 是一个元构建系统(meta build system of sorts),因为它为其他构建系统(比如 ninja)生成构建文件(it generates build files for a number of other build systems)。也就是说,GYP 和 GN 并不是构建系统,V8 使用的构建系统使用的是 Ninja,而 GN 是用来生产 Ninja 构建文件的工具。

  3. 更新 depot_tool 工具,在终端输入如下命令(注意,该命令不带任何参数):

    gclient

    该步骤更新 depot_tool 工具,特别如果是 Windows 系统下面构建,这一步骤将会在 depot_tool 目录下下载 Git 和 python(2.7 版本)

  4. 选择一个目录(该目录用于存放下载 v8 的源码),比如/usr/local/v8/在该目录下打开终端,执行如下命令:

    fetch v8

    然后进入 v8 的源文件目录

    cd v8

  5. 步骤 1-4 确保工具和 v8 的源码下载完成,在执行第 5 步前,再次确认当前工作目录已经在 v8 的源目录下(步骤 4 中的示例来看的话,就是/usr/local/v8/v8/)。然后在当前工作目录(比如/usr/local/v8/v8/)下执行如下命令,该命令将会下载所有的构建依赖项(Download all the build dependencies)

    gclient sync

  6. (这一步只在 linux 系统构建时才需要执行,且只需要执行一次--(only on linux, only needed once) 安装构建依赖项:

    ./build/install-build-deps.sh

  7. 生成目标平台必需的构建文件(Generate the necessary build files by executing the following in your terminal/shell):

    tools/dev/v8gen.py x64.release

    注意:目标平台有很多,可以通过tools/dev/v8gen.py list命令查看。这里以本文写作时的 master 分支版本为例,执行tools/dev/v8gen.py list命令后,可以看到 v8 支持如下目标平台构建:

     coulsonchen@coulsonchen:/usr/local/v8/v8$ tools/dev/v8gen.py list
     android.arm.debug
     android.arm.optdebug
     android.arm.release
     arm.debug
     arm.optdebug
     arm.release
     arm64.debug
     arm64.optdebug
     arm64.release
     ia32.debug
     ia32.optdebug
     ia32.release
     mips64el.debug
     mips64el.optdebug
     mips64el.release
     mipsel.debug
     mipsel.optdebug
     mipsel.release
     ppc.debug
     ppc.optdebug
     ppc.release
     ppc64.debug
     ppc64.optdebug
     ppc64.release
     s390.debug
     s390.optdebug
     s390.release
     s390x.debug
     s390x.optdebug
     s390x.release
     x64.debug
     x64.optdebug
     x64.release
    
  8. 编译 v8 源码(Compile the source by executing the following in your terminal/shell):

    ninja -C out.gn/x64.release

    该命令是编译构建 v8 运行的所有文件(building all of V8 run (assuming gn generated to the x64.release folder))。编译完成之后,可以在/path/to/v8 Source dir/out.gn/x64.release目录(这里示例即/usr/local/v8/v8/out.gn/x64.release)及其子目录下查看构建产生的库和可执行文件。可进入该目录下,执行生成的 d8 可执行程序,进入 javascript 的交互命令行模式,示例如下图: 如果只需要编译构建指定的文件,比如 d8(build specific targets like d8, add them to the command line)。则可执行如下命令(将要指定的构建文件添加到 ninja 命令的参数中):

    ninja -C out.gn/x64.release d8

  9. (可选,用于测试构建是否 OK)执行测试(Run the tests by executing the following in your terminal/shell):

    tools/run-tests.py --gn

Linux(Ubuntu 16.04)下构建用于 Android arm 版本的 v8(可执行的二进制文件)

这里,构建主机(Host)是 Ubuntu 16.04 系统,而目标平台(Target)是 Android arm。由于构建平台和目标平台是不同的平台,因此这里,需要使用交叉编译(Cross compiling)

前置条件,和Linux(Ubuntu 16.04)下构建用于 Linux/Mac x64 版本的 v8(可执行的二进制文件)章节的 1-6 步完全一致。接下来,可参考Cross compiling for ARM

这里单独对 Cross compiling for ARM 的步骤做下说明:

  1. 在配置好上述提到的环境之后,编辑.gclient 配置文件(.gclient configuration file),位于用于存放下载 v8 的源码的目录,对应于Linux(Ubuntu 16.04)下构建用于 Linux/Mac x64 版本的 v8(可执行的二进制文件)章节的步骤 4 中的说明,这里的目录路径示例为/usr/local/v8,如下图: 打开该文件,在末尾添加如下一行: target_os = ['android'] # Add this to get Android stuff checked out. 示例如下:
  2. 然后在 v8 的源码目录下(/usr/local/v8/v8),执行如下命令:

    gclient sync

    一旦配置了target_os = ['android'],再执行gclient sync,将会在 v8 的源码目录下下载 Android 相关的工具和文件,主要包括 android_tools 和 android_ndk 等(示例,对应目录/usr/local/v8/v8/third_party/android/android_tools/usr/local/v8/v8/third_party/android_ndk)。注意:这些文件非常大,有 10G 左右,所以需要下载很长时间,网络如果不好的话,会很痛苦

  3. 利用 8gen.py 生成 ARM 架构编译时必要的构建文件(和Linux(Ubuntu 16.04)下构建用于 Linux/Mac x64 版本的 v8(可执行的二进制文件)章节的第 7 步类似)。在 v8 的源码目录下(/usr/local/v8/v8),执行如下命令:

    tools/dev/v8gen.py arm.release

  4. 然后,进入生成的arm.release子目录(示例,/usr/local/v8/v8/out.gn/arm.release),编辑其中的args.gn文件,在其中添加如下几行(如果要查看所有可配置的参数,可以先执行命令gn args out.gn/arm.release --list查看):

     target_os = "android" 
     target_cpu = "arm"  
     v8_target_cpu = "arm"
     is_component_build = false
    

    如果是 arm64 设备,则上面几行应该替换为:

     target_os = "android"     
     target_cpu = "arm64"
     v8_target_cpu = "arm64"
     is_component_build = false
    
  5. 编译构建。这一步与Linux(Ubuntu 16.04)下构建用于 Linux/Mac x64 版本的 v8(可执行的二进制文件)章节的第 8 步类似。执行如下命令(构建全部目标文件):

    ninja -C out.gn/arm.release

    或(只构建 d8)

    ninja -C out.gn/arm.release d8

  6. 构建完成后,可以将生成的 d8 及其相关文件 push 到 Android 手机中,体验一把,看看效果。可以通过 adb 工具来将文件 push 到手机(adb 工具已经在步骤 2 完成后下载了,具体目录/usr/local/v8/v8/third_party/android_tools/sdk/platform-tools。当然你也可以直接通过sudo apt install android-tools-adb命令来额外安装 adb。然后 adb 所在路径添加到 PATH 路径中)。在 v8 源文件目录下(/usr/local/v8/v8)执行如下命令:

    adb push out.gn/arm.release/d8 /data/local/tmp

    adb push out.gn/arm.release/natives_blob.bin /data/local/tmp

    adb push out.gn/arm.release/snapshot_blob.bin /data/local/tmp

    然后,通过 adb shell 进入到 Android 手机的交互 shell 中。

     > $ adb shell
     > $ cd /data/local/tmp
     > $ ls
         v8 natives_blob.bin snapshot_blob.bin
    
     > $ ./d8
     V8 version 5.8.0 (candidate)
     d8> 'w00t!'
     "w00t!"
     d8> 
    

Linux(Ubuntu 16.04)下构建用于 Android arm 版本的 v8(静态库,用于 Android NDK 链接和封装)

注:已经有人在 Github 上提供编译好的 v8 静态库供 Android NDK 嵌入开发,这样可以节省很多时间(只是并非对应 v8 的最新版,不过也是次新版)。请参见https://github.com/social-games/CompiledV8

首先理一下思路:

  1. 先利用交叉编译,构建出用于 Android 平台的 v8 静态库文件;
  2. 利用 Android studio 进行 NDK 开发,将步骤 1 中生成的静态库文件进行链接封装,向 Java 层暴露方法(对应 java 层的 native 方法),最终生成动态库文件;
  3. Android APP 内部,原生的 Java 层可以通过 JNI 调用步骤 2 中生成的动态库中的方法(去加载 JS 代码并执行相关逻辑,然后返回相关数据,反之亦然)

实施前的题外话: 如果是为了练练手,那么可以采用 v8 的最新 master 分支进行构建即可;但如果是为了用于生产环境,那么请一定采用最新的稳定版本来构建。怎么查询到最新的稳定版本是多少呢?请参见官方原文说明。本文写作时最新的适用于 Android 平台的分支是6.5.254.43。因此,在执行前,需要先逐一执行以下命令:

git pull

git checkout branch-heads/6.5

gclient sync

具体实施步骤:

  1. 首先与章节Linux(Ubuntu 16.04)下构建用于 Android arm 版本的 v8(可执行的二进制文件)(#anchor2)的步骤基本一致,但需要在其第 3 步修改目标构建平台(即将 arm.release 改为 android.arm.release),执行命令替换为:

    tools/dev/v8gen.py gen -m client.v8.ports -b "V8 Android Arm - builder" android.arm.release

  2. 首先与章节Linux(Ubuntu 16.04)下构建用于 Android arm 版本的 v8(可执行的二进制文件)(#anchor2)的步骤基本一致,只是需要在其第 4 步修改下args.gn配置文件,如下:

     is_component_build = false
     is_debug = false
     symbol_level = 1
     target_cpu = "arm"
     target_os = "android"
     use_goma = false
     v8_android_log_stdout = true
     v8_enable_i18n_support = false
     v8_static_library = true
    

    注意:use_goma = false、v8_static_library = true、v8_enable_i18n_support = false 都需要添加

  3. 然后重新执行构建,构建完成之后,在 v8 源码目录下的子目录out.gn/android.arm.release/obj下可以找到对应的静态库。然后按照这篇文章的步骤,将相关静态库和头文件聚合整理到一个单独的目录(比如 libs,最终将 libs 目录 copy 到 Android Studio 的工程的 cpp 源文件目录下)。这里列出关键示例代码:
     // Create fat lib files. 
     // You also could add all .* files into one single library.
     // 
     cd out.gn/android_arm.release/obj
     mkdir libs
     cd libs
     // one lib to rule them all.
     ar -rcsD libv8_base.a ../v8_base/*.o
     ar -rcsD libv8_base.a ../v8_libbase/*.o
     ar -rcsD libv8_base.a ../v8_libsampler/*.o
     ar -rcsD libv8_base.a ../v8_libplatform/*.o 
     ar -rcsD libv8_base.a ../src/inspector/inspector/*.o
     // preferred snapshot type: linked into binary.
     ar -rcsD libv8_snapshot.a ../v8_snapshot/*.o 
     // depending on snapshot options, you should left out 
     // v8_snapshot files and include any of these other libs.
     ar -rcsD libv8_nosnapshot.a ../v8_nosnapshot/*.o
     ar -rcsD libv8_external_snapshot.a ../v8_external_snapshot/*.o
     // source headers, for inspector compilation.
     mkdir -p src/base/platform
     cp -R ../../../../src/*.h ./src
     cp -R ../../../../src/base/*.h ./src/base
     cp -R ../../../../src/base/platform/*.h ./src/base/platform
     // copy v8 compilation header files:
     cp -R ../../../../include ./
     // For compilation on Android, always **use the same ndk** as 
     // `gclient sync` downloaded. 
     // As of the time of this writing it was `ndk r12b`
     // Enjoy v8 embedded in an Android app
    
    关于 libv8_snapshot.a/libv8_nosnapshot.a/libv8_external_snapshot.a 在使用的时候任选其一,这三者的区别如下: Currently, snapshots are compiled by default. These snapshots will contain base objects, like for example, Math. There’s no runtime difference among them, just different initialization times. In my nexus 5x, no snapshot takes around 400ms to initialize an Isolate and a Context, and around 30 with snapshot. The external snapshot and snapshot differ in that the external snapshot must be explicitly loaded (.bin files in the compilation output), and snapshot library is a static lib file of roughly 1Mb in size, that will be linked with the final .so file binary instead of externally loaded. Bear in mind that snapshot libs, internal or external, would require you to supply some extra native code for reading the Natives (.bin) files.
  4. 新创建一个 Android Studio 工程(支持 C++,利用默认的向导创建完成即可。注:这里 AS 的版本是 3.1,默认采用 cmake 构建 C++代码),然后将步骤 2 得到的 libs 拷贝到工程的 cpp 源文件对应的目录下。然后就是编辑工程的 CMakeLists.txt 文件,将 libs 里面的静态库链接进来。具体代码和配置可以参考我的Github demo。另外也可参考这篇文章

win10 构建 v8 engine 的心路历程

这里可以完全参照https://medium.com/dailyjs/how-to-build-v8-on-windows-and-not-go-mad-6347c69aacd4这篇文章的步骤来完成。

但需要强调的是,这篇文章编写的时候,当时的 v8 版本可以使用 VS 2015 编译,而 v8 的最新版本(截止到 2018.4.4)要求 VS 2017(随着时间推移,可能后续会要求更新版本的 VS,这里就必须注意,可以在下载下来的 v8 源目录下面的 build 子目录下的 vs_toolchain.py 文件中查看默认的 VS 版本,如下图),所以这篇文章里面关于 VS 2015 的部分,请替换成 VS 2017。另外,关于 Windows SDK 的部分,也请下载最新版本的。

Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea