Android Studioでndk-buildを使う

# はじめに

Android StudioでNDKをビルドするためのツールとして、cmakeとndk-buildがある。

公式にはcmakeが推奨なため、ndk-buildを使った方法の説明が不十分なので、ここに記録を残す。
https://developer.android.com/studio/projects/add-native-code 



# externalNativeBuildブロックを使う方法

基本的には、この方法を使う。

## フォルダ構造

```
├── app
│   ├── build.gradle
│   ├── src
│   │   ├─- main
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── java
│   │   │   ├── jni
│   │   │   │   ├── Android.mk
│   │   │   │   ├── Application.mk
│   │   │   │   ├── *.c
│   │   │   │   ├── *.h
│   │   │   ├── res
├── build.gradle
├── settings.gradle
```


## build.gradleの設定

```gradle:build.gradle


android {

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 28

        ndk {
            // NDKビルドするABIを指定する
            abiFilters 'x86', 'armeabi-v7a'
        }
    }

    externalNativeBuild {
        ndkBuild {
            // Android.mkのファイルパス
            path 'src/main/jni/Android.mk'
        }
    }

    buildTypes {
        debug {
            externalNativeBuild {
                ndkBuild {
                    // debugビルド時にのみ指定する、ndk-buildの引数。
                    arguments 'LOCAL_CFLAGS+=-SOME_FLAG1', 'LOCAL_CFLAGS+=-DSOME_FLAG2'
                }
            }
        }

        release {
            externalNativeBuild {
                ndkBuild {
                    // releaseビルド時にのみ指定する、ndk-buildの引数。
                    arguments 'LOCAL_CFLAGS+=-SOME_FLAG3', 'LOCAL_CFLAGS+=-DSOME_FLAG4'
                }
            }
        }

```

* Application.mkの APP_PLATFORMとAPP_ABIは無視され、build.gradleのabiFilters、minSdkVersionから自動で設定される。


# ndk-build用カスタムタスクを追加する方法

externalNativeBuildブロックを使う方法だと、Android.mkのAPP_PLATFORMは無視され、build.gradleのminSdkVersionから自動で設定されてしまう。そのため次のようなプロジェクトの場合に問題となる。 
* minSdkVersionは9
* NDKで作るnative libraryではOpenGL ES (18以上で利用サポート)を使う 
（当然、API levelが18以上時のみSystem.LoadLibrary()をする必要がある。）

このようなプロジェクトの場合、ndk-build用のカスタムタスクを作成して実行する。

## フォルダ構造

```
├── app
│   ├── build.gradle
│   ├── src
│   │   ├─- main
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── java
│   │   │   ├── native   ## jniとするとerrorになる。
│   │   │   │   ├── Android.mk
│   │   │   │   ├── Application.mk
│   │   │   │   ├── *.c
│   │   │   │   ├── *.h
│   │   │   ├── res
├── build.gradle
├── settings.gradle
```

## build.gradleの設定


```gradle:build.gradle

android {

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 28
    }

    // Put src/main/libs in aar
    sourceSets {
        main {
            jniLibs.srcDir 'src/main/libs'
        }
    }
}

tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn buildNative}

// ndk-build用の buildNativeタスクの作成
task buildNative(type: Exec) {
    // ndk-buildの設定。 環境変数ANDROID_NDK_HOMEで設定
    def ndkBuild;
    def ndkBuildingDir = file(new File("src/main"))
    def hasNdk = false;
    if (System.env.ANDROID_NDK_HOME != null) {
        hasNdk = true;
        ndkBuild = new File(System.env.ANDROID_NDK_HOME, 'ndk-build')
    }
     
    // ndk-buildの実行
    commandLine ndkBuild, "--directory", ndkBuildingDir, "APP_BUILD_SCRIPT=${ndkBuildingDir}/native/Android.mk", "NDK_APPLICATION_MK=${ndkBuildingDir}/native/Application.mk", "-j", Runtime.runtime.availableProcessors()

    doFirst {
        if (!hasNdk) {
            logger.error("ERROR: ANDROID_NDK_HOME not set.")
        }
        assert hasNdk : "ANDROID_NDK_HOME not set"
    }
}



// ndk-build clean用の task cleanNativeの作成
clean.dependsOn 'cleanNative'
task cleanNative(type: Exec) {
    def ndkBuild;
    def ndkBuildingDir = file(new File("src/main"))
    def hasNdk = false;
    if (System.env.ANDROID_NDK_HOME != null) {
        hasNdk = true;
        ndkBuild = new File(System.env.ANDROID_NDK_HOME, 'ndk-build')
    }

    commandLine ndkBuild, "--directory", ndkBuildingDir, "APP_BUILD_SCRIPT=${ndkBuildingDir}/native/Android.mk", "NDK_APPLICATION_MK=${ndkBuildingDir}/native/Application.mk", "-j1", "clean"

    doFirst {
        if (!hasNdk) {
            logger.error("ERROR: ANDROID_NDK_HOME not set.")
        }
        assert hasNdk : "ANDROID_NDK_HOME not set"
    }
}


```