Android
NDK
JNI
AndroidStudio
Androidセキュリティ

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

More than 3 years have passed since last update.


目的


  • セキュリティの向上


    • 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を利用した暗証番号の隠蔽(予定)