Android APP 集成 JavaScriptCore 执行 JavaScript

2018-12-21

介绍

在之前的文章中我们编译了供 Android 平台使用的 JavaScriptCore。在本文中,我们的目标是创建一个 Android 工程,导入编译的 JavaScriptCore,并进行 JavaScript 脚本解析。

主要有以下步骤:

  1. 将 JavaScriptCore 编译为 Android 库(已经完成,参见之前文章)
  2. 在 Android 项目中链接上 JavaScriptCore 库
  3. 调用库执行脚本

在本文中,我们直接从第二步开始。

创建新项目

首先,我们在 Android Studio 中创建一个新项目,需要注意的是,要勾选 **Include C++ support**

项目创建好后,打开项目,C++ 代码都位于:native-lib.cpp 中。

导入 JavaScriptCore 库

在 app/src/main 下创建目录 jniLibs,并将所有 so 文件拷贝到该目录下:

.
├── AndroidManifest.xml
├── cpp
│   └── native-lib.cpp
├── java
│   └── com
│       └── maxiee
│           └── jsclearning1
│               └── MainActivity.java
├── jniLibs
│   ├── arm64-v8a
│   │   ├── libc++_shared.so
│   │   └── libjsc.so
│   ├── armeabi-v7a
│   │   ├── libc++_shared.so
│   │   └── libjsc.so
│   ├── x86
│   │   ├── libc++_shared.so
│   │   └── libjsc.so
│   └── x86_64
│       ├── libc++_shared.so
│       └── libjsc.so

导入了 so 库之后,我们还需要导入头文件。需要修改 CMakeLsits.txt,添加以下代码:

include_directories(../../path/to/your/header/files)

这里遇到一点问题,我用 jsc-android-buildscripts 编译出来的 dist 目录下,头文件被打平到一个目录下了,而 Header 文件中还是使用 JavaScriptCore 嵌套目录。我的解决方法是修改头文件,修复扁平化引用。

问题 ninja error missing and no known rule to make it

看起来是 native-lib 依赖了 jsc,但是在具体连接的时候出现了问题。

通过这篇文章:Android Studio cmake和jni的一些坑 找到了原因,是路径的问题。

修改 CMakeLists.txt 为:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

include_directories(/Users/maxiee/Code/Android/native-js-android/nativejs/src/main/jni/include)

set(CURRENT_DIR ${CMAKE_SOURCE_DIR})
message("CURRENT_DIR:" ${CMAKE_SOURCE_DIR})

add_library(jsc SHARED IMPORTED)
set_target_properties( # Specifies the target library.
                        jsc PROPERTIES
                        IMPORTED_LOCATION "${CURRENT_DIR}/src/main/jniLibs/${ANDROID_ABI}/libjsc.so")

add_library( # Sets the name of the library.
                native-lib

                # Sets the library as a shared library.
                SHARED

                # Provides a relative path to your source file(s).
                src/main/cpp/native-lib.cpp )

find_library( # Sets the name of the path variable.
                log-lib

                # Specifies the name of the NDK library that
                # you want CMake to locate.
                log )

target_link_libraries( # Specifies the target library.
                        native-lib
                        jsc
                        ${log-lib} )

'CoreFoundation/CoreFoundation.h' file not found

出现这个问题,是因为我使用的头文件不对。之前编译出来的 jsc 的头文件有问题,得到的可能是 macOS 下的头文件。

我换了一份头文件可以成功编译了,正确的头文件是不包含 CoreFoundation 依赖的。

编写 JNI

下面来到 native-lib.cpp,加入以下代码:

#include <jni.h>
#include <string>

#include "JavaScriptCore/JavaScript.h"

std::string JSStringToStdString(JSStringRef jsString) {
    size_t maxBufferSize = JSStringGetMaximumUTF8CStringSize(jsString);
    char* utf8Buffer = new char[maxBufferSize];
    size_t bytesWritten = JSStringGetUTF8CString(jsString, utf8Buffer, maxBufferSize);
    std::string utf_string = std::string(utf8Buffer, bytesWritten - 1);
    delete [] utf8Buffer;
    return utf_string;
}

extern "C" JNIEXPORT jstring

JNICALL
Java_com_maxiee_jsclearning1_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    JSContextGroupRef contextGroup = JSContextGroupCreate();
    JSGlobalContextRef globalContext = JSGlobalContextCreateInGroup(contextGroup, nullptr);

    JSStringRef statement = JSStringCreateWithUTF8CString(
            "function degToRad(value) {return (value * Math.PI) / 180; } ('90 deg = ' + degToRad(90))");

    JSValueRef retValue = JSEvaluateScript(globalContext, statement, nullptr, nullptr, 1, nullptr);

    JSStringRef retString = JSValueToStringCopy(globalContext, retValue, nullptr);

    std::string hello = JSStringToStdString(retString);

    JSGlobalContextRelease(globalContext);
    JSContextGroupRelease(contextGroup);
    JSStringRelease(statement);
    JSStringRelease(retString);

    return env->NewStringUTF(hello.c_str());
}

其中:

  • 此时的头文件和类型都是访问不到的,在 IDE 中与 jsc 相关的都是标红状态。

运行

当成功连接后,编译 App,可以看到成功运行了一个 JavaScript 方法,并在屏幕上展示结果:

结论

通过这次实践,虽然最终把代码跑起来了,但整个过程中懵懵懂懂,还是非常模糊。下一步要做的,是把概念不清晰的地方搞明白。

网络资源

本文参考了以下文章: