APK Expansion Files(OBBファイル)の使い方完全版 [Android/Kotlin]

# 概要
Google Playストアに公開できるAPKファイルのサイズは100MBまでという制限がある。100MBを超えた場合は、音声や動画などのリソースファイルを拡張ファイルとしてアップロードする必要がある。
拡張ファイルの形式は自由だが、obbと言う形式で圧縮してアップロードするのが無難である。obbファイルは暗号化付きディスクイメージファイルで、USBのようにマウント/アンマウントして使う。

実際の現場で使った所、WEB上にこれに関する情報があまりに少く非常に苦労したため、今回APK Expansion Filesの使い方を一通りここで纏める。

また、公式のドキュメンテーションは以下なので、合わせて参照してほしい。
[APK Expansion Files](https://developer.android.com/google/play/expansion-files#Downloading)

#I. 準備
### 1. OBBファイルの生成
まずリソースを.obbという拡張子で圧縮する。圧縮には、Android SDKに含まれるjobbコマンドを使用する。
jobbコマンドの場所は `[Android SDKルート]/tools/bin/jobb`。

```bash
jobb -d /path/to/directory  -pn com.foo.bar  -pv 1 -o /path/to/output/examople.obb
```

|オプション  |意味  |
|---|---|
|-d  |圧縮するディレクトリ  |
|-pn |アプリのパッケージ名 |
|-pv |このobbファイルを使用できる最低のversionCode|
|-o  |生成されるobbファイルの出力先|


なお、obbファイルではルートディレクトリに512個以上のファイルをおくことができない。それ以上のファイルを圧縮したいときは、`obbroot/data/様々なファイル`のような感じで適当なサブディレクトリを作り、その中に入れた方がいい。


# II. obbファイルの利用
### 1. obbファイルの配置
APKと一緒にobbファイルをGooglePlayストアで公開すると、アプリをダウンロードする際に一緒にダウンロードされて、所定のディレクトリに入る。開発段階では手動でこのディレクトリにobbファイルを配置しなければならない。
具体的な場所は、
`<Shared Storage>/Android/obb/<パッケージ名>/`
である。Android Studioを使ってるなら、Device File Explorerで、`/sdcard/Android/obb/<パッケージ名>/`に入れるといい。

また、GooglePlayストアに公開する際、obbファイルのファイル名は
`main.(versionCode).(package name).obb`
のようになるので、開発段階でもそのようなファイル名にしておくといいと思われる。
versionCodeの部分は、GooglePlayストアに公開した際のAPKのバージョンコードになるので、開発段階では適当に1とかにしておいていい。

具体的に言うと、versionCodeが1でパッケージ名がcom.foo.barなら、obbファイルを配置すべき場所は、
`/sdcard/Android/obb/com.foo.bar/main.1.com.foo.bar.obb` になる。

### 2. パーミッションの編集
当然なことだが、obbファイルを使う場合、アプリケーションに外部ファイル読み込みの権限を与えなければならない。

```AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
```

### 3. obbファイルをマウントする
obbファイルはディスクイメージの一種なのでマウントして使う。具体的には以下のように使う。

```hoge.kt
val storageManager = context.getSystemService(STORAGE_SERVICE) as (StorageManager)
storageManager.mountObb(obbFilePath, null, object : OnObbStateChangeListener() {
    override fun onObbStateChange(path: String, state: Int) {
        when (state) {
            ERROR_ALREADY_MOUNTED -> {
                Log.d("obb mount failed", "Already Mounted")
            }
            UNMOUNTED -> {
                Log.d("obb mount failed", "Unmounted")
            }
            MOUNTED -> {
                Log.d("obb mount successed", "Mounted")
            }
            else -> {
                // Error occurred!!
                Log.d("obb mount failed", state.toString())
            }
        }
    }

});
```


storageManager.mountObbメソッドは、
　第1引数に obbファイルのパス、
　第2引数に obbファイルのパスワード（obb生成時に指定していなければnull）
　第３引数にはリスナー（OnObbStateChangeListener）
を指定する。
先ほどの例で言うと第一引数`"/sdcard/Android/obb/com.foo.bar/main.1.com.foo.bar.obb"`になる。

OnObbStateChangeListener内では、マウントの成功/失敗に応じて処理を分岐できる。


### 4. マウントしたobbファイルを利用する
マウントに成功したobbファイルは通常のファイルと同じように扱うことができる。例えば、先ほどのソースコードのonObbStateChange関数内でマウントが成功した場合 (MOUNTEDの場合)に、以下のようなコードからobb内の特定のファイルをFileオブジェクトとして取得できる

```hoge.kt
val mountedObbPath = storageManager.getMountedObbPath(path)
val file = File(mountedObbPath + "/hogehoge.mp3")
```

storageManager.getMountedObbPathメソッドは第一引数にobbファイルへのパスを渡すことで、マウントされたobbファイルへアクセスするためのパスを返す。このパスを使うことで通常のファイルと同じようにファイルの読み込み/利用が可能になる。


# III. Obbファイルの公開

アプリケーションを公開する際、obbファイルはAPKファイルと一緒にGooglePlayストアにアップロードする必要がある。このアップロードの仕方が微妙にわかりづらい。
APKファイルをアップロードした後、バージョンコードなどが表示されている場所の右側に[＋]ボタンがある（画像参照）。そこをクリックするとアップロードするためのダイアログが表示される。
なお、obbファイルのファイル名に含まれるバージョンコードは、そのobbファイルを最初にアップロードした際の最新のAPKバージョンになる。

<img width="1350" alt="スクリーンショット 2019-03-20 23.27.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/28813/b61098b1-ab95-c416-540c-891586344ea7.png">


---
さらに何か疑問がある方は[こちら](https://twitter.com/singing_fool_)まで。