Разработка нативных Android‑модулей (JNI) традиционно ассоциируется с Android Studio и полноценным Android NDK. Однако на практике часть цикла разработки — от сборки и валидации до быстрой отладки — можно выполнять непосредственно в Termux. Это особенно удобно, когда нужно быстро собрать библиотеку, проверить зависимость, воспроизвести проблему и подготовить артефакты для дальнейшей интеграции в Android‑проект.
Ниже — практический и ориентированный на инженера подход: как организовать toolchain в Termux, как собрать JNI‑библиотеку под нужные ABI, как встроить результаты в Android‑приложение и как протестировать поведение.
1. Что именно можно делать в Termux при JNI
Termux — это полноценная Linux‑среда, где можно:
- собрать C/C++ код с помощью cross‑toolchain (NDK) под целевую архитектуру;
- собрать .so и проверить артефакты (зависимости, символы, корректность ABI);
- упаковать артефакты для последующей установки в APK/каталог приложения;
- выполнить утилиты анализа (например, проверку бинарников) на стороне Termux;
- подготовить тестовые сценарии, которые вы запускаете уже в Android‑окружении.
Важно понимать ограничение: исполнение собранного JNI‑кода происходит в составе приложения в Android‑процессе. Termux не заменяет Dalvik/ART. Но сборка и подготовка к запуску — вполне реальны.
2. Подготовка окружения Termux
Начните с обновления пакетов и установки базовых инструментов. Для сборки нативных библиотек понадобятся:
- компилятор и утилиты сборки;
- CMake (или другой build‑system);
- обвязка для загрузки NDK (в зависимости от вашего подхода);
- утилиты для работы с архивами и проверки файлов.
Пример базовой подготовки:
pkg update -y
pkg upgrade -y
pkg install -y clang cmake ninja-build git wget unzip tar file
Если для анализа понадобятся дополнительные инструменты, добавляйте их по мере необходимости (например, binutils для readelf и т.п.).
3. NDK в Termux: подходы и рекомендации
Для JNI‑сборки нужен Android NDK: это источник toolchain и sysroot для целевых ABI.
В Termux обычно используют один из двух подходов:
- Стационарная загрузка NDK в файловую систему Termux (скачать архив NDK, распаковать и указать путь);
- Интеграция с имеющимся NDK, если вы уже располагаете NDK в системе/хранилище.
Поскольку состав пакетов и пути зависят от вашей среды, ниже показан общий шаблон: предполагается, что вы распаковали NDK в некоторую директорию.
# Пример: указываем путь к NDK (замените на свой)
export ANDROID_NDK_ROOT=$HOME/ndk
export PATH=$PATH:$ANDROID_NDK_ROOT
Дальше вы будете обращаться к toolchain напрямую через CMake toolchain file, который предоставляет NDK.
Если вы хотите, чтобы сборка была воспроизводимой, зафиксируйте версию NDK и целевую версию API (android-ndk rxx + android API level).
4. Архитектура JNI-проекта: минимальный каркас
Схема, удобная для Termux:
- native/ — C/C++ код и CMake;
- java/ (или ваш Android проект) — Java/Kotlin код с объявлением native methods;
- скрипт сборки/тестов — чтобы быстро собрать нужную .so под ABI.
Минимальный C/C++ пример (JNI function):
// native/jni_example.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_native_NativeBridge_stringFromJNI(JNIEnv env, jobject /thiz/) {
std::string s = "Hello from JNI";
return env->NewStringUTF(s.c_str());
}
Пример CMakeLists.txt для сборки shared library под Android:
# native/CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(native_example LANGUAGES C CXX)
add_library(native_example SHARED jni_example.cpp)
# Опционально: параметры компиляции
set_target_properties(native_example PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
)
На этом этапе создайте директорию сборки отдельно (out-of-tree build) — так проще переключать ABI/конфигурации.
5. Сборка .so в Termux через CMake + NDK
В NDK используется toolchain file, чтобы CMake понимал, как собрать под Android. Типовой вызов выглядит так:
# Пример параметров (замените значения при необходимости)
export ANDROID_API=24
export ABI=arm64-v8a
# Директория проекта
export PROJECT_DIR=$PWD
# Директория под сборку
rm -rf build_$ABI
mkdir -p build_$ABI
cmake -S $PROJECT_DIR/native -B build_$ABI \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=$ABI \
-DANDROID_PLATFORM=android-$ANDROID_API
Далее сборка:
cmake --build build_$ABI --target native_example
Обычно результат окажется в структуре наподобие:
# Пример путей (зависят от настроек CMake/NDK)
# build_$ABI/<...>/libnative_example.so
find build_$ABI -name 'libnative_example.so' -print
Рекомендуется сделать скрипт, который собирает сразу несколько ABI (arm64-v8a, armeabi-v7a, x86_64) — в зависимости от требований вашего приложения.
6. Проверка артефактов и совместимости ABI
Чтобы не тратить время на бесконечные попытки загрузки неподходящей .so в приложение, сразу проверьте:
- архитектуру (ELF e_machine);
- наличие динамических зависимостей;
- ссылки на библиотеки (если вы используете сторонние .so).
Пример быстрой диагностики:
SO_PATH="$(find build_$ABI -name 'libnative_example.so' -print -quit)"
file "$SO_PATH"
Если установлены утилиты из binutils, полезны:
# (при наличии) - просмотр заголовков ELF
readelf -h "$SO_PATH"
readelf -d "$SO_PATH"
Если на этапе запуска приложение падает с ошибкой загрузки (например, dlopen failed), чаще всего причина в несовпадении ABI, неверном пути в APK или отсутствующих зависимостях.
7. Интеграция .so в Android‑приложение
Для того чтобы Java/Kotlin JNI мог вызвать вашу нативную функцию, библиотека должна лежать в каталоге с корректным ABI, например:
app/src/main/jniLibs/arm64-v8a/libnative_example.soapp/src/main/jniLibs/armeabi-v7a/libnative_example.soapp/src/main/jniLibs/x86_64/libnative_example.so
В Termux вы можете подготовить копирование артефактов в структуру вашего Android проекта (или в промежуточную директорию для упаковки).
Пример копирования из build‑папки в jniLibs:
ANDROID_PROJECT_DIR="$HOME/android_project"
ABI=arm64-v8a
SO_PATH="$(find build_$ABI -name 'libnative_example.so' -print -quit)"
mkdir -p "$ANDROID_PROJECT_DIR/app/src/main/jniLibs/$ABI"
cp "$SO_PATH" "$ANDROID_PROJECT_DIR/app/src/main/jniLibs/$ABI/"
В Java/Kotlin часть обычно выглядит так (фрагмент):
// Kotlin/Java: пример декларации
class NativeBridge {
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("native_example")
}
}
}
Обратите внимание: имя библиотеки в System.loadLibrary() должно соответствовать имени add_library(... SHARED ...) (без префикса lib и без суффикса .so).
8. Настройка отладки: логирование JNI и проверка трассировок
Полноценная отладка (breakpoints/step) нередко требует IDE. Но базовую диагностику можно вести уже на стороне JNI:
- используйте Android Log через
<android/log.h>; - пишите информативные сообщения (какой ABI, какая ветка кода);
- валидируйте входные параметры и корректно обрабатывайте исключения JNI.
Пример добавления логов:
// native/jni_example.cpp
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "NativeExample"
#define LOGI(...) android_log_print(ANDROID_LOG_INFO, LOG_TAG, VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_native_NativeBridge_stringFromJNI(JNIEnv env, jobject /thiz/) {
LOGI("stringFromJNI called");
return env->NewStringUTF("Hello from JNI");
}
Для корректной сборки потребуется линковка с android log (обычно это делается через target_link_libraries, зависит от вашей конфигурации CMake). Пример добавления:
# native/CMakeLists.txt
add_library(native_example SHARED jni_example.cpp)
find_library(log-lib log)
target_link_libraries(native_example PRIVATE ${log-lib})
Просмотр логов выполняется на стороне Android‑устройства (обычно через logcat). Если вы используете окружение Termux, то команды для logcat зависят от наличия инструментария Android platform tools. Если вы подключаете устройство по USB, на практике удобно иметь доступ к adb.
9. Тестирование: быстрый цикл «собрал → установил → проверил»
Чтобы тестирование было быстрым, держите под контролем три вещи:
- ABI‑целевое (собрали ли вы ту же архитектуру, что у устройства);
- имя библиотеки и package/class (строгое соответствие сигнатуре JNI);
- путь загрузки (корректное размещение .so в APK).
Практика:
- Соберите одну ABI и убедитесь, что вызов работает.
- Добавьте логирование JNI.
- После подтверждения расширьте набор ABI.
- Только затем подключайте сложные зависимости и оптимизации.
Если тесты зависят от ввода/вывода, рассмотрите вариант написания небольшого JNI‑интерфейса, который легко проверить (например, возврат версии, хэш строки, контроль размеров буфера).
10. Частые ошибки при JNI в контексте сборки в Termux
- UnsatisfiedLinkError: чаще всего mismatch имени метода/класса или несоответствие сигнатуры.
- dlopen failed: неверный ABI или отсутствующие зависимости .so.
- Проблемы с C++ стандартом: не совпадают флаги компиляции или часть кода использует слишком новые фичи без поднятия
CMAKE_CXX_STANDARD. - Неверный target_link_libraries: для android log, c++_shared/статические зависимости.
Когда ошибка воспроизводится только на устройстве, полезно собирать с диагностическими флагами и включать понятные логи на старте JNI (с выводом версии сборки и выбранного ABI).
11. Организация повторяемости сборки и артефактов
Для инженерной поддержки важно, чтобы сборка была воспроизводимой. Рекомендуется:
- фиксировать версию NDK и API;
- хранить артефакты .so по структуре, удобной для интеграции;
- использовать одинаковые параметры CMake между машинами/окружениями;
- делать отдельные директории сборки по ABI.
Скрипт, который помогает удерживать порядок (шаблон):
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
export ANDROID_NDK_ROOT="$HOME/ndk"
export ANDROID_API=24
PROJECT_DIR="$PWD"
for ABI in arm64-v8a armeabi-v7a x86_64; do
rm -rf build_$ABI
mkdir -p build_$ABI
cmake -S $PROJECT_DIR/native -B build_$ABI \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=$ABI \
-DANDROID_PLATFORM=android-$ANDROID_API
cmake --build build_$ABI --target native_example
done
echo "Done. Check libnative_example.so in build_* directories."
Заключение
Разработка JNI‑модулей непосредственно в Termux возможна и практична: Termux даёт удобную среду для сборки нативного кода, подготовки .so под разные ABI и быстрой проверки артефактов. Основной принцип — грамотно подключить NDK через CMake toolchain file, соблюдать соответствие ABI и имён JNI‑методов, а также вести диагностику через логи и проверку ELF‑структуры библиотек.
Если вам нужно ускорить внедрение JNI в Android‑проект, настроить сборку под несколько ABI, или организовать воспроизводимую CI‑подобную схему для нативных модулей, команда РыбинскЛАБ поможет: от архитектуры и toolchain‑настройки до подготовки интеграции и тестового контура.