Работа с Android NDK под Windows

от
Android    c++, ndk

Бывают случаи, когда жуть как надо использовать код, написанный на C/C++ в своём приложении под Android. Это может быть что угодно: какая-нибудь библиотека по обработке музыкального формата (тот же FLAC к примеру), игра с использованием OpenGL, проект OpenCV и т.д. Вот я сегодня и расскажу, как же использовать нативный код в своих проектах.

  Вообще, когда дело доходит до использования Си, Windows тут же отходит на задний план. Об этом свидетельствует множество туториалов по Android NDK - почти все они ориентированы на Unix или Mac OS. Но не будем отчаиваться, лично мне удалось успешно "скрестить" существующую библиотеку на Си с Java, и делал я это под Windows. Приступим!
  Сначала качаем Android NDK с этой страницы. Затем распаковываем в папку, чтобы в пути не было пробелов и кириллицы (например D:\Android\android-ndk). Теперь запускаем Eclipse IDE и в меню Window->Preferences->Android->NDK указываем путь к NDK. Готово!
Создаём новый Android проект.

1.png

Структура проекта выглядит так:

  2.png

Теперь щелкаем правой кнопкой мыши на заголовке проекта и выбираем Android Tools -> Add Native Support. Появится окно, в котором можно задать название библиотеки. Пусть будет "ndkexample". Нажимаем Finish. Eclipse автоматически создаст папку jni, в которой и будет вестись работа с кодом на Си.
  * ndkexample.cpp - главный класс библиотеки.
  * Android.mk - Make-файл, который указывает компилятору, что и как собирать.

Давайте определим, что будем делать в нативной библиотеке. Выводить строки в лог, как это делается в большинстве туториалов неинтересно. Поэтому давайте-ка подсчитаем сумму одного миллиона рандомных чисел!
Создадим вспомагательный класс NdkLib в пакете com.annimon.ndkexample, в котором будет определён нативный метод:
  1. package com.annimon.ndkexample;
  2.  
  3. public class NdkLib {
  4.  
  5.     static native String getInfo(int numElements);
  6. }
Теперь на основе этого класса создадим заголовок для C++ класса, используя утилиту javah из JDK. Заходим из командной строки в папку bin\classes нашего проекта и пишем "javah <путь к папке jni> <имя класса с нативными методами с учетом пакета>". В моём случае команда выглядит так: "javah -d C:\Users\aNNiMON\Documents\Eclipse\TestWorkspace\NdkExample\jni com.annimon.ndkexample.NdkLib". Если всё сделали верно, то в папке jni появится файл "com_annimon_ndkexample_NdkLib.h" с таким вот содержимым:
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_annimon_ndkexample_NdkLib */
  4.  
  5. #ifndef _Included_com_annimon_ndkexample_NdkLib
  6. #define _Included_com_annimon_ndkexample_NdkLib
  7. #ifdef __cplusplus
  8. extern "C" {
  9. #endif
  10. /*
  11.  * Class:     com_annimon_ndkexample_NdkLib
  12.  * Method:    getInfo
  13.  * Signature: (I)Ljava/lang/String;
  14.  */
  15. JNIEXPORT jstring JNICALL Java_com_annimon_ndkexample_NdkLib_getInfo
  16.   (JNIEnv *, jclass, jint);
  17.  
  18. #ifdef __cplusplus
  19. }
  20. #endif
  21. #endif

Теперь реализовываем метод Java_com_annimon_ndkexample_NdkLib_getInfo в ndkexample.cpp:
  1. #include "com_annimon_ndkexample_NdkLib.h"
  2.  
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <time.h>
  6.  
  7. JNIEXPORT jstring JNICALL Java_com_annimon_ndkexample_NdkLib_getInfo(
  8.         JNIEnv * env, jclass thiz, jint numElements) {
  9.  
  10.     long startTime = time(NULL);
  11.     srand(startTime);
  12.  
  13.     long sum = 0;
  14.     for (int i = 0; i < numElements; i++) {
  15.         sum += (rand() % 20 - 10); // -10..10
  16.     }
  17.  
  18.     long totalTime = time(NULL) - startTime;
  19.  
  20.     char info[96] = { 0 };;
  21.     sprintf(info, "Sum: %ld\n Total time: %ld", sum, totalTime);
  22.  
  23.     return env->NewStringUTF(info);
  24. }

Добавляем в класс NdkLib строки для инициализации библиотеки:
  1. static {
  2.     System.loadLibrary("ndkexample");
  3. }

Класс MainActivity приводим к следующему виду:
  1. package com.annimon.ndkexample;
  2.  
  3. import java.util.Random;
  4. import android.os.Bundle;
  5. import android.widget.TextView;
  6. import android.app.Activity;
  7.  
  8. public class MainActivity extends Activity {
  9.  
  10.     private static final int ELEMENTS = 1000000;
  11.  
  12.     @Override
  13.     protected void onCreate(Bundle savedInstanceState) {
  14.         super.onCreate(savedInstanceState);
  15.  
  16.         StringBuilder sb = new StringBuilder();
  17.         sb.append("Native:\n").append(NdkLib.getInfo(ELEMENTS));
  18.         sb.append("\n\nJava:\n").append(getInfo(ELEMENTS));
  19.  
  20.         TextView tv = new TextView(this);
  21.         tv.setText(sb.toString());
  22.         setContentView(tv);
  23.     }
  24.  
  25.     private String getInfo(int numElements) {
  26.         long startTime = System.currentTimeMillis();
  27.         Random rnd = new Random(startTime);
  28.  
  29.         long sum = 0;
  30.         for (int i = 0; i < numElements; i++) {
  31.             sum += (rnd.nextInt(20) - 10);
  32.         }
  33.  
  34.         long totalTime = System.currentTimeMillis() - startTime;
  35.  
  36.         return String.format("Sum: %d\n Total time: %d", sum, totalTime);
  37.     }
  38. }

Компилируем проект. Если выскочила ошибка "Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 8", то добавьте в папку jni файл Application.mk с содержимым:
APP_PLATFORM := android-8
Вот такой результат выдал эмулятор, впечатляет?

  3.png

А вот данные моего Sony Xperia Pro:

  4.png

А какие у вас результаты?

P.S. Весь проект тут (бинарник тоже): NdkExample.zip
  • +3
  • views 7220