monthly gimite

試験運用中。

AndroidでC言語で書いたネイティブアプリを動かしてみる

以下の説明はAndroidのroot権限が取れる環境(DevPhoneやエミュレータなど)を前提に書かれています。市販のAndroid携帯など、root権限が取れない環境では、AndroidのJavaアプリ上からインストール/実行する方法を使えば実行できます。

Androidのシェルを動かす

Androidのには、adb shellというコマンドでログインしてシェルを動かすことができます。(Android SDKをC:\android_sdkにインストールしたとすると)まずC:\android_sdk\tools\emulator.exeを起動しておいてから、

> C:\android_sdk\tools\adb shell
# ls
ls
drwxr-xr-x root     root              1970-01-01 00:00 var
drwxrwxrwt root     root              2007-11-17 06:18 tmp
drwxr-xr-x root     root              2007-11-11 20:59 system
...

という感じ。かなりコマンドが少ないですが、ちゃんとLinuxです。なんかエラーが出た場合は、ちょっと待ってからもう一度実行すればいいみたいです。adb pushでファイルの転送もできます。詳しくはこのへん。注意点として、/data以外のところに置いたファイルは、エミュレータを終了すると消えてしまいます。

# cat /proc/cpuinfo
cat /proc/cpuinfo
Processor       : ARM926EJ-S rev 5 (v5l)
BogoMIPS        : 182.27
Features        : swp half thumb fastmult vfp edsp java
...

CPUはARMらしいので*1、ARM用の実行ファイルを作ってつっこめば動くだろう、ということでやってみました。

Scratchboxのインストール

ロスコンパイルとかよく分からないので適当にぐぐったらScratchboxというのを使う方法が出てきたので、これでやってみました。Scratchboxを動かす環境はDebian*2

Debianのパッケージがあったのでそこから以下のファイルをダウンロードしてインストール*3

# dpkg -i scratchbox-core_1.0.8_i386.deb
# dpkg -i scratchbox-libs_1.0.8_i386.deb
# dpkg -i scratchbox-toolchain-arm-linux-2006q3-27_1.0.6-1_i386.deb
# dpkg -i scratchbox-devkit-cputransp_1.0.3_i386.deb
# dpkg -i scratchbox-devkit-debian_1.0.9_i386.deb
# dpkg -i scratchbox-devkit-doctools_1.0.7_i386.deb
# dpkg -i scratchbox-devkit-perl_1.0.4_i386.deb

あとはリンク先の手順のとおりに。ただ/scratchbox/loginで

$ /scratchbox/login
Host kernel has port range under 10000.
This causes problems with fakeroot.
You can fix this eg. by running:
  echo "1024 65000" > /proc/sys/net/ipv4/ip_local_port_range

とかいうエラーに。しかも

$ sudo echo "1024 65000" > /proc/sys/net/ipv4/ip_local_port_range

ではだめ(ファイルへの書き込みがroot権限にならない)という罠にかかりました。suでrootになってから上のコマンドを実行すればOK。

Hello worldを実行してみる

Scratchboxの設定が終われば、Scratchboxの中で普通にコンパイルするだけです。ここでは簡単のためにスタティックリンクします。ダイナミックリンクする方法はこちらを参照してください(arm-none-…の代わりに普通のgccやldを使えば、Scratchboxでも同様の手順で可能です)。*4

$ /scratchbox/login
[sbox-arm: ~] > gcc -static -o arm-hello hello.c
[sbox-arm: ~] > file arm-hello
arm-hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.6.14, statically linked, not stripped

あとはこれをエミュレータに転送して実行。

> C:\android_sdk\tools\adb push arm-hello /tmp
> C:\android_sdk\tools\adb shell
# chmod 755 /tmp/arm-hello
# /tmp/arm-hello
/tmp/arm-hello
Hello

おお。

Rubyを動かしてみる

調子に乗ってRubyも動かしてみました。

$ /scratchbox/login
[sbox-arm: ~] > tar xvjf ruby-1.8.6-p111.tar.bz2
[sbox-arm: ~] > cd ruby-1.8.6-p111
[sbox-arm: ~/ruby-1.8.6-p111] > LDFLAGS='-static' ./configure --with-static-linked-ext
[sbox-arm: ~/ruby-1.8.6-p111] > make
[sbox-arm: ~/ruby-1.8.6-p111] > file ruby
ruby: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.6.14, statically linked, not stripped

以下Windowsで。

> C:\android_sdk\tools\adb push ruby /tmp
> C:\android_sdk\tools\adb shell
# chmod 755 /tmp/ruby
# /tmp/ruby -e'puts "Hello Android."'
/tmp/ruby -e'puts "Hello Android."'
Hello Android.

おお。なぜかアドレス解決ができないんですが*5、ソケットも使えます。

# /tmp/ruby -r socket -e'p IPSocket.getaddress("www.google.co.jp")'
/tmp/ruby -r socket -e'p IPSocket.getaddress("www.google.co.jp")'
-e:1:in `getaddress': getaddrinfo: Name or service not known (SocketError)
        from -e:1

# /tmp/ruby -r socket -e'sock= TCPSocket.open("66.249.89.104", 80); sock.write("
GET / HTTP/1.0\n\n"); sock.flush; sock.each_line(){ |s| puts s }'
/tmp/ruby -r socket -e'sock= TCPSocket.open("66.249.89.104", 80); sock.write("GE
T / HTTP/1.0\n\n"); sock.flush; sock.each_line(){ |s| puts s }'
HTTP/1.0 302 Found
Location: http://www.google.co.jp/
Cache-Control: private
Set-Cookie: PREF=ID=bb787e2dce340091:TM=1195281862:LM=1195281862:S=0WHJFGw3n2TNe
ovm; expires=Mon, 16-Nov-2009 06:44:22 GMT; path=/; domain=.google.com
Content-Type: text/html
Server: gws
Content-Length: 221
Date: Sat, 17 Nov 2007 06:44:22 GMT
Connection: Close

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/">here</A>.
</BODY></HTML>

ちゃんと動かすなら、ライブラリとかもコピーしないとだめですけどね。

課題とか

実機にはコンソールなんてないので、コンソールアプリが動いてもしょうがないわけで、なんかの方法で普通のAndroidアプリと通信する必要がありますね。パイプとかソケットとかでしょうか。まあそもそも市販される実機でネイティブアプリの実行ができるのかはかなり怪しいですけど…。*6

当然こんなことはみんな思いつくので既にやられてるわけですが、このスレッドではJNIが使えないかという話になってるみたいです。

2009/1/11追記: Android MarketにちゃんとTerminal Emulatorがあるので、それを使って実機上で直接動かせました。また、Javaアプリ上からUNIXコマンドを呼び出すことも可能です。また、ダイナミックリンクについての情報を本文中に追記しました。

2010/7/10追記: root権限がない環境についての情報を冒頭に追加しました。

*1:Android自体はARM以外にも対応してると思いますが、このエミュレータはARMをエミュレートしてる、ということです。

*2:ScratchboxはLinuxでしか動かないっぽいので。あとでもっと普通そうでWindowsでも動くらしいクロスコンパイラを発見。

*3:devkitなんとかってのはそれ以外よりあとでインストールする必要があるようです。

*4:ダイナミックリンクしたほうが、バイナリサイズはずっと小さくなります。

*5:/etc/resolv.confがないせい?でも実機だと/etc以下って書き込めないんですよね。chrootするしかない?

*6:あとはそもそも日本で使える実機はいつ出るのか、とか。