?

Log in

No account? Create an account

Previous Entry | Next Entry

Use NDK in Android Studio, part 2

Якщо у минулому пості мова йшлася здебільшого про оточення, зараз перейдемо до дійсно C++. Отже, припустимо, що вам потрібно піднести до квадрату, що буде виглядати таким приблизно чином у java (SquaredWrapper.java)

1. Create Java source

package com.anonym.myapplication;

public class SquaredWrapper {
// Declare native method (and make it public to expose directly)
public static native int squared(int base);
}

Ми навмисно починаємо з оголошення у java і залишаємо реалізацію на C++ на потім. Шлях навпаки, коли в нас вже є готові методи на C++ і треба викликати їх із java, буде розглянуто в подальшому.

2. Create C header

Зрозуміло, що подане оголошення існує виключно заради того, щоб дістатися "рідного" методу на C++, але ж як цій "рідний" метод треба декларувати? Щоб втямити це, JDK має утіліту під назвою javah. Вона отримує скомпільований java-код (наприклад, файл з розширенням class). Тож скомпілюємо його:

javac SquaredWrapper.java

та запустимо javah. Треба мати на увазі, що package, якого визначено на початку java файлу, впливає на поведінку javah. Найзручніше, на мою думку, запускати javah з діректорії 'java' під 'main' головного проекту. У цьому випадку треба визначити лише цілкове ім'я package з додатком ім'я класу.  У нашому випадку це буде виглядати

C:\Dev\Android\MyApplication\app\src\main\java>javah com.anonym.myapplication.SquaredWrapper

Побудований файл буде мати назву com_anonym_myapplication_SquaredWrapper.h, але ж його вартить перейменувати, наприклад, до SquaredWrapper.h та пересунити до діректорії, де будуть розташовані подальші C++ файли.

Роздивимось змісту файлу, що було побудовано:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_anonym_myapplication_SquaredWrapper */
#ifndef _Included_com_anonym_myapplication_SquaredWrapper
#define _Included_com_anonym_myapplication_SquaredWrapper
#ifdef __cplusplus
extern "C" {
/*
* Class:     com_anonym_myapplication_SquaredWrapper
* Method:    squared
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_anonym_myapplication_SquaredWrapper_squared
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif

Звернемо увагу до ім'я, яке отримав наш метод. Його побудовано таким чином:
1. текст "Java_"
2. ім'я пакету, де крапки змінені на символи підкреслення
3. ім'я класу, до якого належить метод
4. дійсно і'мя методу

Перші 2 параметри до будь-якого методу, створеного за допомогою javah, не змінні. Інші залежать від типов, декларованих у java-коді. Ось, наприклад, метод, якій отримує масив:

JNIEXPORT void JNICALL Java_com_example_montreuxclient_fastcv_SquaredWrapper_analize
(JNIEnv *, jclass, jintArray);

3. Create C source
Спираючись на побудований header, без труднощів можно реалізувати цільового метода:

#include "SquaredWrapper.h"
JNIEXPORT jint JNICALL Java_com_anonym_myapplication_SquaredWrapper_squared
(JNIEnv *je, jclass jc, jint base)
{
return (base*base);
}

4. Build Java & NDK project
Зараз ми маємо усе необхідне, щоб повернитися то інтеграціі побудованих файлів до NDK і Gradle з Android Studio. Android.mk буде виглядати, як очекується:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := squared
LOCAL_SRC_FILES := SquaredWrapper.c

include $(BUILD_SHARED_LIBRARY)


Інші зміни до Gradle також очікувані:

ndk {
moduleName "squared"
}
sourceSets{
main {
jni.srcDirs = []
}
}

та

task buildNative(type: Exec, description: 'Compile JNI') {
def ndkDir = 'C:\\Android\\ndk\\android-ndk-r10d'
commandLine "$ndkDir/ndk-build.cmd",
    '-C', file('src/main/jni').absolutePath
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}

Протягом того, як Gradle виконує tasks, звернемо увагу, як зветься і де буде розміщена побудована бібліотека

[armeabi] Install        : libsquared.so => libs/armeabi/libsquared.so

5. Use NDK from Java
Все, що залишилось зробити - це завантажити побудовану дінамичну бібліотеку із java-коду та викликати відповідні методи. Перше робимо таким чином:

static {
  System.loadLibrary("squared");
}

Друге - таким:

    SquaredWrapper wrapper = new SquaredWrapper();
    int res = wrapper.squared(4);
    int[] array = new int[]{1,2,3};
    int size = wrapper.analyze(array);