目的
- セキュリティの向上
- 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でした。
- Android Studioを起動
- 左メニュー(Project)の『app』を<<右クリック>>、『Open Module Settings』を選択します。
- 『Project』の”Gradle version”を最新のGradleにします。今回は、『2.5』と入力しました。
- "Gradle project sync in progress..."と表示され、自動的にバージョンアップが行われます。
- 5分ほどで完了します。
Android NDKのダウンロード
- AndroidStudioを起動
- 左メニュー(Project)の『app』を<<右クリック>>、『SDK Location』を選択します。
- "Android SDK Location:"の下にある『Download Android NDK』をクリックします。
- 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を利用した暗証番号の隠蔽(予定)