Использование Android Studio для сборки приложений с NDK и Boost
29.01.2015 11:40

В предыдущей статье мы рассмотрели как собирать простые исполняемые файлы под Android с использованием библиотек Boost. Это хороший пример для понимания того, как все работает "изнутри"; однако для практических целей хорошо было бы уметь собирать готовые к использованию приложения, которые можно залить в магазин приложений Google Play, к примеру.

Официально предлагаемый способ для создания таких приложений - использование Android Studio. К сожалению, Android Studio не поддерживает сборку C/C++ кода так же хорошо, как Java кода. Поддержка NDK в ней на данный момент очень ограничена. Так, единственно поддерживаемыми NDK приложениями являются только те, которые состоят из одного собираемого модуля (финальной динамической библиотеки), держат все исходные коды на C/C++ в каталоге 'jni', в которых также отсутствуют любые зависимости от других библиотек, и которые нельзя разбить на несколько модулей (т.е. набор статических и динамических библиотек). Не предоставляется никаких возможностей для настройки сборки нативных модулей, за исключением очень ограниченного набора опций:

build.gradle
defaultConfig { ... ndk { moduleName "my-module-name" cFlags "-std=c++11 -fexceptions" ldLibs "log" stl "gnustl_shared" abiFilter "armeabi-v7a" } } }

Для сборки нативных модулей доступны только опции moduleName, cFlags, ldLibs, stl и abiFilter; мы не можем указать дополнительные зависимости (такие как библиотеки Boost). Мы не можем указать пути к библиотекам, чтобы линкер знал, где их искать. Список можно продолжить - недоступно очень много настроек.

Это происходит оттого, что gradle plug-in (используемый Android Studio для сборки проектов) игнорирует существующие файлы Application.mk и Android.mk из каталога 'jni'. Вместо этого он генерирует собственный Android.mk на лету, используя настройки из сборочного скрипта.

С практической точки зрения единственный рабочий способ собирать такие приложения в Android Studio - это полностью отключить ее ограниченную поддержку NDK и вызывать $NDK/ndk-build самостоятельно. В этой статье мы опишем шаг за шагом, как это сделать.

Мы создадим с нуля простое приложение в Android Studio, и затем добавим к нему части, написанные на C++. Мы предполагаем, что вы уже установили Android Studio и Android SDK; мы также предполагаем, что вы скачали и распаковали CrystaX NDK

Java

Первым делом, запустите Android Studio и создайте новый Android проект:

Выберите "Android 4.0.3" в качестве целевой версии Android:

Выберите "пустую" activity:

Оставьте все имена как есть и нажмите кнопку "Finish":

Layout

Теперь откройте файл app/res/layout/activity_main.xml и поправьте его, чтоб он выглядел так:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>

MainActivity

Теперь добавьте такие строки в MainActivity.onCreate():

TextView field = (TextView)findViewById(R.id.text); field.setText(getGPSCoordinates(getFilesDir().getAbsolutePath()));

Добавьте объявление нативного метода в класс MainActivity:

private native String getGPSCoordinates(String rootPath);

Также, не забудьте добавить загрузку динамической библиотеки в статический блок инициализации:

static { System.loadLibrary("test-boost"); }

В результате содержимое файла MainActivity.java должно стать таким:

MainActivity.java

Мы закончили заниматься Java-частью приложения; давайте теперь займемся кодом на C++.

C++

Первым делом, создайте каталог, в котором будут лежать исходные файлы на C++:

Используйте 'main' в следующем окне и нажмите кнопку "Finish":

Исходники

Затем добавьте следующие файлы в только что созданный каталог (app/src/main/jni):

Application.mk
Android.mk
gps.hpp
gps.cpp
test.cpp

Сборочный скрипт

Теперь нам надо модифицировать сборочный скрипт, чтобы он правильно собирал наш C++ код вместе с Java. Для этого нам сперва надо открыть файл local.properties и добавить туда путь к CrystaX NDK:

sdk.dir=/opt/android/android-sdk-mac ndk.dir=/opt/android/crystax-ndk-10.1.0

Пользователям Windows: обратные слэши и двоеточия должны быть экранированы:

sdk.dir=C\:\\android\\android-sdk-mac ndk.dir=C\:\\android\\crystax-ndk-10.1.0

И наконец, откроем и отредактируем файл build.gradle:

Его содержимое должно быть следующим:

build.gradle

Для тех, кому интересно, что именно мы добавили в существующий файл, ниже приводится diff:

build.gradle.diff
diff --git a/build.gradle b/build.gradle index a6b8c98..08dce1c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.apache.tools.ant.taskdefs.condition.Os + apply plugin: 'com.android.application' android { @@ -17,9 +19,50 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + sourceSets.main.jni.srcDirs = [] // disable automatic ndk-build call, which ignore our Android.mk + sourceSets.main.jniLibs.srcDir 'src/main/libs' + + // call regular ndk-build(.cmd) script from app directory + task ndkBuild(type: Exec) { + workingDir file('src/main') + commandLine getNdkBuildCmd() + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn ndkBuild + } + + task cleanNative(type: Exec) { + workingDir file('src/main') + commandLine getNdkBuildCmd(), 'clean' + } + + clean.dependsOn cleanNative } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' } + +def getNdkDir() { + if (System.env.ANDROID_NDK_ROOT != null) + return System.env.ANDROID_NDK_ROOT + + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def ndkdir = properties.getProperty('ndk.dir', null) + if (ndkdir == null) + throw new GradleException("NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.") + + return ndkdir +} + +def getNdkBuildCmd() { + def ndkbuild = getNdkDir() + "/ndk-build" + if (Os.isFamily(Os.FAMILY_WINDOWS)) + ndkbuild += ".cmd" + + return ndkbuild +}

Структура каталога

Файловое дерево каталога TestBoost/app должно теперь выглядеть так:

. ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest │ └── java │ └── net │ └── crystax │ └── examples │ └── testboost │ └── ApplicationTest.java └── main ├── AndroidManifest.xml ├── java │ └── net │ └── crystax │ └── examples │ └── testboost │ └── MainActivity.java ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── gps.cpp │ ├── gps.hpp │ └── test.cpp └── res .......

Конечный результат

Дело сделано! Теперь запустите сборку проекта как обычно (Build -> Make Module 'app') и стартуйте приложение на устройстве или эмуляторе. Ниже приводится снимок экрана с устройства при запущенном приложении:

Back
Home
Map
Back
Home
Map

Наши авторы: