This project has been on hold since 2016.
All the data on this site is still available (and will stay available) but not actual anymore.
Использование 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

Наши авторы: