В последние дни я стал изучать JNI, даже путем не зная Java.
Для чего?
Я планирую разрабатывать приложения для Android, используя SDL библиотеку на C++ или egui на Rust и вот, в SDL не было нужных мне функций, а именно:
- Запрос на полный доступ к хранилищу с Android 11 (до этой версии Android можно было спокойно дергать простой запрос права на чтение хранилища. Да, Google Play такое не любит, однако пока я не хочу возиться с Storage Access Framework, да и регистрировать сам Google аккаунт в сторе);
- Уведомления;
- Получить путь к основной папке пользователя;
Хоть я еще точно не определился на каком ЯП писать, JNI понимать мне уж точно не помешает.
В рамках данного полотна текста расскажу про то, как сделать простые уведомления для SDL, остальное пока оставлю на будущее. Будем использовать сам SDL, Dear ImGui для простого UI, пару патчей для SDL Android Project и собственно JNI. Данное полотно текста подразумевает, что вы хоть как-то понимаете программирование и делали поделки на C++ с использованием Cmake, я тут не буду писать конфиг сборки для Cmake, если вам лень самому, можете просто использовать готовый пример здесь.
Собственно, что такое JNI сказано на 100 сайтах, я не буду ничего говорить, не рассматривайте это как руководство для JNI, если нужно больше деталей, рассматривайте другие ресурсы, лично я читал на сайте Oracle, а именно здесь. Я расскажу свой кейс и покажу, включая исходный код, как я сделал эти функции. Да, там не идеально. Да, там нужно будет доработать пару моментов, но на мой взгляд, я хорошо постарался для первого раза.
Первое, что я сделал это посмотрел, а как в SDL вообще работает JNI. Наткнулся на вот такой кусок кода здесь.
|
|
Достаточно сообразить и понять, что из себя представляет JNI:
- Есть
JNIEnv
, собственно штука через которую мы будем вызывать функции JNI; - В Java все классы наследуются от класса
java/lang/Object
, здесь он представлен какjclass
(просто класс, не объект) иjobject
(соотв. объект); GetObjectClass
используется, чтобы получитьjclass
из объекта (в данном случае нативное андроид активити SDL), чтобы потом уже получитьjmethodID
(как понимаю ID метода того или иного класса);- Для вызова функций Java из C++ есть функции типа
CallXMethod
. X в данном случае КАК вызывать метод. Он принимаетjclass
иmethodID
, а также аргументы (которые должны быть представлены вjobject
) в виде C++ шаблона с произвольной длиной; - DeleteLocalRef. Дело в том, что в JNI ты управляешь памятью самостоятельно. А значить нужно очищать ссылки объектов JNI;
Так вот. Чтобы правильно вызывать методы, нужно понимать сигнатуры JNI. Рассмотрим вот эту строку:
|
|
()V
- это сигнатура JNI метода. ()
- значит, что метод не принимает аргументов, V
- метод ничего не возвращает (void). clazz в данном случае класс Java (jclass), jobject здесь не нужен.
Если нужно больше информации про JNI, то как я говорил выше, лучше рассматривайте этот сайт.
А мы начнем делать модификации для Java проекта, прежде чем начать писать код для JNI.
Убедитесь, что вы скачали Android JDK, NDK и SDL, а также вытащили из SDL папку android-project
, мы будем работать только в этой папке.
А теперь, приступим:
- Открываем папку
app/src/main/java/<...>/app
; - Открываем файл SDLAcitivity.java;
- Добавим следующие Java импорты:
|
|
- Сделаем собственно наш метод для уведомлений:
|
|
И вроде бы все, да? Мы сделали же код для уведомлений? А нифига. При попытки компиляции у нас будет ошибка вот здесь:
|
|
У нас нет папки drawable
в src/main/res
и самого ic_launcher в ней. Исправляем. Создаем папку drawable в нужном нам пути и копируем ic_launcher.png
из mipmap-mdpi
. Теперь код должен собраться. Однако мы еще не сделали JNI привязки для него!
Но сначала удалим ВСЕ, что находится в папке app/jni
. Теперь нужно сделать четыре файла в ней:
- android_jni.cpp;
- android_jni.hpp;
- main.cpp (лучше возьмите здесь);
- CmakeLists.txt (настроите сами или опять же возьмите здесь);
В android_jni.hpp:
|
|
Напомню, что в начале этого полотна текста я дал объяснение основным структурам и функций JNI, которые тут используются.
В android_jni.cpp:
|
|
Добавим еще в AndroidManifest.xml эту строчку для новых версий Android API:
|
|
Судя по тому, что я прочитал, где-то пишут, что нужно запрашивать это разрешение во время использования приложения, в каких-то сценариях не нужно запрашивать, поэтому я сделал в main.cpp кнопку на запрос данного разрешения.
Напомню, main.cpp и CMakeFiles.txt вы можете взять из репозитория, который я уже не раз тут писал. И очень важное предупреждение, SDL загружает ваш код из вашей внешней .so библиотеки main, убедитесь, что вы в Cmake файле делаете сборку не исполняемого файла, а внешней библиотеки.
Прежде чем вы сами будете настраивать сборку, я сделаю предупреждение. Так как я не знаком с системой сборки Make и ndk, то я ее полностью выпилил из gradle проекта. Не думаю, что кто-то будет расстроен:).
А теперь если вы настроили Cmake сборку, то можете выполнить:
|
|
Эта команда создаст вам два .apk в папке app/build/outputs/apk
. Release у меня не устанавливался вообще, поэтому пробуйте сначала Debug.
Или же вы можете подключить Android устройство с режимом отладки и прописать:
|
|
Это скомпилирует и установит приложение Game с иконкой SDL.
Лень было картинки оптимизировать, извините заранее! Если вы сделали все правильно и приложение у вас запустилось, у вас должно быть что-то такое:
Собственно все. Если нужен готовый код, то опять повторюсь, забирайте его тут. Можете также загрузить уже готовый .apk под архитектуры armeabi-v7a и arm64-v8a здесь. Вирусов нет ;D.