Android StudioのNDK開発機能を使おう！

@eaglesakura です。

5月のGoogle IOでAndroid StudioのNDK対応が発表されてからずっとNDK対応するする詐欺を１ヶ月半されてきましたが、7/10のアップデート(Android Studio 1.3 RC1)でようやく待望のNDK対応アップデートが行われました。

使った感じはかなり良いのですが、前提条件やハマりどころが多いので、自分用のメモを兼ねて投稿します。

## 準備

準備を行わずにASのアップデートのみを行っても、NDK機能は全く動作してくれません。また、ビルドも行ってくれません。

* そのあたりのツイート
 * http://twitter.com/eaglesakura/status/619305979318939648
 * http://twitter.com/eaglesakura/status/619306946579992576
 * http://twitter.com/eaglesakura/status/619307831049596928

それを回避するためには、前提条件をクリアする必要があります。

1. Android Studio 1.3 RC1をインストールする
1. JDK 1.7をインストールする
 * Android Studioの環境変数もちゃんとJDK1.7に変更しておきましょう。
1. Gradle 2.5をインストールする
 * 最近GradleがC/C++対応したのはコレ関係かもしれないですね。

![スクリーンショット 2015-07-11 9.29.21.png](https://qiita-image-store.s3.amazonaws.com/0/15670/d65282fa-cec0-31f1-b7cb-332f2802779a.png)


## 新Pluginに合わせてbuild.gradleをかなり書き換える

NDK対応を行うためか次のステップのためかはわかりませんが、Android StudioのNDK機能を利用するためには、"実験用"とされてるPluginを利用する必要があります。

このPluginではbuild.gradleの記述がかなり変更されているため、実案件で使うと将来痛い目に会うかもしれません。また、確認した限りいくつかのバグがまだあります。

まず、experimental用のGradlePluginは今までとは別なPluginとして扱われるので、classpathを書き換える（もしくは追記する）必要があります。

```groovy:root/build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // classpathが変わった
        classpath 'com.android.tools.build:gradle-experimental:0.1.+'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
```

さらに、プロジェクトをビルドするためのbuild.gradleが大胆に変更されています。Plugin名も変わっています。

記述方法は、従来は"compileSdkVersion 22"のように記述していた部分が、"compileSdkVersion = 22"のように"="で記述するようになりました。実装的には、メソッドではなくて変数に書き換わったかと思います。

* 全ての変数がそうなのか？というとそうではなくて、一部は従来のようにメソッドのままなので、多少統一感がありません。今後変更される可能性があります。

他にも"defaultConfig"ブロックが"defaultConfig.with"のように書き換えが必要だったり、結構変更は多いです。
そもそもandroidブロック全体を"model"ブロックが包んだりしているので、互換性とかありません。ガンバって書き換えましょう。

```groovy:root/プロジェクト/build.gradle
// Plugin名が変わった
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 22
        buildToolsVersion = "23.0.0 rc3"

        defaultConfig.with {
            applicationId = "com.example.eaglesakura.ndk.helllo_ndk"
            minSdkVersion.apiLevel = 15
            targetSdkVersion.apiLevel = 22
            versionCode = 1
            versionName = "1.0"
        }

        // package除外は多少法則が乱れる。ややこしい。
        packagingOptions.with {
            exclude 'LICENSE.txt'
            exclude 'META-INF/LICENSE'
            exclude 'META-INF/LICENSE.txt'
            exclude 'META-INF/NOTICE'
            exclude 'androidannotations-api.properties'
            exclude 'services/com.fasterxml.jackson.core.JsonFactory'
            exclude 'services/com.fasterxml.jackson.core.ObjectCodec'
        }
    }

    android.buildTypes {
        debug {
            isJniDebuggable = true
        }

        release {
            isMinifyEnabled = false
            proguardFiles += file('proguard-rules.pro')
        }
    }


    android.sources {
        main {
            jni {
                source {
                    // JNIのソースディレクトリを変更したい場合、コレで追加削除できる
                    srcDirs = [file("src/main/jni"), file("ndk")]
                }
            }
        }
    }

    android.ndk {
        moduleName = "app"
    }
    android.productFlavors {
        // 特定のabiだけビルドしたい
        create("arm7") {
            ndk.abiFilters += "armeabi-v7a"
        }

        // 全部のabiをビルドしたい
        create("fat") {
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
}
```

これでAndroid Studioが新Pluginを利用してくれて、NDK機能が有効化されるはずです。あとはjniのディレクトリのhoge.cppを入れておけば、勝手にビルドしてくれます。

NDK機能はJavaの"native"メソッドをチェックして、もしCPP側に実装されていなかったら警告を出すようにしてくれるので、非常に親切です。

* http://twitter.com/eaglesakura/status/619329166748422144

## ブレークポイントで止める

無事にビルドできたら、次はブレークポイントで止めてみたくなりますね。ブレークポイントの設定はJava側と同じです。

![スクリーンショット 2015-07-11 9.28.08.png](https://qiita-image-store.s3.amazonaws.com/0/15670/a689e70c-ff8f-fa76-1254-a83c0c7e82b7.png)

### JNIデバッグをONにする

ドキュメントのどこにも見つかりませんでしたが、JNIのデバッグをONにするためには、android.buildTypesブロックの中でisJniDebuggable=trueをする必要があります。

* ドキュメントのどこにも書いてなかったので、１時間くらいドハマりしてました。
 * http://twitter.com/eaglesakura/status/619340321537703937

次に"Edit Cofigures"でAndroid Nativeの実行設定を作成して、それに切り替えます。

![スクリーンショット 2015-07-11 9.28.46.png](https://qiita-image-store.s3.amazonaws.com/0/15670/95b9faa9-6f5a-d2ba-3461-bc66bcd99555.png)

![スクリーンショット 2015-07-11 9.26.11.png](https://qiita-image-store.s3.amazonaws.com/0/15670/c1fc69cd-64fb-0396-c10a-8a4169862f85.png)

あとはいつものようにデバッグボタンを押して実行すると、JNIのデバッグ接続が行われます。

### 起動直後は停止できない

NDK用のデバッガ(LLDB）は、アプリが起動後に接続される（しかも数秒かかる）ため、onCreate等の起動直後のブレークポイントはそのままでは捉えることが出来ません。接続されているかどうかをチェックするためには、Debugのログを観察しましょう。

"Debugger attached to process XXXX"と表示されたタイミングから、ブレークポイントで止めることが出来ます。

![スクリーンショット 2015-07-11 9.36.03.png](https://qiita-image-store.s3.amazonaws.com/0/15670/8bc434a5-51e4-3c60-4897-0b8af3d2ae3c.png)

### 起動直後のブレークポイントで停止させる

起動直後のブレークポイントで止めたい場合、少しだけ工夫が必要です。Androidの開発者機能で"デバッグアプリを選択"をして、デバッグしたいアプリを選択します。

![device-2015-07-11-093830.png](https://qiita-image-store.s3.amazonaws.com/0/15670/95d7c095-76e8-27a3-739b-90b31adcb3fc.png)


次に"デバッガを待機"を有効化します。

![device-2015-07-11-093931.png](https://qiita-image-store.s3.amazonaws.com/0/15670/b0de377f-6c56-44a7-e68e-b1c810d99ee2.png)

これでデバッガが接続されるまで、アプリの起動を保留することが出来ます。この状態で再度実行すると、こんな感じで止まります。

![device-2015-07-11-094059.png](https://qiita-image-store.s3.amazonaws.com/0/15670/50a2c1cc-e394-a025-62d0-0fc923f953a7.png)

ただしNDKのデバッガ(LLDB)が接続されても、AndroidはLLDBをデバッガと認識しないため、いつまでたっても起動しません。そこで、Java側のデバッガも追加でアタッチします。

メニューの"Attach debugger to Android process"を選択して、接続したいアプリのpackageを選択してOKします。

* 画像の一番右側 ![スクリーンショット 2015-07-11 9.40.45.png](https://qiita-image-store.s3.amazonaws.com/0/15670/a01a3b13-7441-acb8-81ab-9bd5d700b8ad.png)

![スクリーンショット 2015-07-11 9.43.09のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/15670/a81e3580-9551-43a0-3f21-a4d0d612cfef.png)

するとAndroidはデバッガ接続を認識してアプリ起動を続行してくれるので、あとはブレークポイントを待てばOKです。Javaのデバッガも同時接続しているので動作は緩慢になりますが、この方法であればNDK/SDK両方のデバッグを行えます。便利ですね。

* C++のブレークポイントで止められる
 * ![スクリーンショット 2015-07-11 9.45.14.png](https://qiita-image-store.s3.amazonaws.com/0/15670/3b3574d9-d67b-267b-8e08-ce425c60185e.png)
* 値を見ることが出来る
 * ![スクリーンショット 2015-07-11 9.45.18.png](https://qiita-image-store.s3.amazonaws.com/0/15670/154ad64c-4af0-ef66-bf3b-87759d2cf673.png)
* 値の強制的な書き換えもできる(私の環境だと変数を指定してF2ボタン）
 * ![スクリーンショット 2015-07-11 9.46.45.png](https://qiita-image-store.s3.amazonaws.com/0/15670/71cce8a1-7b8b-bbe0-d9a1-b40d2bd6c5e0.png)
 * ![スクリーンショット 2015-07-11 9.46.37.png](https://qiita-image-store.s3.amazonaws.com/0/15670/af59c3aa-3619-4ca7-ce17-e6e6bc03050a.png)

## 現行バージョンのGradleプロジェクトとの連携

実際の開発で使おうと思うと非常に問題となるのは、「そもそもbuild.gradleの構成が違いすぎる」ということです。例えば、android-aptのPluginは正常に動作しませんでした。

これはライブラリプロジェクトを併用することで回避できます。具体的には、Java側をライブラリプロジェクトに移行して、C++側だけを新しいプロジェクト構成にすればOKです。

現状のASはライブラリプロジェクトもデバッガを接続でき、なおかつ従来構成のgradleも今までどおり利用できます。併用を出来るようにしてくれたのは、非常にありがたい点でした。

## 現状での不明な機能

* 指定した*.cppや*.cをビルドから除外する
 * ビルドはディレクトリ単位で突っ込まれるので、もしOSS等でtest用のcppがディレクトリに含まれていると、ビルドが壊れる原因になる。
 * glmとか。

## 現状の困った不具合

* clangでビルドすると、成果物にapkが含まれない

## 困ったときのLink

* Google公式NDKサンプル
 * https://github.com/googlesamples/android-ndk
 * includeディレクトリの追加方法とかもここを参考にすればOK
* NDK Previewの解説ページ
 * http://tools.android.com/tech-docs/android-ndk-preview
* Experimental Pluginの解説ページ
 * http://tools.android.com/tech-docs/new-build-system/gradle-experimental

## 最後に

* 別な環境だと別な点でドはまりするかもしれません。
* 何かあったら追記していきます。