仙石浩明の日記

2010年10月25日

Android 端末 (Nexus One) でアプリを SDカードの ext3 パーティションにインストールする (Apps 2 SD)

新しい Android 端末が次から次へと発表される今日このごろ、 1月6日に発表された Nexus One は (まだ一年たっていないのに) 旧型機といった感じになりつつある。 特に端末内部メモリ容量が 200MB しかないのは致命的。 正確に言えば内蔵フラッシュメモリは 512MB だが、 システム側で 300MB ほど使うので、 ユーザが自由に使えるのは残り 200MB 程度となる (もちろんそれとは別に SD カードが使える)。

いまどき 200MB というのはいかにも少ない。 例えば私が Nexus One で使ってるアプリのうち、 サイズの大きいものを一部ピックアップしてみると、

アプリappdata合計
Google Earth20.32MB0.24MB20.56MB
Twitter2.70MB14.57MB17.27MB
Skype10.68MB3.40MB14.08MB
OpenWnn Flick対応版7.68MB4.78MB12.46MB
Adobe Flash Player12.40MB0.00MB12.40MB
Google Map8.09MB1.44MB9.52MB
Graffiti5.14MB0.84MB5.22MB
K-9 Mail2.67MB1.07MB3.74MB

わずか 8個のアプリだけで 100MB 近く使っている。 200MB に収めようと思えば、 インストールするアプリを相当厳選する必要がある (100個程度で限界)。

さすがにこれでは使い物にならないということで、 アプリを SDカード上にインストールできるようにする仕掛け Apps 2 SD (A2SD) が提案されてきた。 A2SD には App2sd など、 いろんな実装があるが、 基本的には SDカードに ext3 (あるいは ext4) パーティションを切り、 /system/ext あたりにマウントして (実はこれが難しい, 後述)、 /data/app などからシンボリックリンクを張る。

つまり、こんな感じ:

# cat /proc/mounts
	...
/dev/block/mtdblock3 /system yaffs2 ro,relatime 0 0
/dev/block/mtdblock5 /data yaffs2 rw,nosuid,nodev,relatime 0 0
	...
/dev/block/mmcblk0p2 /system/ext ext3 rw,nosuid,nodev,relatime,errors=continue,data=writeback 0 0
/dev/block/vold/179:1 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/block/vold/179:1 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
	...
# cd /data
# ls -l
drwxrwxr-x    1 system   system        2048 Oct 23 19:33 anr
lrwxrwxrwx    1 root     root            15 Oct 23 20:45 app -> /system/ext/app
lrwxrwxrwx    1 root     root            23 Oct 23 20:46 app-private -> /system/ext/app-private
lrwxrwxrwx    1 root     root            18 Oct 23 20:47 backup -> /system/ext/backup
lrwxrwxrwx    1 root     root            24 Oct 24 11:23 dalvik-cache -> /system/ext/dalvik-cache
drwxrwx--x    1 system   system        2048 Oct 24 11:59 data
drwxr-x---    1 root     log           2048 May 13 08:47 dontpanic
lrwxrwxrwx    1 root     root            17 Oct 23 20:02 local -> /system/ext/local
drwxrwx---    1 root     root          2048 May 13 08:47 lost+found
drwxrwx--t    1 system   misc          2048 Oct 24 14:02 misc
drwx------    1 root     root          2048 Oct 23 19:33 property
drwxrwxr-x    1 system   system        2048 Oct 24 14:20 system
lrwxrwxrwx    1 root     root            22 Oct 23 21:11 tombstones -> /system/ext/tombstones

アプリを Android 端末にインストールすると、 パッケージファイル (*.apk) が /data/app ディレクトリに格納 (「コピー防止」 アプリは /data/app-private ディレクトリに格納) され、 クラスファイル (classes.dex) が /data/dalvik-cache ディレクトリに格納される。 上記のように /data/app および /data/dalvik-cache はそれぞれ /system/ext/app および /system/ext/dalvik-cache へのシンボリックリンクにしてあるので、 内蔵フラッシュメモリ (/data パーティション) の代わりに SDカード (/system/ext パーティション) へ格納される、という仕掛け。

/data/data ディレクトリにはアプリが使用するデータが格納される。 /data/app および /data/dalvik-cache が、 アプリのインストール/アンインストール時のみ書き込みが行なわれるのに対し、 /data/data はアプリ動作中も読み書きが行なわれるわけで、 /data/data を SDカードへ移すとアプリの実行速度が低下したり、 あるいは頻繁な書込みによって SDカードの寿命が短くなったりする恐れがある。 もし /data/data も SD カード上に置こうとするなら、 使っている SDカードが充分高速 (Class6 以上?) で、 かつウェアレベリング (wear leveling) をサポートしているか確認したほうが無難。

私は /data/data は /system/ext へのシンボリックリンクを張らずに /data パーティションをそのまま使用している。 現時点での各パーティションの空き容量は次のような感じ:

# df
/dev:               197552K total,       0K used,  197552K available (block size 4096)
/mnt/asec:          197552K total,       0K used,  197552K available (block size 4096)
/system:            148480K total,  127580K used,   20900K available (block size 4096)
/data:              200960K total,  102148K used,   98812K available (block size 4096)
/cache:              97280K total,    9128K used,   88152K available (block size 4096)
/system/ext:       3690832K total,  269908K used, 3420924K available (block size 4096)
/mnt/sdcard:      11547432K total, 6808720K used, 4738712K available (block size 8192)
/mnt/secure/asec: 11547432K total, 6808720K used, 4738712K available (block size 8192)

私が使ってる SD カードは 16GB Class6 だが、 /data パーティションはまだ 99MB の空きがあるので /data/data を無理に /system/ext へ移動させることもないと判断した次第。

なお、Android 2.2 froyo から OS 標準で、 アプリを SD カードへ移動できるようになった。 しかし対応アプリでないと移動できないし、 現時点では多くのアプリが対応していない。 さらに 「USBストレージをON にする」 と SD カードへ移動したアプリは USB ストレージが ON の間は使えなくなるので注意が必要。

というわけで、 アプリを SDカード上にインストールできるようにするのは、 シンボリックリンクを /data から /system/ext へ張るだけのことなので全く難しくない。 しかしながら、 どうやって SDカード上の ext3 パーティションをマウントするか、 という問題がまだ残っている。

ふつうの Linux では /etc/fstab に書いておけば OS が勝手にマウントしてくれる。 しかしながら標準の Android に /etc/fstab は無い (正確に言えば、起動時に 「mount -a」 が実行されない)。 また、 Linux では起動スクリプトを /etc/rc.d 以下に置くのが一般的だが、 これも Android には無い。 なぜなら Android では起動シーケンスが initramfs のみで完結してしまっているから。

ふつうの Linux だと initramfs から /sbin/init へ制御が引き継がれて、 /sbin/init が /etc/inittab の設定にしたがって /etc/rc.d 以下の起動スクリプトを実行するが、 Android では initramfs 内の /init が直接、デーモン等を起動する。 だから起動スクリプトに相当するものは /init の設定ファイル /init.rc だけで、 これも initramfs 内にある。

したがって Android の起動シーケンスで何か他のこと (SDカード上の ext3 パーティションをマウントするとか) をやらせようと思ったら、 initramfs 内をいじらなければならず、 これはつまり boot.img (Android のカーネルイメージ) をいじることを意味する。 boot.img から initramfs を取り出す方法と boot.img を再構成する方法が公開されているので、 /init.rc を書き換えることも難しくはない。

しかし boot.img を書き換えてしまうとシステムアップデートするとき面倒くさい。 boot.img を書き換えるくらいなら、 カスタムROM を入れてしまえばいいわけで、 そうすれば最初から SDカード上の ext3 パーティションがサポートされているので簡単。 実際、 Apps 2 SD を実現する方法の多くはカスタムROM を前提としているが、 起動時にマウントしたいだけなのに ROM を入れ替えるというのはいかにも牛刀。

何とか boot.img に手をつけず必要最小限の変更で SDカード上の ext3 パーティションをマウントする方法はないものかと、 /init のソースと /init.rc を眺めていたら、 /init.rc に以下の記述を見つけた:

service flash_recovery /system/etc/install-recovery.sh
    oneshot

これは、 起動時に一度だけ (oneshot) 「/system/etc/install-recovery.sh」 を実行することを意味する。 おお、 これは 「起動スクリプト」 として流用できそう!

/system/etc/install-recovery.sh は、 以下のような内容で、 本来の目的はリカバリイメージのバージョンアップ。 つまり現在のリカバリイメージのハッシュ値が期待した値と異なれば、 /system/recovery-from-boot.p で上書きする処理。

#!/system/bin/sh
if ! applypatch -c MTD:recovery:2048:9f01821e89ca4cbac43892a7e3cf12f0d9329c6a; then
  log -t recovery "Installing new recovery image"
  applypatch MTD:boot:2394112:33377a035edcb141d49f9a604bf4223f79140132 MTD:recovery bf4a8cd2ebc210f227e60a4538ca59f66b1ba41b 2625536 33377a035edcb141d49f9a604bf4223f79140132:/system/recovery-from-boot.p
else
  log -t recovery "Recovery image already installed"
fi

この sh スクリプトの冒頭に以下のように 2行書き加えて SDカード上の ext3 パーティション /dev/block/mmcblk0p2 を /system/ext へマウントするようにすればよい。

#!/system/bin/sh
mount -t ext3 -onosuid,nodev /dev/block/mmcblk0p2 /system/ext
exit 0

if ! applypatch -c MTD:recovery:2048:9f01821e89ca4cbac43892a7e3cf12f0d9329c6a; then
	...

「exit 0」 を書き加えているのは、 以下の install-recovery.sh スクリプト本来の処理をスキップするため (もちろん以下の処理を消しちゃってもいい)。 いったん新しいリカバリイメージをインストールしてしまったら無用であるし、 recovery-RAClockworkMod Recovery など、 代替リカバリを利用する場合は、 標準リカバリに書き戻されないようにするために、 このスクリプト本来の処理をスキップすることが必須。

Filed under: Android — hiroaki_sengoku @ 09:56

3 Comments »

  1. すばらしいソリューションです。
    さっそく試してみました。きれいにアプリがSDに移行しました。
    しかしandroidのcommandって癖が強いですね。
    rm -rf hogeってやると-rfは知らないと言われました。
    結局rooted explorerのお世話になりました。

    まずは御礼まで。

    Comment by wittmann — 2010年11月1日 @ 12:22

  2. Nexus One 正規ROMですが、何の問題もなく移動できました。移動に際して、2,3のアプリが強制終了したりしましたがそれだけでした。稼働中のシステム上でこのように簡単に移動できてしまうというのがちょっと驚きではあります。

    Comment by forzando — 2010年11月8日 @ 19:58

  3. このソリューションを使って幸せにつかっておりましたが、ある日突然sdのext3部分を認識しなくなりました。ひょっとしたらext3をうまくumountせずに終了したせいかもしれません。
    system終了時にうまくumountする方法はないでしょうか?

    Comment by wittmann — 2011年3月5日 @ 13:07

RSS feed for comments on this post.

Leave a comment