久しぶりにJNIを触って感動した時のメモ

久しぶりにJNIを触ってみたらだいぶやりやすくなっていたので感動しました :sparkles: 
メモがてらツラツラ書いてみました。

## そもそもJNIとは?
> Java Native Interface (JNI) Javaプラットフォームにおいて、Javaで記述されたプログラムと、他のプログラミング言語（たとえばCやC++など）で書かれた、実際のCPU上で動作するコード（ネイティブコード）とを連携するためのインタフェース仕様である

[Wikipedia](https://ja.wikipedia.org/wiki/Java_Native_Interface) より

## 環境構築

大まかには以下の手順になります。

1. jni用のディレクトリ作成
2. Kotlin/Java側のネイティブ呼び出し用のクラスを作る
3. CMakeLists.txtを作成
4. ネイティブ側実装
5. `app/build.gradle`に設定追加

CMakeLists.txt を作成してbuild.gradleに記述するだけで
コンパイルして使えるとは。。おじさん感動しました :sparkles: 
(昔はAndroid.mk作ってだねえ。。)

**1. jni用のディレクトリ作成**

デフォルトだと `src/main/cpp` が使われているっぽいが、
分かりやすければどこでも良いかと個人的には思います。
今回は `src/main/java/xxxxx/jni` を作成しました。

**2. Kotlin/Java側のネイティブ呼び出し用のクラスを作る**

例としてネイティブから `hello` の文字列を返すメソッドを持ったクラスを作成してみます。

```kotlin:jni/SampleNative.kt
class SampleNative {
  external fun hello(): String

  companion object {
    init {
      System.loadLibrary("sample-native")
    }
  }
}
```
↑で指定した `sample-native` は作成される共有ライブラリ名を指定してます。

**3. CMakeLists.txtを作成**

```txt:jni/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)

add_library(
        sample-native
        SHARED
        sample-native.cpp)

find_library(
        log-lib
        log)

target_link_libraries(
        sample-native
        ${log-lib})
```

**4. ネイティブ側実装**

```c++:jni/sample-native.cpp
#ifndef __SAMPLE_NATIVE_H_
#define __SAMPLE_NATIVE_H_

#include <jni.h>
#include <android/log.h>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL
Java_xxx_xxx_xxx_xxx_jni_SampleNative_hello(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

#ifdef __cplusplus
}
#endif

#endif // __SAMPLE_NATIVE_H_
```
`Java_xxx_xxx_xxx_xxx_jni_SampleNative_hello` の `xxx` にはパッケージ名が入ります。

**5. `app/build.gradle`に設定追加**

```gradle:app/build.gradle
android {
  defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
  }
  externalNativeBuild {
      cmake { path "src/main/java/xxx/jni/CMakeLists.txt" }
  }
}
```

これで晴れてビルドできるようになりました :tada:
使う時には普通のメソッドを呼び出す様に使えます。

```kotlin
val sample = SampleNative()
sample.hello() // Hello from C++
```

## Tips
実装中に個人的に便利だと思ったものをピックアップしました。

- Log出力に関して

    ```c++
    #define TAG               "SampleNative"
    #define LOGD(...)         __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
    #define LOGE(...)         __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
    #define LOGI(...)         __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
    
    // LOGD("numbers %d %d", num1, num2);
    ```
    ↑の様にdefineしておくと便利

- オブジェクト生成

    ```c++
    #if defined(DEBUG) || defined(_DEBUG)
    #define __ASSERT__(e)      assert(e)
    #else
    #define __ASSERT__(e)
    #endif // DEBUG

    /**
     * 引数なしコンストラクタを呼び出しクラスオブジェクトを生成<br>
     *
     * @param  env       JNIEnv
     * @param  classPath クラスパス
     * @return           jobject
     */
    jobject newObject(JNIEnv *env, const char *classPath)
    {
        jclass cls = env->FindClass(classPath);
        __ASSERT__(cls != NULL);
        // コンストラクタ取得
        jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
        __ASSERT__(mid != NULL);
        jobject jobj = env->NewObject(cls, mid);
        // 後片付け
        env->DeleteLocalRef(cls);
        return jobj;
    }
    
    /**
     * java.util.ArrayListクラスのインスタンス取得
     *
     * @param  env JNIEnv
     * @return     インスタンス
     */
    jobject newArrayList(JNIEnv *env)
    {
        return newObject(env, "java/util/ArrayList");
    }
    ```
    JNI側でJava/Kotlin側オブジェクト生成はよく使うので↑の様にメソッド作っておくと便利


## バッドノウハウ
JUnitができない!

- [Android JUnit Testing for Native Libraries](https://www.sureshjoshi.com/mobile/android-junit-native-libraries/)

    どうしてもJUnitだとホストのPC環境のアーキテクチャでビルドするので
    `UnsatisfiedLinkError` が発生してしまう。

    頑張って↑のリンク先の様にビルドするか、instrumentedテストでやる

## 参考URL

- [JNIでC++のソースコード・共有オブジェクトをAndroidから利用するチュートリアルをやってみた](https://qiita.com/ymmtyuhei/items/7109579210af34ec697c)
- [kotlin-jni-javah.sh](https://gist.github.com/donaldmunro/0e5fd744726713f0aa428f9029244f3d)