Javascript - это мощный язык сценариев, который также прост в изучении и очень популярен. Существует множество возможностей для использования его в приложении или игре, от разработки сценариев до создания полноценного движка.

Одна из замечательных особенностей этого заключается в том, что вы можете обновлять свое приложение или игру в горячем режиме без необходимости повторной отправки в магазины. Однако это может привести к бану, так что не вините меня за то, что я не сказал вам заранее.

У меня всегда было желание создать свой собственный кроссплатформенный SDK для приложений, похожий на React-native или Flutter с Javascript в качестве языка программирования. Разница будет в дизайне и архитектуре пользовательского интерфейса, но это уже другая история. Таким образом, моим первым камнем преткновения было бы запускать исходный код Javascript с собственным кодом.

Вы можете спросить, почему я хочу заново изобрести колесо, но, черт возьми, я этого не делаю. Дерьмо застряло на колесах. Дизайн пользовательского интерфейса с поддержкой React слишком утомителен, и я ненавижу флюс. Чтобы еще больше разозлить нас, программистов, Flutter использует Dart. При всем уважении к людям, создавшим эти хорошие SDK, я не могу понять, почему они не сделали вещи похожими на Microsoft WPF.

Во всяком случае, вернемся к основной теме.

Любопытно, что я не смог найти ни одного документа, который рассказывал бы мне, как выполнять эту работу поэтапно. После некоторой борьбы и усилий, чтобы сделать это самому, я решаю поделиться своим опытом здесь.

Если вы дочитали до этого места, держу пари, вы знаете, что хотите получить из этого рассказа, который я пишу. Вы, наверное, тоже страдали от отсутствия документов для этого задания.

Цель этой истории - запустить простую строку Javascript и показать результат на экране.

Давай начнем!

Почему именно JavaScriptCore?

  • React-native использует его. Большая часть тяжелой работы была сделана сообществом React-native.
  • Он встроен в iOS, половина работы уже выполнена.

Обзор

  • Первое, что нужно сделать, это скомпилировать JavaScriptCore в общую библиотеку Android.
  • Свяжите общую библиотеку с проектом Android.
  • Запустите простой сценарий, используя эту библиотеку.

Шаг 1. Скомпилируйте JavaScriptCore

Это самая сложная и длительная задача, однако она была сделана очень простой, потому что сообщество, использующее реагирование, уже сделало все здесь: https://github.com/react-community/jsc-android-buildscripts.

Итак, для начала, клонируйте это репо где-нибудь и делайте точно так, как их инструкции в файле README, пока не получите папку с именем compiled в / build. Этот процесс может занять более 2 часов.

Вы можете многому научиться, пытаясь скомпилировать разделяемую библиотеку самостоятельно, не прибегая к тому, что уже было сделано сообществом react-native. Но поверьте мне, эти знания будут очень тяжелыми, и вы будете много ругаться в процессе.

В этой папке вы можете увидеть файлы общей библиотеки (файлы .so) JavaScriptCore:

Нам потребуются все созданные выше so файлы. Вы можете попробовать удалить некоторые из них позже и посмотреть, работает ли приложение по-прежнему.

Шаг 2 - Создайте новый проект Android

В этой истории для простоты я использую Android Studio.

При создании нового проекта убедитесь, что включена поддержка C ++:

И минимальный целевой API - 21, потому что он нужен JavaScriptCore:

Попробуйте запустить пустой проект приложения.

Часть нашего приложения с собственным кодом начинается в app / src / main / cpp / native-lib.cpp, мы изменим его для запуска этой простой строки Javascript.

Строка вычисляет простую математическую операцию с использованием определенной функции, а затем возвращает простое сообщение в контекст.

Но перед этим нам нужно связать разделяемую библиотеку с нашим проектом.

Шаг 3. Свяжите общую библиотеку с проектом Android

Вам необходимо создать каталог с именем jniLibs в app / src / main и скопировать сюда все so файлы, созданные на шаге 1. Таким образом, вы получите следующее:

Все, что находится в app / src / main / jniLibs, будет автоматически скопировано в сборку. Нам не нужно указывать Android Studio делать что-либо еще.

Теперь, если мы вызовем функции JavaScriptCore в файле native-lib.cpp, мы обязательно получим ошибки компиляции. Однако просто продолжайте, измените этот файл cpp следующим образом. Я хочу, чтобы вы получили ошибку при обучении.

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

#include "JavaScriptCore/JavaScriptCore.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_example_chungnguyen_testjsc_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; } ('90deg = ' + 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());
}

Самая первая ошибка будет отсутствовать в файле заголовка JavaScriptCore / JavaScriptCore.h. Моя реакция, когда я получил эту ошибку компиляции, была: «Где, черт возьми, я могу найти этот файл заголовка?».

Вернитесь в папку репозитория jsc-android-buildscripts, которую вы клонировали ранее, вы можете найти исходный код JavaScriptCore в build / target / webkit / Source. Что нужно сделать, так это добавить путь поиска файлов заголовков в наш Android-проект.

Для этого в файле CMakeLists.txt по адресу app / src добавьте следующие строки:

include_directories(../../jsc-android-buildscripts/build/target/webkit/Source/JavaScriptCore)
include_directories(../../jsc-android-buildscripts/build/target/webkit/Source/JavaScriptCore/ForwardingHeaders)

В моем случае папка репозитория jsc-android-buildscripts находится в том же месте в папке проекта Android, поэтому я помещаю пути поиска заголовков, как указано выше.

Теперь пути к файлам заголовков разрешены, но после повторной компиляции у нас есть ошибки ссылок о неопределенных ссылках.

Не нужно паниковать, нам просто нужно связать разделяемую библиотеку с нашим собственным кодом. Для этого сначала нам нужно определить свойство библиотеки, добавив эти строки в файл CMakeLists.txt.

add_library(jsc SHARED IMPORTED)
set_target_properties( # Specifies the target library.
                       jsc

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       src/main/jniLibs/${ANDROID_ABI}/libjsc.so )

Эти строки определяют общую библиотеку с именем jsc. Настоящее имя файла - libjsc.so, но расположение файла основано на архитектуре ABI при компиляции собственного кода.

Затем нам нужно фактически связать библиотеку, добавив jsc эту строку:

target_link_libraries( # Specifies the target library.
                       native-lib

                       jsc

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

Вот и все! Запустите приложение, чтобы увидеть результат.