Android Studio 1.3でNDKアプリケーションを作成する

gradle-experimentalプラグインを使ってNDKアプリケーションを作成します。

Android Studio 1.3ではNDKを正式にサポートしていません。

 

1 Android Studio 1.3のNDKサポート状況

Android Studio 1.3はデフォルトでNDKをサポートしていません。gradle-experimentalプラグインを使う必要があります。Android Studio 1.3でNDKを使用しているサンプルコードはgradle-experimentalを使用しています。

gradle-experimentalプラグインは既存のAndroid.mkを流用できません。現状はAndroid.mkの内容をapp/build.gradleにDSLとして記述します。Android.mkを作成したプロジェクトは移植が必要となります。

Android.mkをそのまま使えるようになることを今後期待しましょう。MakefileをAndroid.mkに変換した工数がまた発生しないことを願って。

2 作成するアプリケーションの内容

Android Studioのデフォルトでプロジェクトを作成します。NDKのコードで__android_log_printを呼び出すJNI関数を定義します。JavaのコードでJNI関数を呼び出します。JNIの使い方はこちらを参照してください。

3 Android Studioでプロジェクトを作成

NDKApplicationという名前でプロジェクトを作成します。他の設定はデフォルトのままにします。

0001_New-Project.png

0002_Project-Name.png

0003_API-Level.png

0004_Add-Activity.png

0005_Activity-Name.png

4 gradle-experimentalプラグインを使う

gradle-experimentalプラグインとは、Android Studioで使われているgradleプラグインの実験的なバージョンです。Android Studioで使われているgradleプラグインはGradle 2.4を使用しますが、gradle-experimentalプラグインはGradle 2.5を使用します。

4.1 build.gradle

build.gradleでgradle-experimentalプラグインを読み込むようにします。gradleとgradle-experimentalのバージョンは完全に別物です。

diff -uprN NDKApplication.org/build.gradle NDKApplication/build.gradle
--- NDKApplication.org/build.gradle 2015-10-01 01:33:17.000000000 +0900
+++ NDKApplication/build.gradle 2015-10-01 01:41:14.000000000 +0900
@@ -5,7 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.3.0'
+        classpath 'com.android.tools.build:gradle-experimental:0.2.0'

         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files

4.2 gradle/wrapper/gradle-wrapper.properties

gradle/wrapper/gradle-wrapper.propertiesでGradle 2.5を使用するようにします。

diff -uprN NDKApplication.org/gradle/wrapper/gradle-wrapper.properties NDKApplication/gradle/wrapper/gradle-wrapper.properties
--- NDKApplication.org/gradle/wrapper/gradle-wrapper.properties 2015-10-01 02:02:18.000000000 +0900
+++ NDKApplication/gradle/wrapper/gradle-wrapper.properties 2015-10-01 01:36:03.000000000 +0900
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip

4.3 app/build.gradle

gradle-experimentalプラグインの変更に伴い、app/build.gradleの書式を変更します。

Android Studioでプロジェクトを作成した場合は以下のようになっています。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.hiroom2.ndkapplication"
        minSdkVersion 23
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

gradle-experimentalプラグインに対応させる為、以下のように変更します。modelというブロックが追加され、=で値が設定されるようになっています。

apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.1"

        defaultConfig.with {
            applicationId = "com.hiroom2.ndkapplication"
            minSdkVersion.apiLevel = 23
            targetSdkVersion.apiLevel = 23
            versionCode = 1
            versionName = "1.0"
        }

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

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

対応が十分でない場合、以下のエラーが発生します。

Failed to sync Gradle project 'NDKApplication'
Error:Unable to load class 'com.android.build.gradle.managed.ProductFlavor_Impl'.
Possible causes for this unexpected error include:
  * Gradle's dependency cache may be corrupt (this sometimes occurs after a network connection timeout.)
    Re-download dependencies and sync project (requires network)
  * The state of a Gradle build process (daemon) may be corrupt. Stopping all Gradle daemons may solve this problem.
    Stop Gradle build processes (requires restart)
  * Your project may be using a third-party plugin which is not compatible with the other plugins in the project or
    the version of Gradle requested by the project.
In the case of corrupt Gradle processes, you can also try closing the IDE and then killing all Java processes.

0006_Error-with-gradle-experimental.png

4.4 Java 1.7互換

Java 1.8を使っている場合、以下のエラーが発生します。

Error:com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
at com.android.dx.command.dexer.Main.parseClass(Main.java:764)
at com.android.dx.command.dexer.Main.access$1500(Main.java:85)
at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1684)
at com.android.dx.command.dexer.Main.processClass(Main.java:749)
... 19 more
UNEXPECTED TOP-LEVEL EXCEPTION:

0007_Error-with-java-1.8.png

Java 1.7互換にする為、app/build.gradleに以下の設定を加えます。

diff -uprN NDKApplication.err/app/build.gradle NDKApplication/app/build.gradle
--- NDKApplication.err/app/build.gradle 2015-10-01 02:17:56.000000000 +0900
+++ NDKApplication/app/build.gradle 2015-10-01 02:22:35.000000000 +0900
@@ -20,6 +20,11 @@ model {
             }
         }
     }
+
+    compileOptions.with {
+        sourceCompatibility = JavaVersion.VERSION_1_7
+        targetCompatibility = JavaVersion.VERSION_1_7
+    }
 }

 dependencies {

5 NDK

NDKのコードを追加していきます。

5.1 JNI Folderの追加

projectのタブにてappを右クリックしてJNI Foloderを追加します。

0008_Add-JNI-Folder.png

JNI Folderの場所を設定します。デフォルトだとsrc/main/jniとなります。

0009_JNI-Folder-Location.png

5.2 NDKコードの追加

projectのタブにてjniを右クリックしてC/C++ Source Fileを追加します。

0010_CCPP-Source-File.png

今回はhello.cという名前のCソースファイルをヘッダなしで作成します。

0011_CCPP-Source-File-Detail.png

5.3 NDKコードの編集

hello.cを以下のように変更します。

//
// Created by hiroom2 on 2015/10/01.
//

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

JNIEXPORT void Java_com_hiroom2_ndkapplication_MainActivity_hello(JNIEnv *env, jobject obj)
{
    __android_log_print(ANDROID_LOG_INFO, "JNI", "hello");
}

5.4 Javaコードの編集

libhello.soをロードして、onCreate実行時にJNIのhello関数を呼びます。

$ diff -uprN NDKApplication{.org,}/app/src/main/java/com/hiroom2/ndkapplication/MainActivity.java
--- NDKApplication.org/app/src/main/java/com/hiroom2/ndkapplication/MainActivity.java 2015-10-01 05:21:51.000000000 +0900
+++ NDKApplication/app/src/main/java/com/hiroom2/ndkapplication/MainActivity.java 2015-10-01 12:16:14.000000000 +0900
@@ -7,10 +7,17 @@ import android.view.MenuItem;

 public class MainActivity extends Activity {

+    static {
+        System.loadLibrary("hello");
+    }
+
+    private native void hello();
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
+        hello();
     }

     @Override

5.5 NDKのビルド設定

app/build.gradleのandroid.ndkブロックでモジュール名とライブラリを設定します。__android_log_printは-llogを必要とします。

androidブロック外部でandroid.ndkを使う必要があります(anroidブロック内部でndk.withを使うとJNI Folderがビルドされないようです)。

 

Android.mk app/build.gradle
LOCAL_MODULE moduleName
LOCAL_LDLIBS ldLibs
LOCAL_SRC_FILES なし(JNI Folder配下のコード全て)

$ diff -uprN NDKApplication.org/app/build.gradle NDKApplication/app/build.gradle
--- NDKApplication.org/app/build.gradle 2015-10-01 11:50:47.000000000 +0900
+++ NDKApplication/app/build.gradle     2015-10-01 12:44:07.000000000 +0900
@@ -21,6 +21,11 @@ model {
         }
     }

+    android.ndk {
+        moduleName = "hello"
+        ldLibs += "log"
+    }
+
     compileOption.with {
         sourceCompatibility = JavaVersion.VERSION_1_7
         targetCompatibility = JavaVersion.VERSION_1_7

6 実行例

NDKApplicationのアクティビティが起動し、logcatに__android_log_print関数のログが出力されます。

10-01 12:44:43.962  23837-23837/? I/JNI﹕ hello

0012_Run-Application.png