Платежный плагин для Defold
Введение
Монетизация с помощью встроенных покупок распространена в мобильных играх, которые предлагают премиум-подписки, продажу внутриигровых валют, предметов и т. п.
Чтобы проводить платежи через платежную систему RuStore непосредственно из интерфейсов мобильных приложений без перенаправлений к браузерам для открытия платежных форм, можно использовать SDK платежей RuStore (SDK RuStore Billing).
В настоящем руководстве SDK платежей и SDK RuStore Billing означают одно и то же.
Настоящее руководство содержит сведения о создании плагина-обертки над SDK RuStore Billing для интеграции с игровым движком Defold на платформе Android. Вы ознакомитесь с внутренним устройством плагина, а также сможете воспроизвести ключевые шаги по подготовке проекта Defold — эти сведения можно использовать, чтобы создать собственное приложение для публикации в RuStore.
Приведенные в настоящем руководстве действия выполнялись в следующем окружении.
- Операционная система: Windows 11 x64.
- SDK платежей для Defold 5.1.1.
- SDK Android:
- минимальная версия ― 24;
- целевая версия ― 33.
Особенности реализации
SDK RuStore Billing предоставляет эффективный способ работы с платежами, однако он может быть сложен в использовании в игровых движках из-за того, что написан на языке Kotlin.
Взаимодействие системы скриптинга Defold на языке Lua с нативным SDK предполагает использование JNI (Java Native Interface), что требует громоздкой реализации вызовов и обратных вызовов (callback) JNI на C++.
Здесь мы рассмотрим удобный и гибкий способа интеграции платежных функций RuStore в приложения на игровом движке Defold на платформе Android.
В настоящем руководстве подробно рассматриваются следующие задачи.
- Создание плагина для Defold.
- Подключение нативных Android-библиотек SDK RuStore Billing.
- Создание JNI-обертки над методами SDK и их вызов из Lua.
- Создание и настройка демо-приложение для тестирования всех методов плагина.
Стек технологий
| Технология | Краткое о писание |
|---|---|
| Defold | Игровой движок. Для интеграции с внешними библиотеками в настоящей инструкции рассматривается система нативных расширений (плагинов) Defold. Шаги по созданию плагинов описаны в официальной документации Defold, доступна русскоязычная версия документации. |
| Lua | Язык для создания скриптов, встроенный в Defold и использующийся для взаимодействия с нативным SDK. Пример использования методов плагина реализуем на Lua – языке скриптинга Defold. |
| Kotlin | Для реализации функциональности платежей используется библиотека SDK RuStore Billing для Kotlin/Java. |
| C++ | Язык программирования общего назначения. |
| JNI | Java Native Interface — интерфейс, позволяющий вызвать нативные функции SDK. Методы Defold для работы с нативном Android описаны на странице Sdk android api documentation официального сайта. Большинство методов SDK используют асинхронные вызовы и лямбда-выражения. В JNI нет прямой поддержки лямбда-выражений, поскольку лямбда-выражения являются частью высокоуровневых конструкций языков программирования, таких как Java или Kotlin, и не имеют прямого эквивалента в нативном коде C++. Однако чтобы реализовать аналогичное поведение можно использовать указатели на функции в блоках extern "C". В этом случае нам понадобится создать обертку для методов SDK, которые будут принимать указатель на функцию обратного вызова (callback function) и вызывать ее внутри лямбда-выражения. |
| Android Studio | Писать обертки над SDK методами для их вызова через JNI будем на Kotlin в Android Studio. |
| Gradle | Удаленная сборка Defold не поддерживает использование локально расположенных пакетов .aar, но файлы .jar работают прекрасно. Для их создания с помощью Gradle реализуем распаковку наших .aar для извлечения .jar. |
| RuStore Console | Консоль разработчика RuStore. Для тестирования работы плагина необходимо зарегистрироваться в RuStore Console и создать запись о приложении и тестовых продуктах доступных для покупки. |
Инструменты разработки
Для создания платежного решения в рамках настоящего руководства необходимы следующие инструменты разработки (см. таблицу ниже).
| Инструмент | Описание |
|---|---|
Скачайте движок с официального сайта в разделе Download или GitHub. Установка не требуется — для работы нужно будет только запустить исполняемый ф айл (для Windows это к сведению В настоящем руководстве для примера используется версия Defold 1.8.0. | |
к сведению В настоящем руководстве для примера используется версия Android Studio Koala 2024.1.1. | |
Java Development Kit — набор инструментов разработчика Java. В состав Android Studio включен улучшенный набор инструментов JBR. Если по каким-то причинам при использовании JBR возникнут затруднения, скачайте JDK и установите в любое место на компьютере. Рекомендуется установить по пути по умолчанию. примечание В настоящем руководстве используется JDK версии 11.0.2, набор инструментов установлен по пути | |
У вас должен быть доступ к консоли разработчика RuStore. |
Предварительные действия
Создание файла хранилища ключей
Android требует, чтобы все установленные приложения были подписаны. Все магазины приложений также будут проверять загружаемые приложения на наличие подписи и соответствие его ранее заявленному отпечатку.
Для выполнения подписи понадобится файл хранилища ключей в формате .keystore.
Для создания файла хранилища ключей в рамках настоящего руководства используется утилита командной строки keytool из состава Android Studio.
Чтобы создать файл хранилища ключей, выполните следующие действия.
-
Откройте консоль (в Windows с помощью команды
cmd). -
Перейдите в папку с утилитой
keytool.Для WindowsЕсли программное обеспечение Android Studio установлено в папку по умолчанию, что утилита
keytoolрасположена по следующему пути:c:\Program Files\Android\Android Studio\jbr\bin\keytool.exe. -
Выполните следующую команду.
keytool -genkeypair -v -keystore key.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias -
Вы процессе выполнения команды заполните необходимые поля (см. таблицу ниже).
| Поле | Описание |
|---|---|
| Введите пароль для защиты файла хранилища ключей. |
| Повторите введенный ранее пароль. подсказка Сохраните пароль в простом файле Внимание! Это небезопасный способ хранения пароля. В настоящем руководстве он используется для примера. При реализации полноценного платежного решения руководствуйтесь рекомендациями безопасности. |
| Введите имя и фамилию, например: |
| Введите название организационного подразделения, например: |
| Введите название организации, например: |
| Введите название или обозначение города или населенного пункта, например: |
| Введите название или обозначение региона, например: |
| Введите двухбуквенный код страны, например: |
После заполнения полей вам будет предложено проверить свой ввод.
Is CN=John Doe, OU=DEV, O=ORG, L=MSK, ST=MSK, C=RU correct?
[no]:
- Введите
yesи подтвердите ввод.
В случае успешного создания файла храни лища ключей отобразится следующее сообщение.Generating 2 048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10 000 days
for: CN=John Doe, OU=DEV, O=ORG, L=MSK, ST=MSK, C=RU
[Storing key.keystore] - Сохраните созданный файл хранилища ключей
key.keystoreв безопасном месте — он потребуется для настройки сборки Defold-проекта под Android.
Для целей настоящего руководства будет использоваться путьc:\Defold\keystore\key.keystore.
Если вы потеряет е этот файл, то потеряете возможность обновлять загруженные в магазин приложения.
Вы создали файл хранилища ключей, и у вас есть два сохраненных файла (см. таблицу ниже).
| Файл | Описание |
|---|---|
key.keystore | Файл ключевого хранилища. В настоящем руководстве для примера используется путьc:\Defold\keystore\key.keystore. |
password.txt | Файл пароля ключевого хранилища и закрытого ключа. В настоящем руководстве для примера используется путьc:\Defold\keystore\password.txt. |
Эти файлы потребуются вам позднее, чтобы настроить сборку Defold-проекта платежного решения.
Создание эмулятора Android
Создайте виртуальный эмулятор для будущего Defold-приложения со следующими параметрами, после чего переходите к созданию расширений.
| Параметр | Значение |
|---|---|
| Архитектура | x86 |
| Версия Android | 11 (максимальная для архитектуры x86) |
| OpenGL ES renderer (Extended Controls) | Desktop native OpenGL — для обеспечения наилучшего быстродействия. |
| OpenGL ES API level (Extended Controls) | Renderer maximum (up to OpenGL ES 3.1) — для обеспечения наилучшего быстродействия. |
Если вы не знаете, как создать виртуальный эмулятор с указанными параметрами, ниже представлена пошаговая процедура (пропустить).
-
В Android Studio откройте менеджер виртуальных устройств — Virtual Device Manager.

Virtual Device Manager -
В окне Device Manager нажмите значок
.
Отобразится окно добавления виртуального устройства.
Окно добавления виртуального устройства -
В списке Category слева выберите Phone.
-
В списке в центральной части окна выберите профиль устройства.
прим ечаниеВ настоящей документации для примера будет использоваться Medium Phone.
-
Нажмите Next.
Отобразится окно выбора образа системы.
Выбор образа системы -
Выберите нужную систему.
предупреждениеАрхитектура эмулятора должна быть x86.
примечаниеМаксимальная версия Android доступная для x86-эмуляторов — 11.
В настоящей документации для примера используется система с именем релиза Release name: R.
-
Нажмите на значке загрузки напротив выбранной системы.
Отобразится окно загрузки и установки образа системы (SDK Component Installer).
Окно загрузки и установки образа системыПо завершении установки окно примет следующий вид.

Выбор образа системы -
Нажмите Finish.
-
В окне выбора образа системы (System Image) нажмите Next.
Отобразится окно подтверждения конфигурации виртуального устройства.
Выбор образа системы -
Нажмите Finish.
Установленный образ появится в списке Device Manager основного окна Android Studio.
По завершении установки окно примет следующий вид.
Виртуальное устройство установленок сведениюДальнейшая настройка эмулятора устройства Android нужна для обеспечения наилучшего быстродействия.
-
В секции Device Manager Нажмите значок Start напротив установленного виртуального устройства.
Отобразится окно виртуального эмулятора Android.
Виртуальный эмулятор Android -
Нажмите на значок
.
Отобразится окно расширенных функций контроля (Extended Controls).
Окно Extended Controls -
В левом меню перейдите в раздел Settings.
-
В разделе Settings выберите вкладку Advanced.
Окно примет следующий вид.
Окно Extended Controls -
Задайте значения полей, руководствуясь таблицей ниже.
Поле Значение OpenGL ES renderer Desktop native OpenGL OpenGL ES API level Renderer maximum (up to OpenGL ES 3.1) -
Закройте окно Extended Controls.
Создание Defold-проекта и настройка сборки под Android
-
Запустите исполняемый файл Defold.
-
Выберите шаблон Mobile game и назовите его billing_example (см. изображение ниже).

Создание проекта Defold -
При необходимости задайте путь к проекту в поле Location или оставьте путь по умолчанию.
к сведениюВ настоящем руководстве для примера будет использоваться следующий путь:
C:\Defold\billing_example. -
Нажмите Create New Project, чтобы создать новый проект.
- Отобразится окно приветствия Welcome to Defold созданного проекта.
- Также будут созданы папки
.gitignoreи.gitattributesдля хранения проекта в репозиториях Git.
(см. изображение ниже).

Окно приветствия проекта DefoldПроект создан. Следом настройте и создайте сборку под Android (см. далее).
подсказкаПроекты на Defold собираются нелокально — это упрощает настройку проекта для сборки под Android.
-
Откройте для редактирования как формы файл
game.project, который расположен в корне проекта (см. изображение ниже).
Открыть как форму -
В секции Platforms выберите раздел Android (см. изображение ниже).

Открыть как форму -
В секции настроек Android выполните задайте следующие значения полей (см. таблицу ниже).
Поле Настройка Minimum Sdk Version 24 Target Sdk Version 33 Package Задайте название пакета ru.rustore.billing.defold. Настройки должны выглядеть, как показано на изображении ниже.

Настройка проекта Defold под Android -
В меню инструментов сверху выберите Project > Bundle > Android Application…, как показано на изображении ниже.

Project > Bundle > Android Application…
Отобразится окно Bundle Application.

Bundle Application -
Укажите путь к файлам, необходимым для подписи приложения (см. таблицу ниже).
Поле Описание Keystore Укажите путь к ранее подготовленному файлу хранилища ключей
key.keystore.Keystore Password Укажите путь к ранее подготовленному файлу пароля
password.txt.Key Password -
Убедитесь, что в настройке Architectures установлен флажок архитектуры 32-bit (armv7).
примечаниеЧтобы приложение могло быть запущено на архитектурах 32-bit (armv7) и 64-bit (arm64), отметьте оба флажка.
-
В раскрывающемся списке Variant выберите пункт Release.
-
Снимите флажок Generate debug symbols.
Настройки должны выглядеть следующим образом.
Настройки Bundle Application -
Нажмите Create Bundle
-
В отобразившемся окне укажите папку, в которой будет размещена сборка проекта, и подтвердите выбор.
В настоящем руководстве для примера будет использоваться папкаC:\Defold.
Если все действия были выполнены верно, по указанному пути будет создана папка сборки для Android, в нашем примере:C:\Defold\armv7-android.
В папку armv7-android попадают сборки как armv7, так и arm64.
Создание расширений
В настоящей инструкции приведены минимально необходимые настройки.
Для работы платежного плагина необходимо создать два расширения Defold (см. таблицу ниже).
| Расширение | Описание |
|---|---|
| RuStoreCore | Это расширение будет содержать common-код для работы со JNI и некоторыми методами для работы с Android API. Методы этого расширения смогут использовать в том числе другие плагины на основе SDK RuStore. |
| RuStoreBilling | Это расширение будет содержать все необходимые настройки, исходный код, библиотеки и ресурсы, связанные с конечным плагином. Конструктор расширений автоматически распознает структуру папок и интегрирует все исходные файлы и библиотеки. |
Каждое из расширений в файловой системе, согласно документации Defold, должно иметь структуру следующего вида.
my_extension/
│
├── ext.manifest
│
├── src/
│
├── include/
│
├── lib/
│ └── [platforms]
│
├── manifests/
│ └── [platforms]
│
└── res/
└── [platforms]
Ниже представлено описание структуры расширения.
| Элемент структуры | Обязательно | Описание |
|---|---|---|
my_extension/ | Да | Папка расширения в корне проекта (
|
ext.manifest | Да | Файл формата YAML, который подхватывается конструктором расширений. Минимальный файл манифеста должен содержать название расширения. |
src/ | Да | Обязательная папка папка, должна содержать все файлы исходного кода в т.ч. нативный код. |
include/ | Нет | Содержит все включаемые заголовочные файлы (header files) с расширением .h. |
lib/ | Нет | Содержит все скомпилированные библиотеки, от которых зависит расширение. Файлы библиотек должны быть помещены в подпапки с именем платформы — в зависимости от того, какие архитектуры поддерживаются вашими библиотеками. |
manifests/ | Нет | Содержит дополнительные файлы, используемые в процессе сборки или комплектации. |
res/ | Нет | Содержит любые дополнительные ресурсы, от которых зависит расширение. Не используется для целей настоящей инструкции. |
Структура расширений Defold
RuStoreCore
- В корне проекта
billing_exampleсоздайте папкуextension_rustore_core. - Создайте файл
extension_rustore_core/ext.manifest, который будет содержать только строку с именем расширения.extension_rustore_core/ext.manifestname: RuStoreCore - Создайте файл
extension_rustore_core/src/rustorecore.cppи пока оставьте его пустым. - Создайте папку
extension_rustore_core/includeи оставьте ее пока пустой. В расширении RuStoreCore мы будем создавать заголовочные файлы для классов, которые будут использоваться в других расширениях SDK RuStore. - Создайте папку
extension_rustore_core/lib/android— в дальнейшем в нее будут помещены файлы.jar. - Создайте файл
extension_rustore_core/manifests/android/build.gradle
Структура расширения RuStoreCore на текущем этапе должна иметь следующий вид.
extension_rustore_core/
│
├── include/
│
├── lib/
│ └── android/
│
├── manifests/
│ └── android
│ └── build.gradle
│
├── src/
│ └── rustorecore.cpp
│
└── ext.manifest
RuStoreBilling
- В корне проекта (
billing_example) создайте папкуextension_rustore_billing. - Создайте файл
extension_rustore_billing/ext.manifestсо следующим содержимым, который будет содержать только строчку с именем расширения.extension_rustore_billing/ext.manifestname: RuStoreBilling - Создайте файл
extension_rustore_billing/src/rustorebilling.cpp— этот файл будет содержать JNI-вызовы нативных методов.
Сейчас оставьте этот файл пустым — описание его содержимого будет приведено далее. - Создайте папку
extension_rustore_billing/lib/android— в ней впоследствии нужно будет разместить созданные в дальнейшем файлы.jar, которые будут содержать обертку над классами из SDK RuStore billing. Эта обертка сделает методы SDK пригодными для вызова через JNI. - Создайте файл
extension_rustore_billing/manifests/android/build.gradle.
Структура расширени я RuStoreBilling на текущем этапе должна иметь следующий вид.
extension_rustore_billing/
│
├── lib/
│ └── android/
│
├── manifests/
│ └── android
│ └── build.gradle
│
├── src/
│ └── rustorebilling.cpp
│
└── ext.manifest
Создание точек входа в расширения
Далее займемся настройкой точек входа в расширения. Для этого Defold SDK использует макрос DM_DECLARE_EXTENSION.
RuStoreCore
Реализуйте вызов DM_DECLARE_EXTENSION для расширения RuStoreCore в ранее созданном файле extension_rustore_core/src/rustorecore.cpp.
#define EXTENSION_NAME RuStoreCore
#define LIB_NAME "RuStoreCore"
#define MODULE_NAME "rustorecore"
#if defined(DM_PLATFORM_ANDROID)
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#endif
static void LuaInit(lua_State* L)
{
int top = lua_gettop(L);
luaL_register(L, MODULE_NAME, Module_methods);
lua_pop(L, 1);
assert(top == lua_gettop(L));
}
static dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
{
LuaInit(params->m_L);
return dmExtension::RESULT_OK;
}
static dmExtension::Result UpdateMyExtension(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
static dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, nullptr, nullptr, InitializeMyExtension, UpdateMyExtension, nullptr, FinalizeMyExtension)
RuStoreBilling
Реализуйте вызов DM_DECLARE_EXTENSION для расширения RuStoreBilling в ранее созданном файле extension_rustore_billing/src/rustorebilling.cpp.
#define EXTENSION_NAME RuStoreBilling
#define LIB_NAME "RuStoreBilling"
#define MODULE_NAME "rustorebilling"
#if defined(DM_PLATFORM_ANDROID)
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#endif
static void LuaInit(lua_State* L)
{
int top = lua_gettop(L);
luaL_register(L, MODULE_NAME, Module_methods);
lua_pop(L, 1);
assert(top == lua_gettop(L));
}
static dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
{
LuaInit(params->m_L);
return dmExtension::RESULT_OK;
}
static dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, nullptr, nullptr, InitializeMyExtension, nullptr, nullptr, FinalizeMyExtension)
JNI-вызовы и обратные вызовы (callback)
Для примера рассмотрим создание всплывающего окна простого уведомления (тоста) в рамках создаваемого приложения. Отображение тоста можно реализовать с помощью вызова метода или с помощью обратного вызова (callback).
Создание метода отображения тоста
-
Создайте файл
extension_rustore_core/src/Example.javaсо следующим содержимым.extension_rustore_core/src/Example.javapackage ru.rustore.defold.example;
import android.app.Activity;
import android.widget.Toast;
public class Example {
public static void showToast(Activity activity, String message) {
activity.runOnUiThread(() -> Toast.makeText(activity, message, Toast.LENGTH_LONG).show());
}
} -
Для вызова метода
showToastв файлеextension_rustore_core/src/rustorecore.cppсоздайте метод, реализующий JNI-запросы.примечаниеКод нужно вставить в секцию
#if defined(DM_PLATFORM_ANDROID), чтобы код выполнялся только на устройствах Android, и перед массивомModule_methods.См. пример фаргмента кода До и После ниже.
- До
- После
extension_rustore_core/src/rustorecore.cpp//...
#if defined(DM_PLATFORM_ANDROID)
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
//...extension_rustore_core/src/rustorecore.cpp//...
#if defined(DM_PLATFORM_ANDROID)
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
static int ShowToast(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* msg = (char*)luaL_checkstring(L, 1);
jstring jmsg = env->NewStringUTF(msg);
jclass cls = dmAndroid::LoadClass(env, "ru/rustore/defold/example/Example");
jmethodID method = env->GetStaticMethodID(cls, "showToast", "(Landroid/app/Activity;Ljava/lang/String;)V");
env->CallStaticVoidMethod(cls, method, dmGraphics::GetNativeAndroidActivity(), jmsg);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
//... -
В этом же файле (
extension_rustore_core/src/rustorecore.cpp) в массивеModule_methodsдобавьте псевдоним методаshow_toastдля вызова через Lua (см. пример фрагмента кода до и после).- До
- После
extension_rustore_core/src/rustorecore.cpp//...
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
//...extension_rustore_core/src/rustorecore.cpp//...
static const luaL_reg Module_methods[] =
{
{"show_toast", ShowToast},
{0, 0}
};
#else
//...примечаниеСозданным методом можно воспользоваться в Lua через имя модуля, указанное в
MODULE_NAMEпри вызовеluaL_register. -
В файле
extension_rustore_billing/src/rustorebilling.cppвставьте строку#include <dmsdk/sdk.h>перед секцией#if defined(DM_PLATFORM_ANDROID)(см. пример кода ниже).- До
- После
extension_rustore_billing/src/rustorebilling.cpp#define EXTENSION_NAME RuStoreBilling
#define LIB_NAME "RuStoreBilling"
#define MODULE_NAME "rustorebilling"
#if defined(DM_PLATFORM_ANDROID)
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
//...extension_rustore_billing/src/rustorebilling.cpp#define EXTENSION_NAME RuStoreBilling
#define LIB_NAME "RuStoreBilling"
#define MODULE_NAME "rustorebilling"
#include <dmsdk/sdk.h>
#if defined(DM_PLATFORM_ANDROID)
static const luaL_reg Module_methods[] =
{
{0, 0}
};
#else
//... -
Откройте файл
main/main.scriptи добавьте в методon_inputвызова нашего метода (см. пример кода ниже).- До
- После
main.scriptfunction init(self)
msg.post(".", "acquire_input_focus")
msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
print("Touch!")
end
endmain.scriptfunction init(self)
msg.post(".", "acquire_input_focus")
msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
rustorecore.show_toast("Hello JNI")
end
end -
Выполните сборку:
- Project > Bundle > Android Application > Create Bindle.
ИЛИ - Project > Rebundle (если вы уже делали сборку во время сеанса работы с Defold).
- Project > Bundle > Android Application > Create Bindle.
Готово! Теперь каждый тап по экрану будет запускать тост (см. изображение ниже).

Запуск примера вызовов через JNI
Чтобы проверить работу сборки:
- запустите созданный ранее эмулятор Android;
- перетащите в него APK-файл из папки сборки;
- запустите приложение и в его интерфейсе сэмулируйте тап на экране смартфона.
Обратный в ызов (callback)
Обратный вызов (callback) лишь немногим сложнее. Для примера напишем обратный вызов, который срабатывает после вызова ShowToast и делает запись в Logcat средствами Defold.
Logcat - инструмент, который позволяет получить доступ к системным сообщениям Android.
- Модифицируйте класс
Exampleв файлеextension_rustore_core/src/Example.java, для этого:- добавьте приватный метод
NativeCallbackс ключевым словомnative; - используйте метод для получения текущей даты
Dateи вызовNativeCallback.
После всех доработок Java-код примет следующий вид.
- До
- После
extension_rustore_core/src/Example.javapackage ru.rustore.defold.example;
import android.app.Activity;
import android.widget.Toast;
public class Example {
public static void showToast(Activity activity, String message) {
activity.runOnUiThread(() -> Toast.makeText(activity, message, Toast.LENGTH_LONG).show());
}
}extension_rustore_core/src/Example.javapackage ru.rustore.defold.example;
import android.app.Activity;
import android.widget.Toast;
import java.util.Date;
public class Example {
private static native void NativeCallback(String value);
public static void showToast(Activity activity, String message) {
activity.runOnUiThread(() -> Toast.makeText(activity, message, Toast.LENGTH_LONG).show());
Date currentDate = new Date();
NativeCallback(currentDate.toString() + ": " + message);
}
} - добавьте приватный метод
- JNI требует использования соглашения о вызовах C для взаимодействия между кодом на Java и кодом на C++ — для этого в файле
extension_rustore_core/src/rustorecore.cppобъявите секциюextern "C"и внутри нее напишите функцию согласно соглашениям о вызовах JNI.- До
- После
extension_rustore_core/src/rustorecore.cpp//...
#if defined(DM_PLATFORM_ANDROID)
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
static int ShowToast(lua_State* L)
//...extension_rustore_core/src/rustorecore.cpp//...
#if defined(DM_PLATFORM_ANDROID)
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
#include <android/log.h>
extern "C"
{
JNIEXPORT void JNICALL Java_ru_rustore_defold_example_Example_NativeCallback(JNIEnv* env, jobject obj, jstring jvalue)
{
const char* value = env>GetStringUTFChars(jvalue, 0);
__android_log_print(ANDROID_LOG_INFO, "Example", value);
env>ReleaseStringUTFChars(jvalue, value);
}
}
static int ShowToast(lua_State* L)
//...
Описание кода представлено в таблице ниже.Элемент кода Описание extern "C" { }Объявление, которое указывает компилятору C++ на использование соглашения о вызовах C для функции, чтобы функция могла быть вызвана из кода на Java. JNIEXPORTМакрос, определенный в заголовочных файлах JNI, который обозначает экспорт функции для использования из других библиотек. Этот макрос обеспечивает правильное экспортирование функции для работы с JNI. JNICALLСпецификатор, который указывает на использование соглашения о вызовах Java для функции. Java_ru_rustore_defold_example_Example_NativeCallbackСигнатура JNI функции, включающая: idпакетаru_rustore_defold_example;- имя класса
Example; - название метода
NativeCallback.
JNIEnv* envУказатель на структуру JNIEnv, который предоставляет доступ к функциям JNI. Обязательный параметр функции JNI.jobject objОбъект, который вызывает метод NativeCallback. Обязательный параметр функции JNI.jstring jvalueПараметр, передаваемый со стороны Java-кода. GetStringUTFCharsМетод выполняет преобразование объекта jstringвconst char*.__android_log_printМетод из файла android/log.h, позволяет выводить сообщения вlogcat.ReleaseStringUTFCharsЭтот метод выполняет необходимое удаление объектов строк. - Выполните сборку:
- Project > Bundle > Android Application > Create Bindle.
ИЛИ - Project > Rebundle (если вы уже делали сборку во время сеанса работы с Defold).
Каждый показ тоста теперь сопровождается записью в Logcat вида:
Tue May 14 16:51:16 GMT 2024: Hello JNI.
- Project > Bundle > Android Application > Create Bindle.
Создание проекта в Android Studio
Весь необходимый нам нативный код можно написать непосредственно в редакторе Defold. Но работать в специальных IDE намного продуктивнее да и просто приятнее. Потому создадим проект Android и настроим его на генерацию файлов .jar, понятных Defold.
- Запустите Android Studio и создайте новый проект (кнопка New Project).
Отобразится следующее окно.
Создание нового проекта Android - В разделе Phone and Tablet выберите шаблон No Activity, после чего нажмите Next.
Отобразится окно New Project.
Создание нового проекта Android - Заполните поля, руководствуясь таблицей ниже.
Поле Описание Name Задайте название проекта: extension_libraries. Package name Задайте название пакета: ru.rustore.defold.billing. Save locaiton Задайте путь сохранения проекта. В настоящей инструкции будет использоваться путь C:\Projects\extension_libraries.Language Выберите язык программирования Kotlin. Minimum SDK Выберите SDK – API 24: Android ("Nougat"; Android 8.0). warningЕсли в вашей версии Android Studio присутствует настройка Build Configuration Language, выберите пункт Groovy DSL (build.gradle).
- Нажмите Finish.
Проект отобразится в окне Android Studio.
Проект extension_libraries создан - для удобства дальнейшей работы на вкладке Project слева вверху переключитесь на отображение структуры Project вместо Android (см. изображение ниже).
Структура проекта выглядит следующим образом.

Выбор формата отображение проекта
Отображение структуры проекта
Переходите к настройке проекта.
Первичная настройк а проекта
Чтобы настроить проект, выполните следующие действия.
- В корне проекта (
extension_libraries) найдите файлsettings.gradle: - Удалите из файла все данные, кроме строки
rootProject.name = "extension_libraries"(см. примеры кода До и После).- До
- После
settings.gradlepluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "extension_libraries"
include ':app'settings.gradlerootProject.name = "extension_libraries" - В корне проекта (
extension_libraries) найдите файлbuild.gradleи замените содержимое файла следующей конфигурацией.warningОбратите внимание, что переменная
rustore_example_folderдолжна содержать путь д о проекта Defold. В нашем примере это
c:\Defold\billing_example\. Для обратной наклонной черты нужно использовать экранирующий символ (см. пример До и После ниже.)- До
- После
build.gradle// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
}build.gradleext {
sdk_version_code = 1
rustore_example_folder = "c:\\Defold\\billing_example\\"
rustore_sdk_build_variant = 'release'
}
buildscript {
ext.kotlin_version = "1.7.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
maven {
url = uri("https://artifactory-external.vkpartner.ru/artifactory/maven")
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
} - Справа вверху нажимаем кнопку Sync Now (см. изображение ниже).

Синхронизация проектаподсказкаМогут появиться предупреждения (warning). Здесь и далее, если предупреждения не препятствуют дальнейшей работе, игнорируйте их.
- После обновления проекта удалите папку
appиз контекстного меню (см. изображение ниже).
Удаление папкиapp - В меню инструментов Android Studio выберите File > Project Structure (см. изображение ниже).

Отображение структуры проекта - В отобразившемся окне Project Structure убедитесь, что в разделе Project выставлены следующие настройки (см. таблицу и изображение ниже)
Перед созданием модулей про верьте, чтоб проект имеет следующие настройки (см. таблицу ниже)
Настройка Описание Android Gradle Plugin Version 4.1.1 Gradle Version 6.7.1 
Структура проекта - Выполните одно из следующий действий:
- если настройки соответствуют указанным выше, переходите к созданию модулей.
- если настройка Gradle Version отличается от 6.7.1, выберите эту версию в списке, после чего нажмите OK для подтверждения и продолжите процедуру.
После выбора версии Gradle Version 6.7.1 в Android Studio может отобразиться ошибка синхронизации.

Ошибка синхронизации проектаподсказкаЕсли ошибка не появилась, переходите к созданию модулей. В противном случае продолжите процедуру.
- В панели инструментов Android Studio выберите File > Settings.

Отображение настроек проекта - В открывшемся окне Settings перейдите в раздел Build, Extension, Deployment > Build Tools > Gradle.

Окно настроек проекта - В списке Gradle JDK выберите JDK версии 11 (см. изображение ниже).

Выбор версии JDK 11подсказкаЕсли JDK 11 в списке отсутствует, нажмите
под списком Gradle JDK и укажите путь к загруженной ранее версии JDK.
- Нажмите OK, чтобы сохранить изменения.
- Синхронизируйте проект.
Если все действия были выполнены верно, будет выполнена синхронизация.
Синхронизация успешна
Переходите к созданию модулей.
Создание модулей проекта
RuStoreDefoldCore
В Android Studio создайте новый модуль RuStoreDefoldCore. Для этого выполните следующие действия.
- В панели инструментов Android Studio выберите File > New > New Module.
- В открывшемся окне Create New Module в списке Templates выберите шаблон Android Library и выполните настройки, как показано в таблице ниже.
Поле Описание Module name Задайте значение RuStoreDefoldCore. Package name Задайте значение ru.rustore.defold.core. Language Выберите Kotlin. Bytecode Level Выберите 8 (slower build). Minimum SDK Выберите API 24 ("Nougat"; Android 8.0). Настройки должны быть как на изображении ниже.warningЕсли в вашей версии Android Studio присутствует настройка Build Configuration Language, выберите пункт Groovy DSL (build.gradle).

Создание модуля RuStoreDefoldCore - Нажмите Finish.
Модуль RuStoreDefoldCore создан. Переходите к настройке модуля.
build.gradle
Выполните следующие изменения в файле RuStoreDefoldCore/build.gradle
-
в разделе
androidнайдите и удалите следующую строкуnamespace 'ru.rustore.defold.core'.- До
- После
RuStoreDefoldCore/build.gradle//...
android {
namespace 'ru.rustore.defold.core'
compileSdk 34
defaultConfig {
//...RuStoreDefoldCore/build.gradle//...
android {
compileSdk 34
defaultConfig {
//... -
Замените содержимое раздела
dependencies, подключив следующие зависимости (см. пример До и После).- До
- После
RuStoreDefoldCore/build.gradle//..
dependencies {
implementation libs.appcompat.v7
testImplementation libs.junit
androidTestImplementation libs.runner
androidTestImplementation libs.espresso.core
}RuStoreDefoldCore/build.gradle//..
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
} -
В конец файла вставьте следующий код для извлечения и перемещения
.jar-файла.- До
- После
RuStoreDefoldCore/build.gradle//..
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
}RuStoreDefoldCore/build.gradle//..
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
}
task createJar(type: Copy) {
def aarFile = file( [buildDir.absolutePath, 'outputs', 'aar', "${project.name}-release.aar"].join(File.separator) )
def outputDir = file( ["$rustore_example_folder", 'extension_rustore_core', 'lib', 'android'].join(File.separator) )
from zipTree(aarFile)
include '**/*.jar'
into outputDir
rename { fileName ->
fileName.replace("classes.jar", "${project.name}.jar")
}
}
afterEvaluate {
assembleRelease.finalizedBy('createJar')
}
AndroidManifest.xml
- В папке
RuStoreDefoldCore/src/mainнайдите файлAndroidManifest.xml. - В тег
<manifest>добавьте атрибутpackage="ru.rustore.defold.core">, как показано ниже.- До
- После
RuStoreDefoldCore/src/main<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>RuStoreDefoldCore/src/main<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.rustore.defold.core">
</manifest>
RuStoreDefoldBilling
В Android Studio создайте новый модуль RuStoreDefoldBilling. Для этого выполните следующие действия.
- В панели инструментов Android Studio выберите File > New > New Module.
- В открывшемся окне Create New Module в списке Templates выберите шаблон Android Library и выполните настройки, как показано в таблице ниже.
Поле Описание Module name Задайте значение RuStoreDefoldBilling. Package name Задайте значение ru.rustore.defold.billing. Language Выберите Kotlin. Bytecode Level Выберите 8 (slower build). Minimum SDK Выберите API 24 ("Nougat"; Android 8.0). Настройки должны быть как на изображении ниже.warningЕсли в вашей версии Android Studio присутствует настройка Build Configuration Language, выберите пункт Groovy DSL (build.gradle).

Создание модуля RuStoreDefoldBilling - Нажмите Finish.
Модуль RuStoreDefoldBilling создан. Переходите к настройке модуля.
build.gradle
Выполните следующие изменения в файле RuStoreDefoldBilling/build.gradle.
- в разделе
androidнайдите и удалите строкуnamespace 'ru.rustore.defold.billing'.- До
- После
RuStoreDefoldBilling/build.gradle//...
android {
namespace 'ru.rustore.defold.billing'
compileSdk 34
defaultConfig {
//...RuStoreDefoldBilling/build.gradle//...
android {
compileSdk 34
defaultConfig {
//... - Замените содержимое раздела
dependencies, подключив следующие зависимости (см. пример До и После).- До
- После
RuStoreDefoldBilling/build.gradledependencies {
implementation libs.appcompat.v7
testImplementation libs.junit
androidTestImplementation libs.runner
androidTestImplementation libs.espresso.core
}RuStoreDefoldBilling/build.gradle//..
dependencies {
implementation "ru.rustore.sdk:billingclient:5.1.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
implementation project(path: ':RuStoreDefoldBilling')
} - В конец файла вставьте следующий код — он позволит распаковать
.aar-пакет для извлечения.jar-файла и поместит его в наш проект Defold (см. сравнение До и После).- До
- После
RuStoreDefoldBilling/build.gradle//...
dependencies {
implementation "ru.rustore.sdk:billingclient:5.1.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
implementation project(path: ':RuStoreDefoldBilling')
}RuStoreDefoldBilling/build.gradle//...
dependencies {
implementation "ru.rustore.sdk:billingclient:5.1.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "androidx.fragment:fragment:1.3.0"
implementation project(path: ':RuStoreDefoldBilling')
}
task createJar(type: Copy) {
def aarFile = file( [buildDir.absolutePath, 'outputs', 'aar', "${project.name}-${rustore_sdk_build_variant}.aar"].join(File.separator) )
def outputDir = file( ["$rustore_example_folder", 'extension_rustore_billing', 'lib', 'android'].join(File.separator) )
from zipTree(aarFile)
include '**/*.jar'
into outputDir
rename { fileName ->
fileName.replace("classes.jar", "${project.name}.jar")
}
}
afterEvaluate {
assembleRelease.finalizedBy('createJar')
}
AndroidManifest.xml
- В папке
RuStoreDefoldBilling/src/mainнайдите файлAndroidManifest.xml. - Удалите тег
<application>. В тег<manifest>добавьте атрибутpackage="ru.rustore.defold.billing">(см. ниже).- До
- После
RuStoreDefoldBilling/src/main/AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>RuStoreDefoldBilling/src/main/AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.rustore.defold.billing">
</manifest>
На этом настройка модуля окончена, и можно переходить к проверке работы сборки.
Проверка работы сборки
Чтобы проверить, как работает сборка, выполните следующие действия.
- В панели инструментов Android Studio выберите View > Tool Windows > Gradle (см. изображение ниже).
В правой части окна Android Studio отобразится область Gradle.

Отображение инструмента Gradle
Инструмент Gradle - Нажмите
в панели инструментов области Gradle.
Отобразится окно Run Anything.
Окно Run Anything - В верхней строке выполните команду
gralde assemble.
Если все действия были выполнены верно в Defold-проекте будут созданы файлы:
billing_example/extension_rustore_core/lib/android/RuStoreDefoldCore.jar;billing_example/extension_rustore_billing/lib/android/RuStoreDefoldBilling.jar.
К коду, расположенному в .jar-файлах, можно обращаться точно так же, как в примере Example.java.
На этом настройка Android проекта закончена и можно переходить к реализации обертки.
Классы для работы с JNI
Для упрощения использования JNI для наших плагинов реализуем обертку для обратных вызовов. Идея заключается в том, что вместо создания функций JNIEXPORT JNICALL для каждого обратного асинхронного вызова мы реализуем одну очередь сообщений с одним нативным событием onMessage для передачи новых данных. Кроме того, обработчику очереди будет делегирована задача синхронизации главного и побочных потоков, которые порождает SDK RuStore.
Реализация очереди сообщений на стороне Java
Создание структуры
- Откройте ранее созданный проект Android Studio.
- Раскройте папку
RuStoreDefoldCore/src/main/java/ - Щелкните правой кнопкой мыши на папке
ru.rustore.defold.coreи выберите New > Kotlin Class/File > Object (см. изображение ниже).
Создание нового объекта - В отобразившемся окне введите значение RuStoreCore и в списке ниже выберите Object (см. изображение ниже).
Создание объекта RuStoreCore - Нажмите клавишу ВВОД, чтобы создать объект.
примечание
Методы обработчика очереди сообщений перечислим в интерфейсе. Для порядка поместим интерфейс в отдельный пакет.
- Щелкните правой кнопкой мыши на папке
ru.rustore.defold.coreи выберите New > Package (см. изображение ниже).
Создание пакета - В отобразившемся окне введите значение
ru.rustore.defold.core.callbacks(см. изображение ниже).
Создание пакета - Нажмите клавишу ВВОД, чтобы создать пакет.
- Щелкните правой кнопкой мыши на созданном пакете
callbacksи выберите New > Kotlin Class/File (см. изображение ниже).
Создание нового интерфейса - В отобразившемся окне введите значение IRuStoreChannelListener и в списке ниже выберите Interface (см. изображение ниже).
Создание интерфейса IRuStoreChannelListener - Нажмите клавишу ВВОД, чтобы создать интерфейс.
примечание
Интерфейс будет реализовывать Kotlin-класс RuStoreChannelListenerWrapper.
- Щелкните правой кнопкой мыши на папке ru.rustore.defold.core и выберите New > Package (см. изображение ниже).

Создание пакета - В отобразившемся окне введите значение ru.rustore.defold.core.wrappers (см. изображение ниже).
Создание пакета - Нажмите клавишу ВВОД, чтобы создать пакет.
- Щелкните правой кнопкой мыши на созданном пакете
wrappersи выберите New > Kotlin Class/File (см. изображение ниже). - В отобразившемся окне введите значение RuStoreChannelListenerWrapper и в списке ниже выберите Class (см. изображение ниже).
Создание класса RuStoreChannelListenerWrapper - Нажмите клавишу ВВОД, чтобы создать класс.
После создания структуры содержимое RuStoreDefoldCore/src/main/java/ru.rustore.defold.core должно выглядеть следующим образом.
ru.rustore.defold.core/
│
├── callbacks/
│ └── IRuStoreChannelListener.kt
│
├── wrappers/
│ └── RuStoreChannelListenerWrapper.kt
│
├── RuStoreCore.kt
│
└── AndroidManifest.xml
Cм. также изображение ниже.

Создание класса RuStoreChannelListenerWrapper
Переходите непосредственно к реализации очереди сообщений.
Реализация
IRuStoreChannelListener
Откройте ранее созданный интерфейс IRuStoreChannelListener и добавьте туда два метода:
fun onMessage;fun disposeCppPointer.
- До
- После
package ru.rustore.defold.core.callbacks
interface IRuStoreChannelListener {
}
package ru.rustore.defold.core.callbacks
interface IRuStoreChannelListener {
fun onMessage(channel: String, value: String)
fun disposeCppPointer()
}
- Метод
onMessageбудет реализовывать отправку сообщений на сторону Defold. ПараметрыchannelиvalueтипаStringописывают канал сообщений для разделения адресатов сообщений и полезные данные сообщения соответственно. - Метод
disposeCppPointerиспользуется для освобождения указателя на объект на стороне C++. Этот указатель нужен для перехода к объекту из функции в блокеextern "C".
RuStoreChannelListenerWrapper
В ранее созданном классе RuStoreChannelListenerWrapper добавьте параметр конструктора cppPointer типа Long, наследование от интерфейса IRuStoreChannelListener и реализуйте унаследованные методы (см. пример До и После ниже).
- До
- После
package ru.rustore.defold.core.wrappers
class RuStoreChannelListenerWrapper {
}
package ru.rustore.defold.core.wrappers
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
class RuStoreChannelListenerWrapper(cppPointer: Long): IRuStoreChannelListener {
private val mutex = Any()
private var cppPointer: Long = 0
private external fun nativeOnMessage(pointer: Long, channel: String, value: String)
init {
this.cppPointer = cppPointer
}
override fun onMessage(channel: String, value: String) {
synchronized(mutex) {
if (cppPointer != 0L) {
nativeOnMessage(cppPointer, channel, value)
}
}
}
override fun disposeCppPointer() {
synchronized(mutex) { cppPointer = 0 }
}
}
- Конструктор класса: в конструкторе
RuStoreChannelListenerWrapperмы принимаем значение указателя на объект C++ в виде параметраcppPointer: Longи сохраняем его в одноименной переменнойcppPointerв блокеinit. nativeOnMessage— метод объявлен какexternal, что означает, что его реализация находится в коде на языке C++. Именно этот метод предназначен для передачи сообщения из нативного кода на сторону скриптов Defold.onMessageявляется реализацией метода интерфейсаIRuStoreChannelListener, который вызывается при задаче отправки сообщения через JNI на сторону Defold. В методе происходит вызовnativeOnMessageс передачей указателя cppPointer, значений канала и полезной нагрузки сообщения.disposeCppPointerявляется реализацией метода интерфейсаIRuStoreChannelListener. Метод вызывается со стороны C++ при удалении объекта С++.- Синхронизация: для обеспечения потокобезопасности в классе используется функция
synchronized. МетодыonMessageиdisposeCppPointerсинхронизируются, чтобы избежать попыток обнуления указателя во время передачи данных.
RuStoreCore
В ранее созданном объекте RuStoreCore реализуйте методы для установки объекта обработки очереди сообщений и метод для отправки сообщения:
setChannelListener;emitSignal.
- До
- После
package ru.rustore.defold.core
object RuStoreCore {
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
object RuStoreCore {
private var listener: IRuStoreChannelListener? = null
fun setChannelListener(listener: IRuStoreChannelListener) {
RuStoreCore.listener = listener
}
fun emitSignal(channel: String, value: String) {
listener?.run {
onMessage(channel, value)
}
}
}
setChannelListener— метод используется для установки обработчика очереди сообщений с интерфейсомIRuStoreChannelListenerв объектRuStoreCore.emitSignal— метод используется для передачи сообщения с указанным каналом и значением обработчику очереди, установленному ранее с помощью методаsetChannelListener. Во избежание ошибок в методе происходит проверка, что переменнаяlistenerинициализирована.
Переходите к реализации очереди сообщений на стороне Defold.
Реализация очереди сообщений на стороне Defold
Создание структуры
Методы для работы с очередью разместим в расширении RuStoreCore.
- Откройте ранее созданный проект Defold.
- В папк е
extension_rustore_core/includeсоздайте заголовочные файлы:AndroidJavaObject.h;ChannelCallbackManager.h;QueueCallbackManager.h;RuStoreChannelListener.h.
- В папке
extension_rustore_core/srcсоздайте файлы исходного кода:ChannelCallbackManager.cpp;QueueCallbackManager.cpp;RuStoreChannelListener.cpp. Структура Defold-проекта будет выглядеть следующим образом.
extension_rustore_core/
│
├── include/
│ ├── AndroidJavaObject.h
│ ├── ChannelCallbackManager.h
│ ├── QueueCallbackManager.h
│ └── RuStoreChannelListener.h
│
├── lib/
│ └── android/
│ └──RuStoreDefoldCore.jar
│
├── manifests/
│ └── android
│ └── build.gradle
│
├── src/
│ ├── ChannelCallbackManager.cpp
│ ├── Example.java
│ ├── QueueCallbackManager.cpp
│ ├── RuStoreChannelListener.cpp
│ └── rustorecore.cpp
│
└── ext.manifest
Реализация
AndroidJavaObject
В файл extension_rustore_core/include/AndroidJavaObject.h поместите следующий код.
Класс AndroidJavaObject служит для хранения информации о Java-объекте с которым можно взаимодействовать посредством JNI.
#pragma once
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
namespace RuStoreSDK
{
class AndroidJavaObject
{
public:
jclass cls;
jobject obj;
AndroidJavaObject()
{
cls = nullptr;
obj = nullptr;
}
};
}
jclass cls- указатель на класс Java.jobject obj- указатель на объект Java.
ChannelCallbackManager
- В файл
extension_rustore_core/include/ChannelCallbackManager.hпоместите классChannelCallbackManagerи структуруChannelCallbackItem.C++ | ChannelCallbackManager.h#pragma once
#include <dmsdk/sdk.h>
#include <vector>
#include <mutex>
namespace RuStoreSDK
{
struct ChannelCallbackItem
{
const char* channel;
dmScript::LuaCallbackInfo* callback;
ChannelCallbackItem(const char* ch, dmScript::LuaCallbackInfo* cb) : channel(ch), callback(cb) {}
};
class ChannelCallbackManager
{
private:
std::mutex _mutex;
std::vector<std::shared_ptr<ChannelCallbackItem>> _callbacks;
public:
void AddChannelCallback(std::shared_ptr<ChannelCallbackItem> item);
std::vector<dmScript::LuaCallbackInfo*> FindLuaCallbacksByChannel(const char* channel);
static ChannelCallbackManager* Instance();
};
}к сведению- Структура
ChannelCallbackItemиспользуется для хранения информации о канале и связанной с ним callback-функции. const char* channel- указатель на строку, представляющую имя канала.LuaCallbackInfo* callback- указатель на callback-функцию в Lua, которая будет вызвана при передаче сообщения в канал.- Класс
ChannelCallbackManagerреализует менеджер для управления callback-функциями, связанными с каналами. Он предоставляет методы для добавления новых callback-функций к определенному каналу и получения всех callback-функций, связанных с заданным каналом. Методы класса:AddChannelCallback- метод для добавления нового канала данных;FindLuaCallbacksByChannel- метод для поиска всех callback-функций, связанных с заданным каналом;Instance- статический метод для получения Singleton-экземпляра класса.
- Структура
- В файл
extension_rustore_core/src/ChannelCallbackManager.cppпоместите следующий код.C++ | ChannelCallbackManager.cpp#include "ChannelCallbackManager.h"
using namespace RuStoreSDK;
void ChannelCallbackManager::AddChannelCallback(std::shared_ptr<ChannelCallbackItem> item)
{
std::lock_guard<std::mutex> lock(_mutex);
_callbacks.push_back(item);
}
std::vector<dmScript::LuaCallbackInfo*> ChannelCallbackManager::FindLuaCallbacksByChannel(const char* channel)
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<dmScript::LuaCallbackInfo*> result;
for (const auto& callback : _callbacks)
{
if (std::strcmp(callback->channel, channel) == 0)
{
result.push_back(callback->callback);
}
}
return result;
}
ChannelCallbackManager* ChannelCallbackManager::Instance()
{
static ChannelCallbackManager instance;
return &instance;
}
QueueCallbackManager
- В файл
extension_rustore_core/include/QueueCallbackManager.hпоместите классQueueCallbackManagerи структуруQueueCallbackItem.C++ | QueueCallbackManager.h#pragma once
#include <queue>
#include <mutex>
namespace RuStoreSDK
{
struct QueueCallbackItem
{
const std::string channel;
const std::string value;
QueueCallbackItem(const std::string& channel, const std::string& value)
: channel(channel), value(value) {
}
};
class QueueCallbackManager
{
private:
std::mutex _mutex;
std::queue<std::shared_ptr<QueueCallbackItem>> _queue;
public:
void PushQueueCallback(std::shared_ptr<QueueCallbackItem> item);
std::queue<std::shared_ptr<QueueCallbackItem>> GetExexuteQueueCallback(int max = -1);
static QueueCallbackManager* Instance();
};
}к сведению- Структура
QueueCallbackItemиспользуется для хранения информации о канале и значении, которое должно быть передано callback-функции:string channel- строка, представляющая имя канала;string value- передаваемое значение.
- Класс
QueueCallbackManagerреализует менеджер для управления данными для каналов сообщений. Он предоставляет методы для добавления новых данных связанных с определенным каналом и получения всех данных для передачи в каналы. Ниже представлены методы класса.PushQueueCallback- метод для добавления новых данных в очередь.GetExexuteQueueCallback- метод для получения очереди данных связанных с каналами. Параметрmaxпозволяет ограничить количество данных, которые будут возвращены. При передаче в параметреmaxотрицательного числа будут возвращен весь доступный набор данных.Instance- статический метод для получения Singleton-экземпляра класса.
- Структура
- В файл
extension_rustore_core/src/QueueCallbackManager.cppпоместите следующий код.C++ | QueueCallbackManager.cpp#include "QueueCallbackManager.h"
using namespace RuStoreSDK;
void QueueCallbackManager::PushQueueCallback(std::shared_ptr<QueueCallbackItem> item)
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(item);
}
std::queue<std::shared_ptr<QueueCallbackItem>> QueueCallbackManager::GetExexuteQueueCallback(int max)
{
std::lock_guard<std::mutex> lock(_mutex);
std::queue<std::shared_ptr<QueueCallbackItem>> executeQueue;
while (!_queue.empty() && max !=0)
{
auto item = _queue.front();
executeQueue.push(item);
_queue.pop();
--max;
}
return executeQueue;
}
QueueCallbackManager* QueueCallbackManager::Instance()
{
static QueueCallbackManager instance;
return &instance;
}
RuStoreChannelListener
- В файл
extension_rustore_core/include/RuStoreChannelListener.hпоместите следующий код, который включает классRuStoreChannelListener.C++ | RuStoreChannelListener.h#pragma once
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
#include "QueueCallbackManager.h"
namespace RuStoreSDK
{
class RuStoreChannelListener
{
private:
jobject javaObject = nullptr;
const char* signature = "Lru/rustore/defold/core/callbacks/IRuStoreChannelListener;";
public:
jobject GetJWrapper();
const char* GetSignature();
RuStoreChannelListener();
~RuStoreChannelListener();
void _OnMessage(JNIEnv* env, jstring jchannel, jstring jvalue);
static RuStoreChannelListener* Instance();
};
} - В файл
extension_rustore_core/src/RuStoreChannelListener.cppпоместите следующий код.C++ | RuStoreChannelListener.cpp#include "RuStoreChannelListener.h"
#if defined(DM_PLATFORM_ANDROID)
using namespace RuStoreSDK;
void RuStoreChannelListener::_OnMessage(JNIEnv* env, jstring jchannel, jstring jvalue)
{
const char* channel = env->GetStringUTFChars(jchannel, 0);
const char* value = env->GetStringUTFChars(jvalue, 0);
auto queueCallbackItem = std::make_shared<QueueCallbackItem>((std::string(channel)), (std::string(value)));
QueueCallbackManager::Instance()->PushQueueCallback(queueCallbackItem);
env->ReleaseStringUTFChars(jchannel, channel);
env->ReleaseStringUTFChars(jvalue, value);
}
extern "C"
{
JNIEXPORT void JNICALL Java_ru_rustore_defold_core_wrappers_RuStoreChannelListenerWrapper_nativeOnMessage(JNIEnv* env, jobject obj, jlong pointer, jstring channel, jstring value)
{
auto castobj = reinterpret_cast<RuStoreChannelListener*>(pointer);
castobj->_OnMessage(env, channel, value);
}
}
#endifРассылка данных очереди классак сведению- Класс реализует создание Java-объекта с интерфейсом
IRuStoreChannelListenerи получение обратных вызовов с данными от него в методе_OnMessage. - Метод
_OnMessageпри этом реализует наполнение данными очереди классаQueueCallbackManager.
QueueCallbackManagerпо всем каналам, заданным в классеChannelCallbackManager, реализуетс я в методеUpdateMyExtensionв расширенииRuStoreCore(extension_rustore_core/src/rustorecore.cpp). Добавление новых каналов осуществляется в методеConnect. - Класс реализует создание Java-объекта с интерфейсом
rustorecore.cpp
Добавьте в extension_rustore_core/src/rustorecore.cpp следующий код (см. пример До и После).
- До
- После
#define EXTENSION_NAME RuStoreCore
#define LIB_NAME "RuStoreCore"
#define MODULE_NAME "rustorecore"
#if defined(DM_PLATFORM_ANDROID)
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
#include <android/log.h>
extern "C"
{
JNIEXPORT void JNICALL Java_ru_rustore_defold_example_Example_NativeCallback(JNIEnv* env, jobject obj, jstring jvalue)
{
const char* value = env>GetStringUTFChars(jvalue, 0);
__android_log_print(ANDROID_LOG_INFO, "Example", value);
env>ReleaseStringUTFChars(jvalue, value);
}
}
//...
#endif
static void LuaInit(lua_State* L)
//..
static dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
//..
static dmExtension::Result UpdateMyExtension(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
static dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
//..
DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, nullptr, nullptr, InitializeMyExtension, UpdateMyExtension, nullptr, FinalizeMyExtension)
#define EXTENSION_NAME RuStoreCore
#define LIB_NAME "RuStoreCore"
#define MODULE_NAME "rustorecore"
#if defined(DM_PLATFORM_ANDROID)
#include <dmsdk/sdk.h>
#include <dmsdk/dlib/android.h>
#include <android/log.h>
#include "RuStoreChannelListener.h"
#include "ChannelCallbackManager.h"
#include "AndroidJavaObject.h"
using namespace RuStoreSDK;
static int Connect(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
const char* channel = (char*)luaL_checkstring(L, 1);
dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 2);
auto channelCallbackItem = std::make_shared<ChannelCallbackItem>(channel, callback);
ChannelCallbackManager::Instance()->AddChannelCallback(channelCallbackItem);
return 0;
}
extern "C"
{
JNIEXPORT void JNICALL Java_ru_rustore_defold_example_Example_NativeCallback(JNIEnv* env, jobject obj, jstring jvalue)
{
const char* value = env>GetStringUTFChars(jvalue, 0);
__android_log_print(ANDROID_LOG_INFO, "Example", value);
env>ReleaseStringUTFChars(jvalue, value);
}
JNIEXPORT jobject JNICALL Java_ru_rustore_defold_core_wrappers_DefoldPlayerWrapper_NativeOnActivityRequest(JNIEnv* env, jobject obj)
{
return dmGraphics::GetNativeAndroidActivity();
}
}
//...
#endif
static void LuaInit(lua_State* L)
//..
static dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
//..
static dmExtension::Result UpdateMyExtension(dmExtension::Params* params)
{
auto queue = QueueCallbackManager::Instance()->GetExexuteQueueCallback();
while (!queue.empty())
{
auto item = queue.front();
queue.pop();
const char* channel = item->channel.c_str();
const char* value = item->value.c_str();
auto _callbacks = ChannelCallbackManager::Instance()->FindLuaCallbacksByChannel(channel);
for (const auto& callback : _callbacks)
{
lua_State* L = dmScript::GetCallbackLuaContext(callback);
DM_LUA_STACK_CHECK(L, 0);
if (!dmScript::SetupCallback(callback)) continue;
lua_pushstring(L, channel);
lua_pushstring(L, value);
dmScript::PCall(L, 3, 0);
dmScript::TeardownCallback(callback);
}
}
return dmExtension::RESULT_OK;
}
static dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
//..
DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, nullptr, nullptr, InitializeMyExtension, UpdateMyExtension, nullptr, FinalizeMyExtension)
Работа с очередью сообщений
После реализации очереди сообщений работа с обратными вызовами со стороны Java в Defold больше не представляет труда. На стороне Java (проект в Android Studio) достаточно подготовить данные в виде имени канала и строки данных, и вызвать метод emitSignal объекта RuStoreCore.
Ниже представлен пример кода в общем случае (файл extension_libraries/RuStoreDefoldBilling/src/main/java/ru.rustore.defold.billing/RustoreBilling.kt)
import ru.rustore.defold.core.RuStoreCore
const val CHANNEL_NAME = "rustore_channel_name"
val json = %YOUR_DATA%
RuStoreCore.emitSignal(CHANNEL_NAME, json)
Здесь:
CHANNEL_NAME— имя канала очереди сообщений, например:CHANNEL_CHECK_PURCHASES_AVAILABLE_SUCCESS— сообщение об успешное проверки доступности платежей;
rustore_channel_name, например:rustore_check_purchases_available_success.%YOUR_DATA%— данные ответа по заданному формату, например:"""{"isAvailable": false, "cause": $cause}"""(невозможность проведения платежей с указанием причины).
Подробнее см. пример на GitFlic.
В проекте Defold в методе connect объекта rustorecore укажите имя канала и функцию обратного вызова.
Ниже представлен пример кода в общем случае (файл billing_example/main/main.script).
function init(self)
rustorecore.connect("rustore_channel_name", _callback_method)
function _callback_method(self, channel, value)
rustorecore.show_toast(value)
end
Здесь:
rustore_channel_name— имя канала для очереди сообщений;_callback_method— имя метода обратного вызова.
Подробнее см. пример на GitFlic.
Методы расширений
RuStoreCore
Методы расширения RuStoreCore реализуют систему обратных вызовов Java > C++ через именованные каналы сообщений, а также работу с наиболее часто используемыми методами SDK Android. Все методы плагина, доступные для вызовов из Lua, определены в массиве Module_methods.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
static const luaL_reg Module_methods[] =
{
{"show_toast", ShowToast},
{"connect", Connect},
{"log_verbose", LogVerbose},
{"log_debug", LogDebug},
{"log_info", LogInfo},
{"log_warning", LogWarning},
{"log_error", LogError},
{"copy_to_clipboard", CopyToClipboard},
{"get_from_clipboard", GetFromClipboard},
{"get_string_resources", GetStringResources},
{"get_string_shared_preferences", GetStringSharedPreferences},
{"set_string_shared_preferences", SetStringSharedPreferences},
{"get_int_shared_preferences", GetIntSharedPreferences},
{"set_int_shared_preferences", SetIntSharedPreferences},
{0, 0}
};
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
show_toast
Метод используется для отображения всплывающего уведомления.
Ранее для примера работы с JNI мы реализовали Java-обертку этого метода в файле Example.java. Но теперь у нас есть более подходящее место для размещения нативного кода. Создадим обертку метода в проекте Android на языке Kotlin.
Открываем в Android Studio проект extension_libraries, открываем файл RuStoreCore.kt. Файл расположен по пути RuStoreDefoldCore/src/main/java/ru.rustore.defold.core. Реализация метода на Kotlin будет следующей.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
object RuStoreCore {
private var listener: IRuStoreChannelListener? = null
fun setChannelListener(listener: IRuStoreChannelListener) {
RuStoreCore.listener = listener
}
fun emitSignal(channel: String, value: String) {
listener?.run {
onMessage(channel, value)
}
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
private var listener: IRuStoreChannelListener? = null
fun setChannelListener(listener: IRuStoreChannelListener) {
RuStoreCore.listener = listener
}
fun emitSignal(channel: String, value: String) {
listener?.run {
onMessage(channel, value)
}
}
fun showToast(activity: Activity, message: String) {
activity.runOnUiThread { Toast.makeText(activity, message, Toast.LENGTH_LONG).show() }
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
Далее в проекте Defold нам необходимо исправить JNI-вызов потому что изменилось расположение Java-метода. Для этого открываем в редакторе Defold файл rustorecore.cpp и находим реализацию ShowToast.
Для обеспечения работы нового метода нам достаточно изменить путь к классу ru.rustore.defold.example.Example на новый ru.rustore.defold.core.RuStoreCore, а также заменить статические методы GetStaticMethodID и CallStaticVoidMethodID нестатическими вариантами.
Еще одним важным дополнением станет использование метода GetJavaCoreInstance. Т.к. все методы расширения работают с Java-объектом ru.rustore.defold.core.RuStoreCore, вынесем работу по получению ссылки на объект в отдельный метод. Для этого открываем в редакторе Defold файл rustorecore.cpp и в секции #if defined(DM_PLATFORM_ANDROID) добавляем следующий код.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
#define EXTENSION_NAME RuStoreCore
//...
#if defined(DM_PLATFORM_ANDROID)
//...
using namespace RuStoreSDK;
//...
#define EXTENSION_NAME RuStoreCore
//...
#if defined(DM_PLATFORM_ANDROID)
//...
using namespace RuStoreSDK;
static void GetJavaCoreInstance(JNIEnv* env, AndroidJavaObject* instance)
{
jclass cls = dmAndroid::LoadClass(env, "ru.rustore.defold.core.RuStoreCore");
jfieldID instanceField = env->GetStaticFieldID(cls, "INSTANCE", "Lru/rustore/defold/core/RuStoreCore;");
jobject obj = env->GetStaticObjectField(cls, instanceField);
instance->cls = cls;
instance->obj = obj;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
Внутри метода выполняются следующие шаги.
- Получение класса
ru.rustore.defold.core.RuStoreCoreс помощью функцииdmAndroid::LoadClass. - Получение идентификатора статического поля
INSTANCEс помощьюenv->GetStaticFieldID. - Получение объекта из статического поля INSTANCE с помощью
env->GetStaticObjectField. - Сохранение класса и объекта в переданной через указатель переменной
instance.
Итого получаем следующую реализацию метода ShowToast.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* msg = (char*)luaL_checkstring(L, 1);
jstring jmsg = env->NewStringUTF(msg);
jclass cls = dmAndroid::LoadClass(env, "ru/rustore/defold/example/Example");
jmethodID method = env->GetStaticMethodID(cls, "showToast", "(Landroid/app/Activity;Ljava/lang/String;)V");
env->CallStaticVoidMethod(cls, method, dmGraphics::GetNativeAndroidActivity(), jmsg);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
//...
static int ShowToast(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* msg = (char*)luaL_checkstring(L, 1);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GetJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "showToast", "(Landroid/app/Activity;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jmsg);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
connect
Метод connect предназначен для установления соединения с внешним источником данных или сервисом.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.connect("rustore_check_purchases_available_success", _check_purchases_available_success)
rustorecore.connect("rustore_check_purchases_available_failure", _check_purchases_available_failure)
rustorecore.connect("rustore_on_get_products_success", _on_get_products_success)
rustorecore.connect("rustore_on_get_products_failure", _on_get_products_failure)
rustorecore.connect("rustore_on_purchase_product_success", _on_purchase_product_success)
rustorecore.connect("rustore_on_purchase_product_failure", _on_purchase_product_failure)
rustorecore.connect("rustore_on_get_purchases_success", _on_get_purchases_success)
rustorecore.connect("rustore_on_get_purchases_failure", _on_get_purchases_failure)
rustorecore.connect("rustore_on_confirm_purchase_success", _on_confirm_purchase_success)
rustorecore.connect("rustore_on_confirm_purchase_failure", _on_confirm_purchase_failure)
rustorecore.connect("rustore_on_delete_purchase_success", _on_delete_purchase_success)
rustorecore.connect("rustore_on_delete_purchase_failure", _on_delete_purchase_failure)
rustorecore.connect("rustore_on_get_purchase_info_success", _on_get_purchase_info_success)
rustorecore.connect("rustore_on_get_purchase_info_failure", _on_get_purchase_info_failure)
Выше приведен пример подписки на все обратные вызовы (callback) плагина. Это вызовы в коде Lua. В общем случае нужно выполнить следующие действия.
- Реализовать метод на Java/Kotlin.
- Обернуть в JNI в C++.
- Вызвать из Lua.
Работа с rustorecore.connect описана в разделе Работа с очередью сообщений.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
log_verbose
Метод используется для записи подробных отладочных сообщений в журнал Logcat.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.log_verbose(LOG_TAG, value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun showToast(activity: Activity, message: String) {
activity.runOnUiThread { Toast.makeText(activity, message, Toast.LENGTH_LONG).show() }
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun showToast(activity: Activity, message: String) {
activity.runOnUiThread { Toast.makeText(activity, message, Toast.LENGTH_LONG).show() }
}
fun logVerbose(tag: String, message: String) {
Log.v(tag, message)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
static int LogVerbose(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* tag = (char*)luaL_checkstring(L, 1);
const char* msg = (char*)luaL_checkstring(L, 2);
jstring jtag = env->NewStringUTF(tag);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "logVerbose", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, jtag, jmsg);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
log_debug
Метод используется для записи отладочных сообщений в журнал Logcat.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.log_debug(LOG_TAG, value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logVerbose(tag: String, message: String) {
Log.v(tag, message)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logVerbose(tag: String, message: String) {
Log.v(tag, message)
}
fun logDebug(tag: String, message: String) {
Log.d(tag, message)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
static int LogVerbose(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
static int LogVerbose(lua_State* L)
//...
static int LogDebug(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* tag = (char*)luaL_checkstring(L, 1);
const char* msg = (char*)luaL_checkstring(L, 2);
jstring jtag = env->NewStringUTF(tag);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "logDebug", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, jtag, jmsg);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
log_info
Метод используется для записи информационных сообщений в журнал Logcat.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.log_info(LOG_TAG, value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logDebug(tag: String, message: String) {
Log.d(tag, message)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logDebug(tag: String, message: String) {
Log.d(tag, message)
}
fun logInfo(tag: String, message: String) {
Log.i(tag, message)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogDebug(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogDebug(lua_State* L)
//...
static int LogInfo(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* tag = (char*)luaL_checkstring(L, 1);
const char* msg = (char*)luaL_checkstring(L, 2);
jstring jtag = env->NewStringUTF(tag);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "logInfo", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, jtag, jmsg);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//..
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
log_warning
Метод используется для записи предупреждающих сообщений в журнал Logcat.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.log_warning(LOG_TAG, value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logInfo(tag: String, message: String) {
Log.i(tag, message)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logInfo(tag: String, message: String) {
Log.i(tag, message)
}
fun logWarning(tag: String, message: String) {
Log.w(tag, message)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogInfo(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogInfo(lua_State* L)
//...
static int LogWarning(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* tag = (char*)luaL_checkstring(L, 1);
const char* msg = (char*)luaL_checkstring(L, 2);
jstring jtag = env->NewStringUTF(tag);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "logWarning", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, jtag, jmsg);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
log_error
Метод используется для записи сообщений об ошибках в журнал Logcat.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.log_error(LOG_TAG, value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logWarning(tag: String, message: String) {
Log.w(tag, message)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logWarning(tag: String, message: String) {
Log.w(tag, message)
}
fun logError(tag: String, message: String) {
Log.e(tag, message)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- Д о
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogWarning(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogWarning(lua_State* L)
//...
static int LogError(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* tag = (char*)luaL_checkstring(L, 1);
const char* msg = (char*)luaL_checkstring(L, 2);
jstring jtag = env->NewStringUTF(tag);
jstring jmsg = env->NewStringUTF(msg);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "logError", "(Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, jtag, jmsg);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jmsg);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
copy_to_clipboard
Метод используется для копирования текстовой информации в буфер обмена устройства.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.copy_to_clipboard(value)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logError(tag: String, message: String) {
Log.e(tag, message)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun logError(tag: String, message: String) {
Log.e(tag, message)
}
fun copyToClipboard(activity: Activity, text: String) {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(CLIP_DATA_TOOLTIP, text)
clipboard.setPrimaryClip(clip)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogError(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int LogError(lua_State* L)
//...
static int CopyToClipboard(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* text = (char*)luaL_checkstring(L, 1);
jstring jtext = env->NewStringUTF(text);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "copyToClipboard", "(Landroid/app/Activity;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jtext);
env->DeleteLocalRef(jtext);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
get_from_clipboard
Метод позволяет получить текстовую информацию из буфера обмена устройства.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
local value = rustorecore.get_from_clipboard()
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun copyToClipboard(activity: Activity, text: String) {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(CLIP_DATA_TOOLTIP, text)
clipboard.setPrimaryClip(clip)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun copyToClipboard(activity: Activity, text: String) {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(CLIP_DATA_TOOLTIP, text)
clipboard.setPrimaryClip(clip)
}
fun getFromClipboard(activity: Activity): String {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = clipboard.primaryClip ?: return String()
val text = clip.getItemAt(0).text ?: return String()
return text.toString()
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int CopyToClipboard(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int CopyToClipboard(lua_State* L)
//...
static int GetFromClipboard(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "getFromClipboard", "(Landroid/app/Activity;)Ljava/lang/String;");
jstring jtext = (jstring)env->CallObjectMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity());
const char* ctext = env->GetStringUTFChars(jtext, nullptr);
lua_pushstring(L, ctext);
env->ReleaseStringUTFChars(jtext, ctext);
thread.Detach();
return 1;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
get_string_resources
Метод позволяет получить строковые ресурсы из ресурсов приложения.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
local value = rustorecore.get_string_resources(name)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getFromClipboard(activity: Activity): String {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = clipboard.primaryClip ?: return String()
val text = clip.getItemAt(0).text ?: return String()
return text.toString()
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getFromClipboard(activity: Activity): String {
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = clipboard.primaryClip ?: return String()
val text = clip.getItemAt(0).text ?: return String()
return text.toString()
}
fun getStringResources(activity: Activity, name: String): String {
val application = activity.application
val id: Int = application.resources.getIdentifier(name, "string", application.packageName)
return application.getString(id)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetFromClipboard(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetFromClipboard(lua_State* L)
//...
static int GetStringResources(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* name = (char*)luaL_checkstring(L, 1);
jstring jname = env->NewStringUTF(name);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "getStringResources", "(Landroid/app/Activity;Ljava/lang/String;)Ljava/lang/String;");
jstring jtext = (jstring)env->CallObjectMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jname);
const char* ctext = env->GetStringUTFChars(jtext, nullptr);
lua_pushstring(L, ctext);
env->ReleaseStringUTFChars(jtext, ctext);
env->DeleteLocalRef(jname);
thread.Detach();
return 1;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
get_string_shared_preferences
Метод используется для получения строкового значения из настроек SharedPreferences.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
local value = rustorecore.get_string_shared_preferences(STORAGE_NAME, KEY, DEFAULT)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getStringResources(activity: Activity, name: String): String {
val application = activity.application
val id: Int = application.resources.getIdentifier(name, "string", application.packageName)
return application.getString(id)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getStringResources(activity: Activity, name: String): String {
val application = activity.application
val id: Int = application.resources.getIdentifier(name, "string", application.packageName)
return application.getString(id)
}
fun getStringSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: String): String {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getString(key, defaultValue).orEmpty()
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetStringResources(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetStringResources(lua_State* L)
//...
static int GetStringSharedPreferences(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* storageName = (char*)luaL_checkstring(L, 1);
jstring jstorageName = env->NewStringUTF(storageName);
const char* key = (char*)luaL_checkstring(L, 2);
jstring jkey = env->NewStringUTF(key);
const char* defaultValue = (char*)luaL_checkstring(L, 3);
jstring jdefaultValue = env->NewStringUTF(defaultValue);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "getStringSharedPreferences", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
jstring jtext = (jstring)env->CallObjectMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jstorageName, jkey, jdefaultValue);
const char* ctext = env->GetStringUTFChars(jtext, nullptr);
lua_pushstring(L, ctext);
env->ReleaseStringUTFChars(jtext, ctext);
env->DeleteLocalRef(jstorageName);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jdefaultValue);
thread.Detach();
return 1;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
get_int_shared_preferences
Метод используется для получения целочисленного значения из настроек SharedPreferences.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
local value = rustorecore.get_int_shared_preferences(STORAGE_NAME, KEY, DEFAULT)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getStringSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: String): String {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getString(key, defaultValue).orEmpty()
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getStringSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: String): String {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getString(key, defaultValue).orEmpty()
}
fun getIntSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: Int): Int {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getInt(key, defaultValue)
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetStringSharedPreferences(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetStringSharedPreferences(lua_State* L)
//...
static int GetIntSharedPreferences(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* storageName = (char*)luaL_checkstring(L, 1);
jstring jstorageName = env->NewStringUTF(storageName);
const char* key = (char*)luaL_checkstring(L, 2);
jstring jkey = env->NewStringUTF(key);
jint jdefaultValue = static_cast<jint>(luaL_checkint(L, 3));
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "getIntSharedPreferences", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;I)I");
int value = (int)env->CallIntMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jstorageName, jkey, jdefaultValue);
lua_pushinteger(L, value);
env->DeleteLocalRef(jstorageName);
env->DeleteLocalRef(jkey);
thread.Detach();
return 1;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
set_string_shared_preferences
Метод позволяет установить строковое значение в настройках SharedPreferences.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.set_string_shared_preferences(STORAGE_NAME, KEY, VALUE)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getIntSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: Int): Int {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getInt(key, defaultValue)
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun getIntSharedPreferences(activity: Activity, storageName: String, key: String, defaultValue: Int): Int {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
return preferences.getInt(key, defaultValue)
}
fun setStringSharedPreferences(activity: Activity, storageName: String, key: String, value: String) {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
val editor = preferences.edit()
editor.putString(key, value)
editor.apply()
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetIntSharedPreferences(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int GetIntSharedPreferences(lua_State* L)
//...
static int SetStringSharedPreferences(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* storageName = (char*)luaL_checkstring(L, 1);
jstring jstorageName = env->NewStringUTF(storageName);
const char* key = (char*)luaL_checkstring(L, 2);
jstring jkey = env->NewStringUTF(key);
const char* defaultValue = (char*)luaL_checkstring(L, 3);
jstring jdefaultValue = env->NewStringUTF(defaultValue);
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "setStringSharedPreferences", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jstorageName, jkey, jdefaultValue);
env->DeleteLocalRef(jstorageName);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jdefaultValue);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
set_int_shared_preferences
Метод позволяет установить целочисленное значение в настройках SharedPreferences.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorecore.set_int_shared_preferences(STORAGE_NAME, KEY, VALUE)
Подробнее см. пример на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldCore/src/main/java/ru.rustore.defold.core/RuStoreCore.kt |
- До
- После
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun setStringSharedPreferences(activity: Activity, storageName: String, key: String, value: String) {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
val editor = preferences.edit()
editor.putString(key, value)
editor.apply()
}
}
package ru.rustore.defold.core
import ru.rustore.defold.core.callbacks.IRuStoreChannelListener
import android.app.Activity
import android.widget.Toast
object RuStoreCore {
//...
//...
fun setStringSharedPreferences(activity: Activity, storageName: String, key: String, value: String) {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
val editor = preferences.edit()
editor.putString(key, value)
editor.apply()
}
fun setIntSharedPreferences(activity: Activity, storageName: String, key: String, value: Int) {
val application = activity.application
val preferences: SharedPreferences = application.getSharedPreferences(storageName, Context.MODE_PRIVATE)
val editor = preferences.edit()
editor.putInt(key, value)
editor.apply()
}
}
Подробнее см. пример реализации метода в проекте Android Studio на GitFlic.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_core/src/rustorecore.cpp |
- До
- После
//...
static int ShowToast(lua_State* L)
//...
//...
static int SetStringSharedPreferences(lua_State* L)
//...
//...
static int ShowToast(lua_State* L)
//...
//...
static int SetStringSharedPreferences(lua_State* L)
//...
static int SetIntSharedPreferences(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
const char* storageName = (char*)luaL_checkstring(L, 1);
jstring jstorageName = env->NewStringUTF(storageName);
const char* key = (char*)luaL_checkstring(L, 2);
jstring jkey = env->NewStringUTF(key);
jint jvalue = static_cast<jint>(luaL_checkint(L, 3));
AndroidJavaObject instance;
GatJavaCoreInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "setIntSharedPreferences", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;I)V");
env->CallVoidMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jstorageName, jkey, jvalue);
env->DeleteLocalRef(jstorageName);
env->DeleteLocalRef(jkey);
thread.Detach();
return 0;
}
//...
Подробнее см. пример реализации метода в проекте Defold на GitFlic.
RuStoreBilling
Большинство методов расширения RuStoreBilling реализуют обертку над соответствующим методом SDK rustorebillingclient. Все методы плагина, доступные для вызовов из Lua, определены в массиве Module_methods (см. ниже).
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_billing/src/rustorebilling.cpp |
static const luaL_reg Module_methods[] =
{
{"init", Init},
{"check_purchases_availability", CheckPurchasesAvailability},
{"get_products", GetProducts},
{"purchase_product", PurchaseProduct},
{"get_purchases", GetPurchases},
{"confirm_purchase", ConfirmPurchase},
{"delete_purchase", DeletePurchase},
{"get_purchase_info", GetPurchaseInfo},
{"set_theme", SetTheme},
{"set_error_handling", SetErrorHandling},
{0, 0}
};
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Все методы расширения работают с Java-объектом типа ru.rustore.defold.billing.RuStoreBilling. Поэтому стоит вынести работу по получению ссылки на объект в отдельный метод. Для этого открываем в редакторе Defold файл rustorebilling.cpp и в секции #if defined(DM_PLATFORM_ANDROID) добавляем следующий код.
static void GetJavaObjectInstance(JNIEnv* env, AndroidJavaObject* instance)
{
jclass cls = dmAndroid::LoadClass(env, "ru.rustore.defold.billing.RuStoreBilling");
jfieldID instanceField = env->GetStaticFieldID(cls, "INSTANCE", "Lru/rustore/defold/billing/RuStoreBilling;");
jobject obj = env->GetStaticObjectField(cls, instanceField);
instance->cls = cls;
instance->obj = obj;
}
Внутри метода выполняются следующие шаги.
- Получение класса
ru.rustore.defold.billing.RuStoreBillingс помощью функцииdmAndroid::LoadClass. - Получение идентификатора статического поля
INSTANCEс помощьюenv->GetStaticFieldID. - Получение объекта из статического поля INSTANCE с помощью
env->GetStaticObjectField. - Сохранение класса и объекта в переданной через указатель переменной
instance.
init
Метод init предназначен для инициализации расширения и подготовки его к работе. Этот метод должен быть вызван прежде всех других методов расширения. Соответствует методу RuStoreBillingClientFactory.create нативного SDK.
Создадим обертку метода в проекте Android. Открываем в Android Studio ранее подготовленный проект extension_libraries и создадим файл RuStoreBilling.kt по пути RuStoreDefoldBilling/src/main/java/ru.rustore.defold.billing.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldBilling/src/main/java/ru.rustore.defold.billing/RustoreBilling.kt |
private var client: RuStoreBillingClient? = null
private var isInitialized = false
fun init(activity: Activity, id: String, scheme: String, debugLogs: Boolean) {
if (isInitialized) return
client = RuStoreBillingClientFactory.create(
context = activity.application,
consoleApplicationId = id,
deeplinkScheme = scheme,
internalConfig = mapOf(
"type" to "defold"
),
themeProvider = RuStoreBillingClientThemeProviderImpl,
debugLogs = debugLogs,
externalPaymentLoggerFactory = if (debugLogs) { tag -> this.tag = tag; this } else null
)
isInitialized = true
}
Подробнее см. пример на GitFlic.
Далее в проекте Defold нам необходимо реализовать JNI-вызов созданного метода. Для этого открываем в редакторе Defold файл rustorebilling.cpp и в секции #if defined(DM_PLATFORM_ANDROID) добавляем следующий код.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_billing/src/rustorebilling.cpp |
static int Init(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
AndroidJavaObject instance;
GetJavaObjectInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "init", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;Z)V");
const char* id = (char*)luaL_checkstring(L, 1);
const char* scheme = (char*)luaL_checkstring(L, 2);
jstring jid = env->NewStringUTF(id);
jstring jscheme = env->NewStringUTF(scheme);
jboolean jdebugLogs = false;
int n = lua_gettop(L);
if (n > 2) jdebugLogs = (jboolean)lua_toboolean(L, 3);
env->CallVoidMethod(instance.obj, method, dmGraphics::GetNativeAndroidActivity(), jid, jscheme, jdebugLogs);
env->DeleteLocalRef(jid);
env->DeleteLocalRef(jscheme);
thread.Detach();
return 0;
}
Подробнее см. пример на GitFlic.
check_purchases_availability
Метод используется для проверки доступности покупок или подписок в магазине приложений. Соответствует методу checkPurchasesAvailability нативного SDK.
Здесь мы уже будем активно использовать ранее созданную систему обратных вызовов. Открываем в Android Studio файл RuStoreBilling.kt и вставляем следующий код в объект RuStoreBilling.
| Проект | Путь к файлу |
|---|---|
| Android Studio | RuStoreDefoldBilling/src/main/java/ru.rustore.defold.billing/RustoreBilling.kt |
const val CHANNEL_CHECK_PURCHASES_AVAILABLE_SUCCESS = "rustore_check_purchases_available_success"
const val CHANNEL_CHECK_PURCHASES_AVAILABLE_FAILURE = "rustore_check_purchases_available_failure"
fun checkPurchasesAvailability() {
client?.run {
purchases.checkPurchasesAvailability()
.addOnSuccessListener { result ->
when (result) {
is FeatureAvailabilityResult.Available -> {
RuStoreCore.emitSignal(
CHANNEL_CHECK_PURCHASES_AVAILABLE_SUCCESS,
"""{"isAvailable": true}"""
)
}
is FeatureAvailabilityResult.Unavailable -> {
val cause = JsonBuilder.toJson(result.cause)
val json = """{"isAvailable": false, "cause": $cause}"""
handleError(result.cause)
RuStoreCore.emitSignal(CHANNEL_CHECK_PURCHASES_AVAILABLE_SUCCESS, json)
}
}
}
.addOnFailureListener { throwable ->
handleError(throwable)
RuStoreCore.emitSignal(CHANNEL_CHECK_PURCHASES_AVAILABLE_FAILURE, JsonBuilder.toJson(throwable))
}
}
}
Подробнее см. пример на GitFlic.
CHANNEL_CHECK_PURCHASES_AVAILABLE_SUCCESSиCHANNEL_CHECK_PURCHASES_AVAILABLE_FAILUREзадают имена каналов данных.- Передача данных выполняется методом
RuStoreCore.emitSignal. - Информацию о результате проверки метод
checkPurchasesAvailabilityвозвращает в событиях задаваемых черезaddOnSuccessListenerиaddOnFailureListener. - В случае соответствия условиям работы платежей возвращается
FeatureAvailabilityResult.AvailableиFeatureAvailabilityResult.Unavailableв случае невыполнения условий. - Для упрощения обработки ответов о состоянии проверки условий работы платежей на стороне Lua вместо передачи
FeatureAvailabilityResultпередадим JSON-строки"{"isAvailable": true}"и"{"isAvailable": false, "cause": $cause}"вместоFeatureAvailabilityResult.AvailableиFeatureAvailabilityResult.Unavailableсоответственно.
В проекте Defold нам также необходимо реализовать JNI-вызов метода. Открываем файл rustorebilling cpp в редакторе Defold и в секции #if defined(DM_PLATFORM_ANDROID) добавляем следующий код.
| Проект | Путь к файлу |
|---|---|
| Defold | extension_rustore_billing/src/rustorebilling.cpp |
static int CheckPurchasesAvailability(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
dmAndroid::ThreadAttacher thread;
JNIEnv* env = thread.GetEnv();
AndroidJavaObject instance;
GetJavaObjectInstance(env, &instance);
jmethodID method = env->GetMethodID(instance.cls, "checkPurchasesAvailability", "()V");
env->CallVoidMethod(instance.obj, method);
thread.Detach();
return 0;
}
Подробнее см. пример на GitFlic.
Метод выполняет следующие шаги.
- Через макрос
DM_LUA_STACK_CHECKзадает количество ожидаемых элементов в стеке Lua после выполнения функции.0– нет возвращаемых параметров. - Получает JNI-интерфейс с помощью
dmAndroid::ThreadAttacherиthread.GetEnv. - Через метод
GetJavaObjectInstanceполучает ссылки на Java-объект. - Получает идентификатор метода
checkPurchasesAvailabilityс помощьюenv->GetMethodID. - Вызывает метод
checkPurchasesAvailabilityна Java-объекте без передачи параметровenv->CallVoidMethod. - Освобождает JNI-ресурсы
dmAndroid::ThreadAttacher.
Вызов метода в Lua должен осуществляться с передачей трех следующих параметров.
CONSOLE_APPLICATION_ID— код приложения из консоли разработчика RuStore (пример:https://console.rustore.ru/apps/123456,consoleApplicationId = 123456). Подробная информация о публикации приложений в RuStore доступна на странице help.DEEPLINK_SCHEME— схема deeplink, необходимая для возврата в ваше приложение после оплаты через стороннее приложение (например, SberPay или СБП).DEBUG_LOGS— флаг, регулирующий ведение журнала событий. Укажите значениеtrue, если хотите, чтобы события попадали в журнал. В ином случае укажитеfalse.
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_check_purchases_available_success;rustore_check_purchases_available_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_check_purchases_available_success", _check_purchases_available_success)
rustorecore.connect("rustore_check_purchases_available_failure", _check_purchases_available_failure)
end
function _check_purchases_availability_success(self, channel, value)
local data = json.decode(value)
end
function _check_purchases_availability_failure(self, channel, value)
local data = json.decode(value)
end
rustorebilling.check_purchases_availability()
Подробнее см. пример на GitFlic.
get_products
Метод предназначен для получения списка продуктов и подписок доступных для покупки в вашем приложении. Соответствует методу getProducts нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_on_get_products_success;rustore_on_get_products_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_get_products_success", _on_get_products_success)
rustorecore.connect("rustore_on_get_products_failure", _on_get_products_failure)
end
function _on_get_products_success(self, channel, value)
local data = json.decode(value)
end
func _on_get_products_failure(self, channel, value)
local data = json.decode(value)
end
local PRODUCT_IDS = {
"non_con2",
"non_con1",
"con2",
"con1",
"sub2",
"sub1"}
rustorebilling.get_products(PRODUCT_IDS)
Подробнее см. пример на GitFlic.
purchase_product
Метод позволяет осуществить покупку конкретного продукта или подписки. Соответствует методу purchaseProduct нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_on_purchase_product_success;rustore_on_purchase_product_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_purchase_product_success", _on_purchase_product_success)
rustorecore.connect("rustore_on_purchase_product_failure", _on_purchase_product_failure)
end
function _on_purchase_product_success(self, channel, value)
local data = json.decode(value)
end
function _on_purchase_product_failure(self, channel, value)
local data = json.decode(value)
end
local PRODUCT_ID = "example_id"
local PARAMS = "{" ..
"\"orderId\":\"example\"," ..
"\"quantity\":1," ..
"\"payload\":\"example\"" ..
"}"
rustorebilling.purchase_product(PRODUCT_ID, PARAMS)
Подробнее см. пример на GitFlic.
get_purchases
Метод используется для получения списка совершенных покупок или подписок. Соответствует методу getPurchases нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода необходимо единожды выполнить подписку на события:
rustore_on_get_purchases_success;rustore_on_get_purchases_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_get_purchases_success", _on_get_purchases_success)
rustorecore.connect("rustore_on_get_purchases_failure", _on_get_purchases_failure)
end
function _on_get_purchases_success(self, channel, value)
local data = json.decode(value)
for key, value in pairs(data) do
-- value.amount
end
end
function _on_get_purchases_failure(self, channel, value)
local data = json.decode(value)
end
rustorebilling.get_purchases()
Подробнее см. пример на GitFlic.
confirm_purchase
Метод служит для подтверждения совершенной покупки. Соответствует методу confirmPurchase нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_on_confirm_purchase_success;rustore_on_confirm_purchase_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_confirm_purchase_success", _on_confirm_purchase_success)
rustorecore.connect("rustore_on_confirm_purchase_failure", _on_confirm_purchase_failure)
end
function _on_confirm_purchase_success(self, channel, value)
local data = json.decode(value)
end
function _on_confirm_purchase_failure(self, channel, value)
local data = json.decode(value)
end
Подробнее см. пример на GitFlic.
-- Ваша реализация UI подтверждения покупки
function _on_confirm_purchase_pressed(purchaseId):
rustorebilling.confirm_purchase(purchaseId)
end
delete_purchase
Метод позволяет удалить информацию о конкретной покупке. Соответствует методу deletePurchase нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_on_delete_purchase_success;rustore_on_delete_purchase_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_delete_purchase_success", _on_delete_purchase_success)
rustorecore.connect("rustore_on_delete_purchase_failure", _on_delete_purchase_failure)
end
function _on_delete_purchase_success(self, channel, value)
local data = json.decode(value)
end
function _on_delete_purchase_failure(self, channel, value)
local data = json.decode(value)
end
-- Ваша реализация UI отмены покупки
function _on_delete_purchase_pressed(purchaseId):
rustorebilling.delete_purchase(purchaseId)
end
Подробнее см. пример на GitFlic.
get_purchase_info
Метод предназначен для получения дополнительной информации о конкретной покупке. Соответствует методу getPurchaseInfo нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
Перед использованием метода в Lua необходимо единожды выполнить подписку на события:
rustore_on_get_purchase_info_success;rustore_on_get_purchase_info_failure.
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorecore.connect("rustore_on_get_purchase_info_success", _on_get_purchase_info_success)
rustorecore.connect("rustore_on_get_purchase_info_failure", _on_get_purchase_info_failure)
end
function _on_get_purchase_info_success(self, channel, value)
local data = json.decode(value)
end
function _on_get_purchase_info_failure(self, channel, value)
local data = json.decode(value)
end
-- Ваша реализация UI запроса информации о покупке
function _on_get_purchase_info_pressed(purchaseId):
rustorebilling.get_purchase_info(purchaseId)
end
Подробнее см. пример на GitFlic.
set_theme
Метод используется для установки темы (стиля) интерфейса расширения. Является адаптацией механизма динамической смены темы через интерфейс провайдера BillingClientThemeProvider нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
rustorebilling.set_theme(0)
Подробнее см. пример на GitFlic.
set_error_handling
Метод позволяет настроить обработку ошибок в расширении. Является адаптацией использования метода resolveForBilling у объектов RuStoreException нативного SDK.
Примеры реализации всех методов доступны в официальном репозитории проекта на GitFlic:
| Проект | Путь к файлу |
|---|---|
| Defold | main/main.script |
function init(self)
-- Инициализация rustorebilling
rustorebilling.set_error_handling(true)
end
Подробнее см. пример на GitFlic.
GUI приложения
Далее рассмотрим создание простого пользовательского интерфейса (GUI), в котором будет реализовано использование всех методов SDK RuStore Billing:
- верхнее горизонтальное меню;
- слайдер с информацией о продуктах/покупках;
- меню операций с продуктами;
- меню операций с покупками.
Подготовка
- В папке
mainпроекта Defold создайте субдиректориюgui.к сведениюВ ней будут размещены все файлы
*.gui,*.gui_script,*.lua, шрифты и спрайты пользовательского интерфейса. - В папке
main/gui/создайте файлы.guiдля каждой из четырех групп компонентов интерфейса (см. таблицу ниже)Файл Группа компонентов интерфейса main/gui/top_menu.guiВерхнее горизонтальное меню. main/gui/slider.guiСлайдер с информацией о продуктах/покупках. main/gui/product_actions.guiМеню операций с продуктами main/gui/purchase_actions.guiМеню операций с покупками. - Поместите исходный файл шрифта в формате
.ttfв папкуmain/gui/fonts.к сведениюВ настоящем руководстве для примера используется бесплатный шрифт
Cygre.ttf.
Теперь нужно создат ь шрифт в формате .font, с которым работает Defold.
-
В панели управления Deflod выберите File > New.
Отобразится следующее окно.
Окно New -
Выберит пункт Font и нажмите OK.
Отобразится следующее окно.
Окно New Font -
Выполните настройки, руководствуясь таблицей ниже.
Настройка Описание Name Укажите имя шрифта. В настоящей инструкции для примера будет использоваться значение text48. Location Укажите папку, где будет располагаться созданный файла, в настоящем примере: main/gui/fonts.Preview Поле заполнится автоматически, в нашем примере должно быть значение main/gui/fonts/text48.font.Окно New Font будет выглядеть следующим образом.

Окно New Font -
Нажмите Create Font.
Созданный шрифт отобразится в меню проекта Defold (см. изображение ниже).
Новый шрифт создан -
Двойным щелчком мыши в интерфейсе Defold выберите созданный файл text48.font и в правой части окна в секции Properties задайте размер шрифта, установив настройку Size в значение 48 см. изображение ниже).
Изменение размера шрифта -
В проекте Defold создайте папку
main/gui/spritesи поместите туда файлbutton_orange_190_80.png— это изображение в формате PNG 190 на 80 пикселей, которая будет использоваться в качестве фона кнопки.
Чтобы Defold мог использовать текстуру кнопки, необходимо создать текстурный атлас и разместить на нем все необходимые текстуры.
-
Щелкните правой кнопкой мыши на созданной папке sprites и выберите New... > Atlas (см. изображение ниже).

Создание атласа -
В отобразившемся окне задайте имя атласа (в настоящем руководстве испол ьзуется имя sprites) — окно будет выглядеть следующим образом.

Окно New Atlas -
Нажмите Create Atlas.
-
В навигационном меню проекта Defold выберите созданный sprites.atlas (см. изображение ниже).
Окно New Atlas -
В правой части интерфейса в секции щелкните правой кнопкой мыши и выберите Add Images... (см. изображение ниже).

Добавление изображенияОтобразится следующее окно.

Выбор изображения -
Выберите добавленный ранее файл button_orange_190_80.png и нажмите OK.
Defold автоматически создаст холст нужного размера и разместит на нем все добавленные изображения (см. изображение ниже).

Атлас текстуры кнопкиВся необходимая подготовка к созданию UI выполнена, можем переходить к созданию «менюшек» приложения.
Создание компонентов интерфейса
Верхнее горизонтальное меню
В настоящем руководстве создание компонентов интерфейса подробно рассматривается на примере создания верхнего горизонтального меню. Ознакомьтесь с примером и создайте остальные компоненты по аналогии.
Отображение
Верхнее меню будет включать следующие кнопки.
| Кнопка | Описание |
|---|---|
| Проверка доступности платежей | Вызов метода check_purchases_availability. |
| Запрос списка продуктов | Вызов метода get_products. |
| Запрос списка покупок | Вызов метода get_purchases. |
Каждому из наших .gui нужно указать, какие шрифты, спрайты и скрипты он может использовать. Для этого выполните следующие действия.
-
Откройте для редактирования ранее созданный файл
top_menu.gui. -
В древе панели Outline (справа) в папку Fonts добавьте шрифт text48.font (см. изображения ниже).

Add > Fonts
Окно Select Fonts -
В папку Textures добавьте атлас с кнопкой sprites.atlas (см. изображения ниже).

Add > Textures
Окно Select TexturesПанель Outline будет иметь следующий вид.

Панель Outlineк сведениюТеперь мы можем создавать объекты интерфейса, который в Defold называются нодами, и использовать добавленные ресурсы шрифтов и текстур для их декорирования.
Defold имеет несколько типов нод. Сейчас нас интересуют только два типа (см. таблицу ниже).
Тип ноды Описание Box Прямоугольная нода с одним цветом, текстурой или мультикадровой анимацией. Text Нода отображающая текст. -
В папке Nodes создайте три ноды типа Box (см. изображение ниже).

Добавление ноды Box -
Созданным нодам в панели Properties присвойте Id:
availability– кнопка проверки доступности платежей;products– кнопка получения списка продуктов;purchases– кнопка получения списка покупок.
к сведениюId – идентификатор объекта, Id должен быть уникальным для каждого объекта внутри заданного
.gui.См. из ображение ниже.

Id объекта -
В свойствах каждого Box (панель Properties) в поле Texture укажите текстуру button_orange_190_80. Текстуры будут автоматически подтянутся из всех атласов, добавленных в папку Textures.
Выбор текстуры объекта -
Назовите каждую кнопку согласно ее предназначению. Для этого внутри каждого объекта Box создайте объекты Text и заполните следующие поля.
Поле Описание Id Идентификатор объекта, должен быть уникальным для всех объектов внутри одного Gui. Text Отображаемый текст: - Availability для ноды availability;
- Products для ноды products;
- Purchases для ноды purchases.
Font Используемый шрифт, в настоящем примере: text48. См. изображения ниже.

Выбор текстуры объекта
Выбор текстуры объектак сведениюКаждая нода имеет богатый набор свойств для настройки внешнего вида, подробнее о настройке каждого параметра можно найти в документации Defold.
После создания верхнего меню должен получиться следующий результат.

Верхнее горизонтальное меню
Обработчик нажатий
Для обработчиков GUI используются скрипты с расширением .gui_script.
-
В папке
main/guiсоздадим файлtop_menu.gui_script(см. изображения ниже).
Создание нового скрипта
Окно New Gui Script -
В навигационной панели проекта Defold выберите main > gui > top_menu.gui (см. изображение ниже).

Окно New Gui Script -
В панели Outline справа ноду верхнего уровня Gui.
-
В панели Properties ниже в поле Script укажите файл скрипта top_menu.gui_script (см. изображение ниже).
Выбор скрипта обработки для верхнего меню -
Сохраните сделанные изменения и переходите к редактированию файла скрипта обработчика платежей main > gui > top_menu.gui.script.
Defold уже создал структуру файла
.gui_script, со стандартным набором функций:init;final;update;on_message;on_input;on_reload.
Из них нам понадобятся только следующие:
init;on_message;on_input.
-
Оставьте только необходимые функции и удалите комментарии, чтобы не загромождать код (см. пример До и После ниже.)
- До
- После
Lua | top_menu.gui_scriptfunction init(self)
-- Add initialization code here
-- Learn more: https://defold.com/manuals/script/
-- Remove this function if not needed
end
function final(self)
-- Add finalization code here
-- Learn more: https://defold.com/manuals/script/
-- Remove this function if not needed
end
function update(self, dt)
-- Add update code here
-- Learn more: https://defold.com/manuals/script/
-- Remove this function if not needed
end
function on_message(self, message_id, message, sender)
-- Add message-handling code here
-- Learn more: https://defold.com/manuals/message-passing/
-- Remove this function if not needed
end
function on_input(self, action_id, action)
-- Add input-handling code here. The game object this script is attached to
-- must have acquired input focus:
--
-- msg.post(".", "acquire_input_focus")
--
-- All mapped input bindings will be received. Mouse and touch input will
-- be received regardless of where on the screen it happened.
-- Learn more: https://defold.com/manuals/input/
-- Remove this function if not needed
end
function on_reload(self)
-- Add reload-handling code here
-- Learn more: https://defold.com/manuals/hot-reload/
-- Remove this function if not needed
endLua | top_menu.gui_scriptfunction init(self)
end
function on_message(self, message_id, message, sender)
end
function on_input(self, action_id, action)
end -
В методе
initподключите возможность получения фокуса и прослушивания пользовательского ввода для.guiиспользующей данный скрипт.- До
- После
Lua | top_menu.gui_scriptfunction init(self)
endLua | top_menu.gui_scriptfunction init(self)
msg.post(".", "acquire_input_focus")
end -
В методе
on_inputреализуйте проверку нажатия кнопки. Для того чтобы меню могло обращаться к логике других скриптов, вы можете передать сообщение через системуmsg.post.- До
- После
Lua | top_menu.gui_scriptfunction on_input(self, action_id, action)
endLua | top_menu.gui_scriptfunction on_input(self, action_id, action)
local button_name = "availability"
local button = gui.get_node(button_name)
if gui.pick_node(button, action.x, action.y) then
msg.post("go#main", "top_menu_button_pressed", { button = button_name })
end
end -
Чтобы выполнить обраб отку нажатия кнопки в скрипте
main.script, воспользуйтесь событиемon_message.- До
- После
Lua | top_menu.gui_scriptfunction on_message(self, message_id, message, sender)
endLua | top_menu.gui_scriptfunction on_message(self, message_id, message, sender)
if message_id == hash("top_menu_button_pressed") then
if message.button == "availability" then
rustorebilling.show_toast("Hello JNI")
end
end
end -
Для удобства использования и сопровождения кода вынесите Id нод и сообщений в файл
top_menu_consts.lua— см. изображения и пример кода ниже.
Создание нового Lua-модуля