閉じる

APK Expansion Files

obbファイルを使った時のメモ。
ヘルパーライブラリ
「Google Pla APK Expansion Library」「Google PlayLicensing Library」の二つを導入。
どちらもライブラリ部分*1 だけ eclipseにインポートして前者が後者に依存するように設定。
アプリケーションは前者に依存するように設定。
拡張ファイルの名前と場所
名前は Helpers.getExpansionAPKFileName(…)を使う。
場所は Helpers.generateSaveFileName(…)を使う。
手動アップロード
テスト環境や野良アプリの場合は手動でアップロードする必要がある。
作成した .obbファイルを前述のパスに置けば普通に動く。
monitor.batから File Explorerを使うのが手っ取り早いけど、野良アプリを蒔くのならアプリ自体にダウンロード機能を付けるのもありかと。
.obbの作成
システムは内容について関知しないのでマウントして使うのでなければ単一の jpegだろうが、zipアーカイブだろうが好きに作ればよろし。*2
マウントして使う場合、Windowsの場合は Android SDKに含まれる jobb.batを使う。*3
自分は Linux上で以下のようにして作成している。

# latest_dir : 最新のディレクトリ構造
# main_dir : メイン拡張 APKのソース
# patch_dir : パッチ拡張 APKのソース

# メインのデータを用意
$ rsync -a latest_dir/* main_dir/
# メイン拡張 APKの作成
$ ~/android-sdk-linux/tools/jobb -pn PACKAGE -pv 1 -d main_dir -o main.X.PACKAGE.obb

# latest_dirが更新されたらパッチデータを作成
$ rsync -a --link-dest=../main_dir/ latest_dir/* patch_dir/
# メインファイルに存在するファイルを除去
$ find patch_dir -type f -links 2 -exec rm {} \;
# パッチファイルの作成
$ ~/android-sdk-linux/tools/jobb -pn PACKAGE -pv 1 -ov -d patch_dir -o patch.X.PACKAGE.obb

ERROR_PERMISSION_DENIED
obb作成時に指定したパッケージ名と StorageManager.mountObbを実行しているパッケージ名が異なる時に返る。
当初、わざわざ別の名前を付けていたので悩んだ。
ERROR_INTERNAL
StorageManager.mountObb(Helpers.getExpansionAPKFileName(…),…)のように拡張ファイルをフルパスで指定しないと返る。
マウントされるタイミング
StorageManager.mountObb(…)は実行をブロックしないので注意。
ライブラリ
署名した apkを作ろうとするとヘルパーライブラリの翻訳が未完というエラーになってしまう。
まじめに翻訳しても良いのだけど、気にならないのならプロジェクトから外して、play_apk_expansion/downloader_library/bin/downloader_library.jar , play_licensing/library/bin/library.jarをアプリケーションの libs/ディレクトリに突っ込むだけにしても大丈夫。
ヘルパーライブラリ内にかなりの数でる警告も黙るメリットもある。
アップロード
拡張 APKは新しい APKをアップロードするタイミングでしかアップロードできないので一緒にアップロードすること。
メインの拡張 APKをアップロードするとパッチ用の拡張 APKがアップロードできるようになる。
バージョン番号
拡張 APKのバージョン番号(「main.X.YYYY.obb」のX)は一緒にアップロードした標準 APKの物が使われる。
Helpers.getExpansionAPKFileNameに指定するのも Xにあたる番号になる。
.obb作成時のエラー
以下のようなエラーが出ることがある。
文面からするとサイズが問題らしいのだけど、1Gを超すディレクトリでは出なく、1500個程度のファイルを含むトータル 8M程度のディレクトリで出る。

Exception in thread "main" java.lang.IllegalArgumentException: disk too small for FAT16
at de.waldheinz.fs.fat.SuperFloppyFormatter.sectorsPerCluster16FromSize(SuperFloppyFormatter.java:409)
at de.waldheinz.fs.fat.SuperFloppyFormatter.clusterSizeFromSize(SuperFloppyFormatter.java:317)
at com.android.jobb.Main.main(Main.java:379)

ファイルを削除していって、7Mを切った当たりで以下の文面に変わる。

Exception in thread "main" java.lang.IllegalArgumentException: disk too large for FAT12
at de.waldheinz.fs.fat.SuperFloppyFormatter.sectorsPerCluster12(SuperFloppyFormatter.java:461)
at de.waldheinz.fs.fat.SuperFloppyFormatter.clusterSizeFromSize(SuperFloppyFormatter.java:315)
at com.android.jobb.Main.main(Main.java:379)

更に削除していって、2M辺りでエラーが出なくなる。
30M程度だとエラーがないのでそこから減らしてゆくと、20Mを切った辺りで以下のエラーが出る。

java.io.IOException: FAT Full (8359, 8360)
at de.waldheinz.fs.fat.Fat.allocNew(Fat.java:298)
at de.waldheinz.fs.fat.Fat.allocAppend(Fat.java:376)
at de.waldheinz.fs.fat.Fat.allocNew(Fat.java:353)
at de.waldheinz.fs.fat.ClusterChain.setChainLength(ClusterChain.java:164)
at de.waldheinz.fs.fat.ClusterChain.setSize(ClusterChain.java:132)
at de.waldheinz.fs.fat.FatFile.setLength(FatFile.java:91)
at de.waldheinz.fs.fat.FatFile.write(FatFile.java:154)
at com.android.jobb.Main$1.processFile(Main.java:495)
at com.android.jobb.Main.processAllFiles(Main.java:604)
at com.android.jobb.Main.processAllFiles(Main.java:600)
at com.android.jobb.Main.processAllFiles(Main.java:600)
at com.android.jobb.Main.main(Main.java:417)
Exception in thread "main" java.lang.RuntimeException: Error getting/writing file with name: s_4918.jpg
at com.android.jobb.Main$1.processFile(Main.java:501)
at com.android.jobb.Main.processAllFiles(Main.java:604)
at com.android.jobb.Main.processAllFiles(Main.java:600)
at com.android.jobb.Main.processAllFiles(Main.java:600)
at com.android.jobb.Main.main(Main.java:417)

15Mだと大丈夫。
どうやら、「容量を見てフォーマットを変更しているけど内容によってはおかしな判断になってしまう」「内部で使っている FATライブラリにおいて容量の際で挙動がおかしい」辺りかね。
ファイルシステムを強制的に指定するようなことが出来れば回避できそうだけど、そういうオプションはなさげ。
結局、ダミーのファイルを作ってサイズ調整すて回避することに。
パッチファイルについて
パッチファイル作成時、jobbコマンド -ovオプションを使うと具体的に何が起こるのかはよくわからない。
で、名前こそ「パッチ」とはなっているけど基本的にシステムは何もやってくれないようだ。
まぁ ファイル名は固定されるとはいえ、中身は何でも良いと言うことらしいのでどうしようもないのだろうけど。
Googleが提供している ZIPファイルを使ったサポートライブラリにはちゃんとパッチとして扱う仕組みがあるようだけど、単純に .obbとして使う場合はメインとパッチの両方をマウントして真面目にやるしかないみたい。
削除
アプリケーション更新時、不要になった .obbは自動で削除してくれる。
playを使ってインストールしていない場合は手動だろうね。
ダウンロード
本当はダウンロード失敗に備えて自前でのダウンロード機構を用意する必要があるのだけど、いろいろと権限を要求する*4 上にかなり面倒。
ケアできる範囲にしか配布しないのであればオミットしてしまっても問題はない。
そもそも日本みたいに「周り全てがハイエンド環境」みたいな環境ならダウンロードに失敗するようなこともないだろうしね。*5
マウントするタイミング
マウント処理が非同期なのでソレを許容できる適当な箇所で。
自分はアプリ起動時に専用の Activityでマウントし、マウント完了したらメインの Activityを起動して自身を終了するようにしている。
アンマウントするタイミング
ドキュメントによるとマウントポイントは .obb毎にユニーク*6 らしく、基本的に更新された .obbをマウントする時にマウントポイントがぶつかって失敗することはないはず。
初めはタイミングを試行錯誤したけど、結局はアンマウントしないという選択に。
ちなみに、USBストレージをonにすると問答無用でアンマウントされる。
offにした時に再マウントされるなんて便利機能はないのでアプリケーションで対応する必要がある。
通常、onにする時に「アプリケーションが異常な動作をするかも」的なメッセージが出るので「うん。異常な動作になっちゃった。てへっ」と気にしないことにした。
アプリケーションが落ちず、画像が表示されない等の実害がない範囲ならソレで良いかなと。

2013/11/23追記

Android 4.4(KRT16S)では「Image loop device creation failed (Permission denied)」というエラーになってマウントに失敗する。

回避方法は今のところ存在しない?< Issue 61881: Cannot mount Obb on Android KitKat 4.4

とりあえずログをメモっておく

11-23 22:33:36.329: W/MountService(492): Failed to find OBB mounted at /storage/emulated/0/Android/obb/PACKAGE/main.13.PACKAGE.obb
11-23 22:33:36.339: W/ContextImpl(492): Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1543 com.android.server.MountService$ObbActionHandler.connectToService:2432 com.android.server.MountService$ObbActionHandler.handleMessage:2311 android.os.Handler.dispatchMessage:102 android.os.Looper.loop:137
11-23 22:33:36.389: E/Vold(118): Unable to open /mnt/shell/emulated/obb/PACKAGE/main.13.PACKAGE.obb (Permission denied)
11-23 22:33:36.389: E/Vold(118): Image loop device creation failed (Permission denied)
11-23 22:33:36.389: W/Vold(118): Returning OperationFailed - no handler for errno 13

かなり致命的に思えるのにあまり話題になっていないのは OBBファイルとして使っている例が少ないのかね?
ZIPなり何なりとして使う分には問題ないわけだし。

2013/12/10追記

自分はすっとばして 4.4.2になってしまったけど、4.4.1で修正されたようね。

2015/02/06追記

4.4シリーズのうち、4.4.3のみ READ_EXTERNAL_STORAGEが厳格に適用されているのかな?
obbディレクトリはアプリケーションのディレクトリ扱いにはならないようで、obbファイルの存在確認が出来なかった。
今となっては環境がないので確認できないのだけど、4.4.2までと 4.4.4は大丈夫だったと思うのだが。

2015/03/10追記

どうも、jobbで作成した .obbはサイズの小さなファイルの扱いが微妙な感じ。
読めなかったり、読めてもファイルが壊れていたりすることがある。
Androidのバージョンにも依存するっぽいのだけど、調べ切れていない。


*1 play_apk_expansion/downloader_library , play_licensing/library

*2 マウントして使うと通常のファイルと同じ処理が出来るのが利点となり、.obbへのアクセスが自分のコードだけで完結しない場合はそちらの方が無難。

*3 ソースとなるディレクトリとターゲットファイルはフルパスで指定する必要がある点に注意

*4 playからダウンロードする分にはシステムがやってくれるのでアプリ自身に権限は不要なのよね。

*5 メインの拡張ファイルをダウンロードするには事前に古いファイルを削除する必要があるような環境は周囲で自分だけだわ。

*6 ユニークなマウントポイントを設定する絡みからか、同じバージョンで異なる .obbをインストールするとすると酷いことになると言う情報もある。

コメントを残す

メールアドレスが公開されることはありません。必須項目には印がついています *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)