LoginSignup
9
11

More than 5 years have passed since last update.

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

Posted at

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

そもそもJNIとは?

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

Wikipedia より

環境構築

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

  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 の文字列を返すメソッドを持ったクラスを作成してみます。

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

  companion object {
    init {
      System.loadLibrary("sample-native")
    }
  }
}

↑で指定した sample-native は作成される共有ライブラリ名を指定してます。

3. CMakeLists.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. ネイティブ側実装

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_helloxxx にはパッケージ名が入ります。

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

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

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

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

Tips

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

  • Log出力に関して

    #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しておくと便利

  • オブジェクト生成

    #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

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

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

参考URL

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11