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は直接使用せず、それらを使わねばならない。

  • サーバー
#include 

int connect(const char *name)
{
    // socket(), setsockopt(), bind() などが行われる
    int socFd = socket_local_server(name, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM);

    // 以後は標準的なサーバー側処理
    ...

    return 0;
}
  • クライアント
#include 

int 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"としてコンパイルされるようにする。

  1. externalに"libcunit"ディレクトリを作成する
  2. ダウンロードしたファイルを展開し、CUnitディレクトリ内にあるHeaders, Sourcesディレクトリを丸ごとexternal/libcunitディレクトリにコピーする
  3. 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)

以上でビルド可能になるはず。

CUnitを使用する

CUnitを使用したいアプリのAndroid.mkでインクルードディレクトリとライブラリの指定を行えばよい。

LOCAL_C_INCLUDES += external/libcunit/Headers
LOCAL_SHARED_LIBRARIES += libcunit

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");    // 追加!
        ...
    }

1.1 r1 と 1.5 の相違点

とりあえず気づいたのは、このくらい。

  • エミュレータはAVDを使う
  • R.javaやaidlの自動出力ソースはsrcディレクトリと同階層のgenディレクトリに出力される
  • ActivityInstrumentaionTestCase, ProviderTestCaseクラスが使えなくなり、代わりにそれぞれ同2というクラスが用意されている。

他にも沢山ありそうだが、調べてないので何ともかんとも。

Android 1.5 SDK エミュレータの起動

エミュレータ起動時に作成したAVDデータを指定する。また、エミュレータのコマンドを使用して、スキンやSDカードイメージなどの指定を変えることが可能。
エミュレータの起動例は以下の通り。@xxxxxでAVDを指定する。


$ emulator @sample1 -sdcard ~/sdcard/sdcard.img
$ emulator @sample2 -skin HVGA-L