AndroidStudioでNDK(JNI)を活用する 準備編

  • 72
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

目的

  • セキュリティの向上
    • Androidのapkパッケージは、ProGuardで難読化が行われます。しかし、難読化は暗号化ではないので、ソースコード上に、暗証番号などを記載していた場合、リバースエンジニアリングですぐに見破られてしまいます。
    • Native(C,C++)のソースコード内に暗証番号を記載し、それをJavaから呼び出すことで、隠蔽することができます。
  • 高速化
    • AndroidのDalvik VM(仮想マシンで)は、VMであるがゆえに処理速度で越えられない壁が存在します。この越えられない壁を越えなければいけない状況に陥った時に、OSが直接実行できるバイトコードにできるNDKが有効です。
    • ほとんどの場合、Javaのプログラムの最適化で済むと思いますが、端末内で画像処理を行うなどの場合には、選択肢に入ってくると思います。
  • iOS,Androidでのライブラリの共通化
    • C,C++でソースコードを書くことができるので、iOS(Objective-C)からも読み込むことができますし、AndroidでもJNIの型にマッチさせれば、読み込むことができます。内部ロジックはそのままで、インターフェイスの書き換え(もしくは、ラップ)のみで両OSから、利用することができます。
    • 注意点としては、環境によってバイトオーダー(ビッグエンディアン, リトルエンディアン)が異なります。

環境構築(2015/10/10現在)

Android NDK Preview
Android Studio 1.3以上、Gradle 2.5以上が必須のようです。
今回は、以下の環境で試しました。

  • Android Studio 1.4
  • Gradle 2.5
  • Android NDK r10e

Gradleのバージョンアップ

Android Studio 1.4で、新規プロジェクトを作成した場合、Gradleのバージョンは、2.4でした。
1. Android Studioを起動
2. 左メニュー(Project)の『app』を<<右クリック>>、『Open Module Settings』を選択します。
3. 『Project』の”Gradle version”を最新のGradleにします。今回は、『2.5』と入力しました。
4. "Gradle project sync in progress..."と表示され、自動的にバージョンアップが行われます。
5. 5分ほどで完了します。

Android NDKのダウンロード

  1. AndroidStudioを起動
  2. 左メニュー(Project)の『app』を<<右クリック>>、『SDK Location』を選択します。
  3. "Android SDK Location:"の下にある『Download Android NDK』をクリックします。
  4. 20分ほどで完了します。

Experimental Pluginの適用

Experimental Plugin User Guide

『gradle-wrapper.properties』修正

  • 変更前
/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
  • 変更後
/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip

プロジェクトの『build.gradle』修正

  • 変更前
/build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
    }
}

  • 変更後(実験的……。まぁ、実験好きですけど)
/build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle-experimental:0.2.0'
    }
}

appの『build.gradle』の修正

  • 変更前
/app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "info.*****.sbx.ndk01"
        minSdkVersion 22
        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'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}
  • 変更後
    • pluginが変わるので、そもそものDSL(記述方法)が変わります。
    • 今回は、『hello.c』というファイルを『libhello.so』というダイナミックリンクライブラリにコンパイルして、Android内から利用する想定です。
/app/build.gradle
apply plugin: 'com.android.model.application'

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

        defaultConfig.with {
            applicationId = "info.*****.sbx.ndk01"
            minSdkVersion.apiLevel = 22
            targetSdkVersion.apiLevel = 23
        }
    }

    /*
     * native build settings
     */
    android.ndk {
        moduleName = "hello"
        /*
         * Other ndk flags configurable here are
         * cppFlags += "-fno-rtti"
         * cppFlags += "-fno-exceptions"
         * ldLibs    = ["android", "log"]
         * stl       = "system"
         */
    }
    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles  += file('proguard-rules.txt')
        }
    }
    android.productFlavors {
        create("arm") {
            ndk.abiFilters += "armeabi"
        }
        create("arm7") {
            ndk.abiFilters += "armeabi-v7a"
        }
        create("arm8") {
            ndk.abiFilters += "arm64-v8a"
        }
        create("x86") {
            ndk.abiFilters += "x86"
        }
        create("x86-64") {
            ndk.abiFilters += "x86_64"
        }
        create("mips") {
            ndk.abiFilters += "mips"
        }
        create("mips-64") {
            ndk.abiFilters += "mips64"
        }
        // To include all cpu architectures, leaves abiFilters empty
        create("all")
    }

}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}
  • "Sync Now!"

JNI(Java Native Interface)で、Hello World!

ライブラリファイルの作成

  • JNI用のフォルダを作成します
    • app/src/main/jni/
  • hello.cを作成します
    • ヘッダファイル(.h)は不要。
    • この時重要なのは関数名です。これを間違えると、時間だけが浪費されます。
    • JNI(Java Native Interface)の名が示す通り、ビルド時に命名規則に従ってインターフェイスをマップしてくれます。
    • Java_{プロジェクトディレクトリ}{呼び出し元クラス名}{メソッド名}
app/src/main/jni/hello.c
#include <string.h>
#include <jni.h>

jstring
Java_info_*****_sbx_ndk01_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz ) {
    return (*env)->NewStringUTF(env, "Hello JNI !");
}

MainActivityからの呼び出し

MainActivity.java
public class MainActivity extends AppCompatActivity {

    // hello.soの読み込み
    static {
        System.loadLibrary("hello");
    }

    // helloライブラリにあるメソッドの定義
    public native String  stringFromJNI();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // メソッドの呼び出し
        // Hello JNI !と表示される
        Log.i("", stringFromJNI());
    }
}

参照

ちょっといっぷく

  • 実際のダイナミックリンクライブラリはどこにあるのか?
    • デバッグビルドだと以下のディレクトリにあるようです。
    • app/build/intermediates/binaries/debug/all/obj/armeabi-v7a/libhello.so
  • Eclipse + NDK
    • 3~4年前、実際の製品を作った時には、Windows + Eclipse + NDKで、Cソースを書き換えるたびにCygwinでビルドして、その後、Eclipseでビルドして、と手間が多かったですが、AndroidStudioでは、二つのビルドを一気にやってくれます。
    • 基本的に、『.so』ファイルに変更がなければ、そのままリリースしていましたが、何かの拍子に同封し忘れて(警告はでない)すとんと落ちていたということがありました。AndroidStudioでは、ワンストップなので、きっとそんなこともなくなるでしょう。
  • 使い所
    • Googleのサンプルを見る限り、やはりマルチメディア(音声、画像、3Dモデリング)の扱いが多いようです。

つづく

  • JNIの型とデータ構造(予定)
  • デバッグ方法(予定)
  • NDKを利用した暗証番号の隠蔽(予定)