Drowsy Dog's Diary

any note, any thought

2014年8月25日
by kazoo
0 comments

[android] APK Expansion Files(2)

前回の続き。Expansion Files のダウンロードについて。

アプリの起動時、あるべき Expansion files がそのパス(shared storage location の Android/obb/<package-name>/
)になかったらその時点で Google Play から DL してくる必要がある。アプリは Application Licensing service を使って対応するファイル名・サイズ・URL を取得し、ファイルを DL して適切なパスに保存を行わなければいけない。

前記事の通り、このための Downloader Library とそれを使ったサンプルが SDK に用意されている。また、Google Play にライセンスの Verification を行い URL をリクエストする Licensing サービスは以下に用意されている。

<ANDROID_SDK_DIR>/extras/google/play_licensing/

ルールと制限

  • Expansion File の上限はそれぞれ2GB
  • ユーザは Google Play からアプリを DL していなければならない。それ以外の方法でインストールされたアプリは Expansion File を DL できない
  • Google Play が提供する URL は、すべての DL に対してユニークであり、短時間で無効化される
  • Expansion Files の更新には、APK 本体の更新も必要
  • obb/ ディレクトリに他のデータを保存してはいけない
  • .obb ファイルを削除・リネームしてはいけない

Expansion Files のダウンロード

Licensing Verification Library (LVL)での認証処理に加えて、ダウンロードのための HTTP 接続とファイル保存のコードが必要になる。またそのために考慮すべき Issue は、

  • ストレージ容量が足りているかの確認
  • UI をブロックせず、またダウンロード途中でもユーザが離脱できるようにバックグラウンドで動作すること
  • ダウンロード中に起こる各種エラーへの対応
  • ネットワークの切断に対応し、また可能であれば途中からレジュームする
  • バックグラウンドでのダウンロード中、そのステータスと進捗をユーザに知らせること

Google 製の Downloader Library を使うことで、これらのタスクをすべて簡素化できる。開発者は、以下のライブラリをアプリに組込めば利用可能。

  • <ANDROID_SDK_DIR>/extras/google/play_licensing/
  • <ANDROID_SDK_DIR>/extras/google/play_apk_expansion/downloader_library/

Downloader Library が Licensing Library に依存する形になっているので、Eclipse などのプロジェクトに組込んで、Main となるプロジェクトのプロパティで前者のライブラリプロジェクトを追加してやればよいはず。

また、zip フォーマットを Expansion Files として使う場合は、APK Expansion Zip Library の使用も推奨されている。

パーミッション

Expansion Files のダウンロードには、Manifest に以下のパーミッションの宣言が必要。

ダウンローダの実装

Downloader Library は DownloaderService という Service のサブクラスを提供する。開発者はこれを継承して自前のダウンローダを実装する。DownloadService はまた、

  • BroadcastReceiver を登録し、必要なときにダウンロードを停止・再開するためにネットワーク接続状況を監視する
  • サービスが kill されたときのためにリトライ用のタイマーをスケジュールする
  • ダウンロード進捗あるいはエラーを表示するための Notification を持つ
  • マニュアルでダウンロードが pause/resume できる仕組みを与える
  • shared storage がマウントされ利用可能であること、ファイルがすでに存在しないこと、容量が足りることを確認して NG の場合は知らせてくれる

開発者は、DownloadService を継承したクラスを作り、以下の3つのメソッドを Override する。

ここで、BASE64_PUBLIC_KEY は、Developer Console で確認できる各開発者アカウントに対応する鍵に変更する。
また、getSALT() は、SharedPreferences に保存される License のデータを難読化するためのソルト値。これも別のランダムな値に更新しておく。
getAlarmReceiverClassName() は、ダウンロードが(何らかのエラー後に)再開したときのアラームを受け取るアプリ内の BroadcastReceiver を返すようにしておく。

追加したサービスは忘れずに Manifest にも追記しておく。

AlarmReceiver の実装

ダウンロードの進捗をモニターし、必要に応じて再開するために、DownloaderService は RTC_WAKEUP アラームを設定する。開発者は BroadcastReceiver を Downloader Library から呼べるように定義する。

ダウンロードの開始

前回記事に書いた Helper ライブラリを使ってExpansion Files の存在(無いこと)を確認したら、下記の static メソッドを呼んでダウンロードを開始する。

DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class serviceClass).

パラメータの意味はそれぞれ:

  • context: アプリケーションの Context
  • notificationClient: メイン Activity を開始するための PendingIntent。DownloaderService からの Notification で使用され、ダウンロードの進捗を表示する(通常はDL開始したものと同じ)Activity を開始するための PendingIntent を登録する
  • serviceClass: DownloaderService の Class オブジェクト。サービスを開始しダウンロードを始めるために必要

戻り値は以下のようになる。

  • NO_DOWNLOAD_REQUIRED: ダウンロード不要
  • LVL_CHECK_REQUIRED: Expansion Files の URL を得るためライセンスのベリフィケーションが必要
  • DOWNLOAD_REQUIRED: すでに URL が判明しておりダウンロードが開始可能

通常は LVL_CHECK_REQUIRED と DOWNLOAD_REQUIRED の違いを意識しない。Main の Activity から startDownloadServiceIfRequired() を呼び、単純に戻り値が NO_DOWNLOAD_REQUIRED かどうかをチェックすればよい。 NO_DOWNLOAD_REQUIRED 以外の何かが返ったときは、Downloader Library は ダウンロードを開始し、その進捗を表示するための Activity をセットアップする。

サンプルは以下の通り。

startDownloadServiceIfRequired() が NO_DOWNLOAD_REQUIRED 以外を返したときは、DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class downloaderService) を呼んで IStub インスタンスを生成する。IStub は Activity と DownloaderService をバインドする(進捗の通知などのため)仕組みを提供する。CreateStub() には、IDownloaderClient インターフェイスを実装した DownloadService の実装を渡す。

この IStub の生成は Activity の onCreate() で startDownloadServiceIfRequired() の後に行うことが推奨される。

先ほどのサンプルの続きは以下のようになる。

onCreate() を抜けた後、onResume() の中で IStub の connect() を呼ぶ。そして onStop() で disconnect() を呼んでやるとよい。

進捗・エラーステートの受信

Downloader Library にある、IDownloaderClient インターフェイスを使う。通常は、ダウンロードを開始した Activity にこれを実装する。

onServiceConnected(Messenger m) は、IStub を生成した後 DownloaderService との接続を通知してくれる。これを受けたら、pause/resume が送れるように DownloaderServiceMarshaller.CreateProxy() を呼んで、IDownloaderService のインターフェイスを取得しておく。

また、onDownloadStateChanged(int newState), onDownloadProgress(DownloadProgressInfo progress) で、それぞれ状態や進捗の変化を通知してくれる。

STATE_COMPLETED が返れば ダウンロード完了。必要に応じてダウンロードしたファイルのバリデーションなどを行い、Activity を抜ければよい(これも Downloader Sample にサンプルがある)。また、IDownloadService を取得した後は、それに対して requestPauseDownload()/requestContinueDownload() を呼ぶことで、手動で中断・再開ができる。

Expansion Files のテスト

Google Play に APK と Expansion Files を UP する前に、まず正しいパスから Expansion Files を読み出せるかをテストする。
たとえば、パッケージ名が com.example.android であれば、Shared Storage に Android/obb/com.example.android/ ディレクトリを作成し、$ adb push などでファイルを設置し、Google Play 上で扱われる正しいファイル名に .obb ファイルをリネームする。
ファイル名フォーマットは、main.0300110.com.example.android.obb のようになる。バージョンコードは何でもよいが、アプリがそれを正しく知っていることが必要。LVL は Expansion Files ではなく、あくまでも APK のバージョンコードに基づいて、Google Play 上のリソースを探しにゆく。ここでバージョンコードが正しくないと、”Download failed because the resources could not be found” というエラーになるので注意。

ダウンロードのテストは、Google Play の Beta バージョンとして Expansion Files をアップロードすれば、通常の APK 同様にベータテストユーザがダウンロードしてテストすることができる。