ICSのEmulatorとKernelを自前でビルドしたときの罠
Ice Cream Sandwichが対応するKernelのバージョンは3.0だ。なので、自前でkernelを用意する場合は、android-goldfish-3.0をダウンロード&ビルドすればいいはず。
しかし、android-4.0.3_r1をビルドし、その環境のEmulatorで使用すると、どうも動かない。起動に失敗しているようだ。(他のバージョンや、SDKのものでは試していない)
とりあえず周囲の方々の助力を得て無理やりデバッグして原因を探ってみた。するとどうやらlibc.soの初期化時にTLSレジスタのアドレスを取得するのだが、ここで落ちている。
goldfish-3.0は、TLSレジスタがあるものとしてビルドされていた(configの設定次第で変わるようだが、ARMv7に対応させるとこうなるようだ)。しかしEmulator側はTLSレジスタがないものとして、アドレス0xFFFF0FF0を直接参照していた。
この部分はTLSレジスタをサポートする場合とそうでない場合で切り分けられている。"ARCH_ARM_HAVE_TLS_REGISTER=true"とすれば、TLSレジスタを使用してくれる。…というか、device/samsung/crespo/BoardConfigCommon.mkやdevice/moto/wingray/BoardConfig.mkを見れば、ちゃんと指定されている。もっとちゃんと調べろよ俺…
generic(goldfish)環境のBoardConfig.mkはdevice/genericではなくbuild/target/board/genericにあるので、これに追加すればよい。またはmake時に直接指定してやればよい。
build/target/board/generic/BoardConfig.mkに追加する場合
ARCH_ARM_HAVE_TLS_REGISTER := true
直接指定する場合
$ make ARCH_ARM_HAVE_TLS_REGISTER=true -jN
これによって、無事Emulatorは起動した。この設定の影響を直接受けるのはbionic/libc, bionic/linkerのようだが、他にもあるかもしれない(そこまでは調査してない)。
なお、goldfish-3.0が古いと他にも問題が出てくるが、最新版を使うかパッチを適用すれば解決する。…するはず。未確認。
しかし、こんな既出っぽい件で随分手間をかけてしまった。世のAndroid開発者にとっては、こんなのは簡単な、躓くことのない部類なんですかねぇ?
NativeでUNIXドメインSocket
Nativeアプリケーションで、ローカルなネットワークを使用して簡単なデータ通知をしたかったのだが、なんとAndroidはメッセージキューをサポートしていない。
代用品として名前つきPIPEを使えばよかったのだが、せっかくなのでSocketを使用することにした。
しかしこれもまた曲者だった... 何故か標準的なLinuxと同じようにやっても、接続できない。
接続が拒否されたとか、そんなエラーコードが返される。
なんと、Androidでは専用の関数が用意されている。
socket(), connect, bind()などのAPIは直接使用せず、それらを使わねばならない。
- サーバー
#includeint connect(const char *name) { // socket(), setsockopt(), bind() などが行われる int socFd = socket_local_server(name, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); // 以後は標準的なサーバー側処理 ... return 0; }
- クライアント
#includeint connect(const char *name) { // socket(), connect() などが行われる int socFd = socket_local_client(name, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); // 以後は標準的なクライアント側処理 ... return 0; }
Google Testを使う
AndroidのNativeアプリやライブラリで、C++で書いたもののユニットテストはGoogle Testを使用することになる。
使用にあたって、STLのライブラリをリンクする必要がある。幸い、Android 2.2以降はSTLportがついている。
これをリンクすればいい… と思いきや、事はそう単純でもないようだ。
どうやら以前からastlというSTLもどきが存在するのだが、こいつをビルドするかどうかで設定を変えなければいけないらしい。
その辺の設定を行ってるAndroid.mkは、こんな感じになる。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests ifeq ($(BUILD_WITH_ASTL),true) libgtest_test_includes := bionic/libstdc++/include external/astl/include libgtest_test_static_lib := libgtest_main libgtest libastl libgtest_test_shared_lib := libgtest_test_host_static_lib := libgtest_main_host libgtest_host libastl_host libgtest_test_host_shared_lib := else BUILD_WITH_ASTL := false libgtest_test_includes := bionic external/stlport/stlport libgtest_test_static_lib := libgtest_main libgtest libgtest_test_shared_lib := libstlport libgtest_test_host_static_lib := libgtest_test_host_shared_lib := endif LOCAL_C_INCLUDES := external/gtest/include $(libgtest_test_includes) LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libandroid_runtime \ $(libgtest_test_shared_lib) \ $(libgtest_test_host_shared_lib) LOCAL_STATIC_LIBRARIES := \ $(libgtest_test_static_lib) \ $(libgtest_test_host_static_lib) LOCAL_SRC_FILES := \ sample.cpp LOCAL_MODULE := gtest_sample include $(BUILD_EXECUTABLE)
実装については、他のサイトで解説とかあるだろうし、ここでは割愛。
下記を参照すれば大抵わかるのではなかろうか。
http://opencv.jp/googletestdocs/samples.html
CUnitを組み込んでみる
NativeのアプリやライブラリのユニットテストをAndroid環境下でやりたい。と、何となく思い立ったので調査してみた。
Androidには標準でEmbedded Unitが含まれているから、意味はないのかもしれないが…でも調べてみた。
日本語ではほとんど資料が見つからなかったので、ここに記しておく。
といっても、まだmakeまでしかやってない。実際にテストアプリを作ってはいないので、本当にこの方法でいいのかは未調査。
※CppUnitの方は、何故かビルドできなくて困ってる。誰か助けて欲しい…
CUnitのソースをダウンロードする
SourceForgeからダウンロードする (現時点での最新版は 2.1-2)
http://sourceforge.net/projects/cunit/
Androidでビルドする
ここでは、外部ライブラリ"libcunit"としてコンパイルされるようにする。
- externalに"libcunit"ディレクトリを作成する
- ダウンロードしたファイルを展開し、CUnitディレクトリ内にあるHeaders, Sourcesディレクトリを丸ごとexternal/libcunitディレクトリにコピーする
- external/libcunitディレクトリに、Android.mkを作成する
Android.mkの中身は、下記のように書けばよし。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # the output modul name LOCAL_MODULE := libcunit # No prelinking of the lib LOCAL_PRELINK_MODULE := false # the src files that will be build in the lib LOCAL_SRC_FILES := \ Sources/Basic/Basic.c \ Sources/Framework/CUError.c \ Sources/Framework/MyMem.c \ Sources/Framework/TestDB.c \ Sources/Framework/TestRun.c \ Sources/Framework/Util.c \ Sources/Automated/Automated.c \ Sources/Console/Console.c # the header files LOCAL_C_INCLUDES :=$(LOCAL_PATH)/Headers # other shared libs that this lib will use LOCAL_SHARED_LIBRARIES := libcutils libc # add rules for building shared lib include $(BUILD_SHARED_LIBRARY)
以上でビルド可能になるはず。
Mediaフォーマット追加
例えば、AndroidのNativeに手を加え、MediaPlayerで再生できるフォーマットを増やしたとする。
しかしそのままでは、その新しいフォーマットはMediaフォーマットとしては認識されず、データベースに登録してくれない。
Mediaフォーマットの追加をするためには、frameworks/base/media/java/android/media/MediaFile.javaを修正する。
36行目あたりから、Audio file types, MIDI file typesと定義している箇所に、任意のフォーマットを定義する。
値は必ず連番になるように指定し、FIRST_XXXX_FILE_TYPEとLAST_XXXX_FILE_TYPEの範囲に入るように変更する。
(例)着信メロディ(SMAF)を追加
public static final int FILE_TYPE_MID = 11; public static final int FILE_TYPE_SMF = 12; public static final int FILE_TYPE_IMY = 13; public static final int FILE_TYPE_SMAF = 14; // 追加! private static final int FIRST_MIDI_FILE_TYPE = FILE_TYPE_MID; private static final int LAST_MIDI_FILE_TYPE = FILE_TYPE_SMAF; // SMAFを範囲内に!
98行目あたりから、フォーマットの拡張子とMIME-TYPEの定義をしている。
追加したフォーマットについても、同じように定義する。
static { ... addFileType("MMF", FILE_TYPE_SMAF, "application/vnd.smaf"); // 追加! ... }