読者です 読者をやめる 読者になる 読者になる

DRY

Web関連の技術の事、食事/お酒の事、旅行の事など

android-ndk-r6を動かす

Android

Androidの開発を進める中で、NDKを使う必要が出てきたので最新バージョン(2011/07/27時点)のandroid-ndk-r6をWindows7にインストールして使用しようとしたのですが、また結構動かすのに変な苦労をしたんのでメモっておきます。

■まず現時点でr6のインストール関連の記事が少ない。(出たばっかみたいですからね。。。)
└build/host-setup.sh とか無いし。。。
└make APP= してもmake targetが無いとか言われるし。。。
■NDK付属のサンプルを動かした記事はいくつもあるのですが、自分のプロジェクトに組み込んで自作の関数を動かしたという記事は余り無い
■みんな「android-ndk-r6」の下で作業してるけど、既存のEclipseのプロジェクト配下でやりたいからそれだと嫌なんだけど。。。

的な人向けの記事です。


# 環境
Windows7
Eclipse 3.6 Helios
Cygwin

Cygwinに関してはここでは割愛しますので、ググッて入れて下さい。
gcc-core: C compiler
・make: The GNU version of the 'make' utility
は必須みたいなので、入れておいて下さい。


# 前準備
Android NDKの入手
今回は「android-ndk-r6-windows.zip」をダウンロードしました。

ダウンロードしたzipはそのまま解凍して、C:\bin\android-ndk-r6に置きました。
Cygwinを起動して、NDKライブラリへのパスを通しておきます。

$ vi ~/.bashrc

export ANDROID_NDK_ROOT=/cygdrive/c/bin/android-ndk-r6
export PATH=$PATH:$ANDROID_NDK_ROOT

Cygwinから見ると、Windows上のドライブはすべて"/cygdrive"以下になります。

他のサイトだと「NDK_PROJECT_PATH」の記載があったりもしますが、いろいろ試した結果、私は無くて問題無いと思います。


Eclipseのworkspaceの場所が「C:\Program Files\〜〜」の場合

最終的にCygwinを使って、NDKのビルドを行う際に「Program Files」のスペースの部分がどう記載しても認識してくれないので常に「No such File or Directory」になってしまいます。(C:\Program\〜〜と認識するため)

\s、バックスペース、PROGRAなどすべて記載しましたがダメでしたので、workspaceの場所を移動しました。
最終的に私はドライブが2個あるので「D:\Develop\Eclipse」以下に配置しました。
これは当然ですが、スペースが入らないパスであれば任意の位置で構いません。

# ついでにEclipseの設定もしておきます。
Eclipseの[ヘルプ⇒新規ソフトウェアのインストール]を選択

作業対象に「Helios」と入力すると候補が出てくるので「Helios - http://download.eclipse.org/releases/helios」を選択

真ん中のリストボックスから[プログラミング言語]を選択し[C/C++ Development Tools]と[C/C++ Library API Documentation Hover Help (インキュベーション)]を選択


で後は[次へ]で進み、次ページでライセンスに同意などしEclipseの再起動でOK



# 実際NDK呼び出しをJava側に記載します
※前提として、既に存在している自分のプロジェクトに追加する手順となります。

まずは、既に存在しているクラスなどから呼び出しのソースを記載します。


public class Main extends Activity
{
static
{
// JNIライブラリのロード
System.loadLibrary ("test-jni");
}
// JNIメソッドの定義
public native String getTestJni ();

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

TextView textView = (TextView)findViewById (R.id.textView);
textView.setText (getTestJni ());
}
}

※「test-jni」というライブラリの「getTestJni」の関数を呼ぶと定義しています。


次に、(慣れれば手動で行っても問題無いと思いますが)javah(JDK付属のJNIヘッダ作成ツール)を使用して、JNI実装用のC/C++言語ヘッダを生成します。

該当パッケージの該当クラス名.classファイルが最新であるかを確認します。(タイムスタンプなどから)
コマンドプロンプトを開いて、該当プロジェクトのプロジェクトトップ(AndroidManifest.xmlがある位置)に移動します。


$ d:
$ cd Develop/Eclipse/<該当プロジェクト>
$ "C:\Program Files\Java\jdk1.6.0_23\bin\javah.exe" -classpath bin -d jni <パッケージ名>

エラーがなければ、カレントディレクトリ(AndroidManifest.xmlと同列)に「jni」フォルダが出来ています。
その中にヘッダーファイルが入っていて、先ほどのjavah.exeの引数「<パッケージ名>.h」になっているはずです。


/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class xxx_xxxx_xxxxx_xxxxxx */

#ifndef _Included_xxx_xxxx_xxxxx_xxxxxx
#define _Included_xxx_xxxx_xxxxx_xxxxxx
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     xxx_xxxx_xxxxx_xxxxxx
 * Method:    getTestJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_xxx_xxxx_xxxxx_xxxxxx_getTestJni
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif



※「xxx_xxxx_xxxxx_xxxxxx」は私の環境の本当のパッケージ名です。すみません、諸事情でここは非公開とさせて下さい。

で、C/C++をやっている人であれば問題ないと思いますが、このファイルを「.h」のまま「.c or .cpp」でinlucdeしてもらっても構いませんし、このファイルを「.c」に変更して貰ってもなんら問題ありません。

私は「.c」にファイルの拡張子を変更して、中身も以下のように書き換えました。



#include
#include

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


まあ実は中身は何の事はなく、NDKのsampleフォルダの下のhello-jniです。
それと、jniフォルダの下に「Android.mk(名称固定)」と言うファイルを作ります。
NDKのsampleフォルダから持って着ても良いです。

そうすると、ディレクトリ構成はこんな感じになります。

[\\D:\Develop\Eclipse]
└ MyProject(+)
  └ jni(+)
    ├ Android.mk
    └ xxx_xxxx_xxxxx_xxxxxx.c
  └ res(+)
  └ src(+)
  └ AndroidManifest.xml
(など。他のDir/Fileは省略)


# Android.mkの編集

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := test-jni
LOCAL_SRC_FILES := xxx_xxxx_xxxxx_xxxxxx.c
include $(BUILD_SHARED_LIBRARY)


ここの中身もイロイロ変更したりしたのですが、結局これは多くの人と同じような形で落ち着きました。
Android.mkのパラメータに関してはこのサイトが圧倒的に詳しいので参考にして下さい。

どちらかと言うと、文字コードとかエディタのクセ?とか注意です。
私はエディタはxyzzyを使っているのですが、こいつで編集を掛けた後ビルドすると、「missing separate...」なんとか言われたりしたので記載に問題無いのにエラーが出る場合は、Eclipseで編集するなどしてみて下さい。


# ビルド
これでビルドの準備は整いました。
以下Cygwin上でコマンドを叩きます

$ cd /cygdrive/d/Develop/Eclipse//jni
$ ndk-build -B V=1

「-B」は強制コンパイル、「V=1」は詳細デバッグ情報出力


rm -f /cygdrive/d/Develop/Eclipse//libs/armeabi/lib*.so /cygdrive/d/Develo
p/Eclipse//libs/armeabi-v7a/lib*.so /cygdrive/d/Develop/Eclipse//lib
s/x86/lib*.so
rm -f /cygdrive/d/Develop/Eclipse//libs/armeabi/gdbserver /cygdrive/d/Deve
lop/Eclipse//libs/armeabi-v7a/gdbserver /cygdrive/d/Develop/Eclipse/
/libs/x86/gdbserver
rm -f /cygdrive/d/Develop/Eclipse//libs/armeabi/gdb.setup /cygdrive/d/Deve
lop/Eclipse//libs/armeabi-v7a/gdb.setup /cygdrive/d/Develop/Eclipse/
/libs/x86/gdb.setup
Compile thumb : test-jni <= test-jni.c
/cygdrive/c/bin/android-ndk-r6/toolchains/arm-linux-androideabi-4.4.3/prebuilt/w
indows/bin/arm-linux-androideabi-gcc -MMD -MP -MF D:/Develop/Eclipse//obj/
local/armeabi/objs/test-jni/test-jni.o.d.org -fpic -ffunction-sections -funwind-
tables -fstack-protector -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D
__ARM_ARCH_5TE__ -Wno-psabi -march=armv5te -mtune=xscale -msoft-float -mthumb -
Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -ID:/Develop/Ecli
pse//jni -DANDROID -Wa,--noexecstack -O2 -DNDEBUG -g -IC:/bin/android-ndk

  • r6/platforms/android-9/arch-arm/usr/include -c D:/Develop/Eclipse//jni/t

est-jni.c -o D:/Develop/Eclipse//obj/local/armeabi/objs/test-jni/test-jni.
o && ( if [ -f "D:/Develop/Eclipse//obj/local/armeabi/objs/test-jni/test-j
ni.o.d.org" ]; then awk -f /cygdrive/c/bin/android-ndk-r6/build/awk/convert-deps

i.o.d.org > D:/Develop/Eclipse//obj/local/armeabi/objs/test-jni/test-jni.o
.d && rm -f D:/Develop/Eclipse//obj/local/armeabi/objs/test-jni/test-jni.o
.d.org; fi )
SharedLibrary : libtest-jni.so
/cygdrive/c/bin/android-ndk-r6/toolchains/arm-linux-androideabi-4.4.3/prebuilt/w
indows/bin/arm-linux-androideabi-g++ -Wl,-soname,libtest-jni.so -shared --sysroo
t=C:/bin/android-ndk-r6/platforms/android-9/arch-arm D:/Develop/Eclipse//o
bj/local/armeabi/objs/test-jni/test-jni.o -Wl,--no-undefined -Wl,-z,noexecst
ack -lc -lm -lsupc++ -o D:/Develop/Eclipse//obj/local/armeabi/libtest-jni
.so
Install : libtest-jni.so => libs/armeabi/libtest-jni.so
mkdir -p /cygdrive/d/Develop/Eclipse//libs/armeabi
install -p /cygdrive/d/Develop/Eclipse//obj/local/armeabi/libtest-jni.so /
cygdrive/d/Develop/Eclipse//libs/armeabi/libtest-jni.so
/cygdrive/c/bin/android-ndk-r6/toolchains/arm-linux-androideabi-4.4.3/prebuilt/w
indows/bin/arm-linux-androideabi-strip --strip-unneeded D:/Develop/Eclipse//libs/armeabi/libtest-jni.so


で、ディレクトリを見てみると「libs」と「obj」が出来ているはずです。

[\\D:\Develop\Eclipse]
└ MyProject(+)
  └ jni(+)
    ├ Android.mk
    └ xxx_xxxx_xxxxx_xxxxxx.c
  └ res(+)
  └ src(+)
  └ AndroidManifest.xml
  └ libs(+)
    └ armeabi(+)
      └ libtest-jni.so
  └ obj(+)
(など。他のDir/Fileは省略)


これでEclipseを実行して、画面に「Hello from JNI !」と表示されれば成功です!


【エラー出力としたこと】
実際私が遭遇したエラーと、恐らくこんな対応したはず的な事を記載していきます
エラーが起きた際に参考にして貰えれば。

「ndk-build "make: *** No rule to make target" xxx」エラーが出る

 1. そもそもビルドしているパス合ってますか?Android.mkがある場所でndk-buidする必要があります。
 2. Android.mkの書式・記述にミスが無いか再度確認して下さい
 3. EclipseAndroid.mkを書き直して保存してみて下さい
 4. Cygwinを再起動してみてやり直してみて下さい

実際xxxのエラー追っても何も解決しませんでした。

Android NDK: Missing file: xxx/xxx/Android.mk !build/core/main.mk:86: *** Android NDK: Aborting . Stop.」エラーが出る

これは正直理由が良く分かってないですが
 1. EclipseAndroid.mkを書き直して保存してみて下さい
 2. sample以下のAndroid.mkを持ってきて書き直してみて下さい


こまったら「android-ndk-r6\samples」が例えばndk-build出来るか?とか設定ファイルとソースファイルをEclipseの下にコピーして、ndk-build出来るか?等を試してみると良いと思います。
環境なのか?記述なのか?の問題の切り離しが出来るので。
「hello-jni」とか「test-libstdc++」は簡単だし使いやすいです。


やっぱり結構記述だと思います。正直環境構築ってほとんどやってない感じじゃないですか。
(とは言え私もハマってのですが)


あと、「Application.mk」を用いた方法はまだ試してないです。
この場合変わるのかも知れません。機会があれば試して、エントリーします。


【その他参考にしたサイト】
Android NDKでJNIを使用してアプリを高速化するには
公開日誌かもしれない
Androieメモ
Mind The Robot

※すみません、勝手にリンク張りますが問題あればご指摘下さい。