#  Первая проба Android NDK или лень против костылей :)
vit01 (mira, 1) → All  –  03:05:58 2015-10-19

Решил не так давно написать что-то родное для своего андроида. Но проблема в том, что на Java я программировать не умею, да и не хочу.
Вспомнил, что существует набор компиляторов Android NDK, который, со слов Гугла, позволяет писать приложения на C/С++ или даже на других языках. На самом деле это немного не так, но об этом позже. Обрадовался и решил написать небольшой скрипт на своём любимом Си + скомпилировать некоторые полезности.

Скачал этот NDK (а он в 7zip SFX, фу и весит гига 2 в распакованном виде), попробовал что-то скомпилировать под arm.

Готовый скрипт для тех, кому надо быстро что-нибудь собрать. Можно адаптировать под Makefile для сборки всяких свободных библиотек.
====
#!/bin/bash
NDK_DIR=/mnt/android-ndk-r10e/
CC_PATH=$NDK_DIR/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/bin/ # для GCC 4.9, доступен также 4.8 и clang
CC=$CC_PATH/arm-linux-androideabi-gcc # есть и другие утилиты из списка
SYSROOT=$NDK_DIR/platforms/android-21/arch-arm/ # android-21 - это версия 5.0, есть и более старые

$CC -I $SYSROOT/usr/include/ --sysroot=$SYSROOT ваша_прога.c -o output
====


Собрал lua, свой клиент для ii вместе с libcurl (писал об этом в ii.14), ещё один небольшой скрипт под 200 строк.

Захотелось большего =) Решил сделать apk, чтобы можно было просто тыкнуть пальцем и запускать свои скрипты, а не заходить в терминал.

Тут и начались проблемы. Документация у Гугла по NDK слишком скудная, поэтому разбираться пришлось самому.
GUI андроид-приложения называется Activity. В каталоге samples из NDK нашёл пример native-activity.

Компилируется оно уже по всем правилам и стандартам. Сначала надо зайти в сам каталог проекта, а потом уже запустить $NDK_DIR/ndk-build. Данный скрипт сам найдёт нужные параметры и соберёт библиотеку для всех архитектур.
Для того, чтобы упаковать программу в apk-файл, потребуется Android SDK и apache-ant. SDK в распакованном виде тоже весит несколько гигабайт, к сожалению.

Самой упаковкой apk и прочими рутинными вещами занимается ant. Но просто так он работать не может, ему нужен специальный файл под названием build.xml. И чтобы не мучаться с его ручным составлением, можно прибегнуть к небольшой хитрости с использованием SDK.

====
#!/bin/bash
SDK_DIR=/mnt/android-sdk-linux
TOOLS=$SDK_DIR/tools

$TOOLS/android create project --activity APP_Name --path ./каталог_проекта -n HelloWorld -t android-21 -k vit01.helloworld # в -k должно быть внутреннее системное имя пакета; точка в названии обязательна
====


Вот мы создали обычный java-проект. Теперь копируем файлы build.xml, local.properties и project.properties в каталог с проектом ndk.

Содержимое build.xml у меня оказалось вот такое:

====
<?xml version="1.0" encoding="UTF-8"?>
<project name="HelloWorld" default="help">
<property file="local.properties" />
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>

<loadproperties srcFile="project.properties" />

<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<import file="custom_rules.xml" optional="true" />
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>
====


Теперь идём в нужный каталог, набираем ant debug, ждём сборки пакета и находим apk в bin/
Устанавливаем, запускаем, радуемся.

А, стоп, надо посмотреть исходники (в jni/main.c)! Нет, не радуемся. Приложение почти полностью построено на EGL. Значит оно работает с видеокартой на низком уровне, а activity в хэдерах NDK - это единственная небольшая обёртка для него.

Если посмотреть в заголовочные файлы NDK, то видно, что для него есть только та самая обёртка, поддержка сенсоров, карты памяти, TTS, логов и прочее незначительное. A GUI-контролов нет!

Без Java, увы, здесь не обойтись. И это главный минус NDK. Пробуем пойти длинным путём. Идём в каталог java-проекта того самого хеллоуворлда, создаём там каталог jni, внутрь jni суём наш сишных хеллоуворлд и файл Android.mk с вот таким содержанием:

====
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := modulename
LOCAL_SRC_FILES := helloworld.c

LOCAL_LDLIBS := -llog -landroid
LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)
====


Ещё не помешает Application.mk с подобным:
====
APP_ABI := all
APP_PLATFORM := android-21
====


И что же нам написать на этот раз, чтобы хоть что-то получилось?
Для связывания Java кода и Си используется вещь под названием Java Native Interface, или JNI. Она позволяет пробрасывать сишные функции, чтобы их можно было вызывать из java-кода. И наоборот, но так гораздо сложнее. Проще говоря, это такой костыль.

Для демонстрации его работы напишем в java файл (а находится он в каталоге src/vit01/helloworld/APP_Name.java для моего проекта) вот такой код:

====
package vit01.helloworld;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class APP_Name extends Activity
{
native public String return_helloworld(); // объявляем функцию, находящуюся в сишной библиотеке; ключевое слово native обязательно
String mytext;

static {
System.loadLibrary("modulename"); // загружаем наше скомпилированное
}

TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);

mytext=return_helloworld(); // вызываем сишную функцию

textView.setText(mytext);
setContentView(textView);
}
}
====


В jni/helloworld.c пишем

====
#include <jni.h>

JNIEXPORT jstring JNICALL Java_vit01_helloworld_APP_1Name_return_1helloworld(JNIEnv * env, jobject jObj) {
// много букв, да? И не говорите. Название для этой функции jni ищет по особым правилам, и если в джаве она называется return_helloworld(), то здесь вот такой ужас
// приходится нагромождать в сишном коде кучу костылей и обёрток
char str[]="Hello World 123456!";
return (*env)->NewStringUTF(env, str);
}
====


Потом в каталоге проекта запускаем $NDK_DIR/ndk-build и ant debug, на выходе получаем готовый apk в bin/, который при запуске выдаст наш хеллоуворлд. Кстати, material design из коробки я в нём так и не увидел. Видимо, надо ещё стилевые библиотеки подключать.

Вывод: программирование на Андроид без IDE и джавы довольно сложно, много минусов у NDK, но так или иначе приноровиться можно. Через любимую консоль будет довольно тяжело писать под эту платформу.