AndroidでOkhttpとWebview間でCookie情報を共有するための実装例

Androidアプリで、HTTPクライアントにはOkhttpを使用することが多いですが、OkhttpとWebViewのCookie情報を共有したい時にどうするか悩むことがあるかと思います。

そこで、私が実践している実装例を書いて共有してみようと思います。

## Okhttp/WebViewで共有するCookieを保持するクラスの作成
OkhttpにてHTTPリクエスト時に行うCookie情報の設定は、OkHttpClientにCookiejarインタフェースを実装したクラスを指定する必要があります。（詳細はWebに転がっているので割愛）


この時、以下のようなクラスを作っています。

まず、Okhttp/WebViewのHTTP通信で共通で使用するCookie情報を保持するインタフェースを定義し、その実装クラスを作成します。

```kotlin:CookieStore.kt
/*
ネイティブ/WebViewのHTTP通信で共通で使用するCookie情報を保持
(実際はその他のメソッドも定義していますが長くなるので割愛）
*/
interface CookieStore {
    fun getCookie(url: String): List<String>
    fun setCookie(url: String, cookies: List<String>)
}
```

```kotlin:SharedCookieStore.kt
import android.webkit.CookieManager
import java.net.HttpCookie
import java.net.URI

class SharedCookieStore constructor(
    /*
    OkhttpのHTTP通信のみで使用するcookie情報を保持
     */
    private val javaNetCookieStore: java.net.CookieStore,
    /*
    WebViewで使用するCookie情報を保持
     */
    private val cookieManager: CookieManager,
) : CookieStore {
    
    override fun getCookie(url: String): List<String> {
        /*
        ・url文字列からURIクラスに変換し、JavaNetCookieStoreに保存されているHttpCookieクラスを読み込む
        ・HttpCookieクラスをStringに変換して返却
         */
        return javaNetCookieStore.get(URI(url))
            .map { it.toString() }
    }

    override fun setCookie(url: String, cookies: List<String>) {
        cookies.map { cookie ->
            HttpCookie.parse(cookie).forEach { httpCookie ->  
                javaNetCookieStore.add(URI(url), httpCookie)
            }
            
            cookieManager.setCookie(url, cookie)
        }
    }
}
```

### 補足：java.net.CookieStoreについて

上記のjava.net.CookieStoreはJavaに標準で搭載されているCookie管理用のインタフェースで、「java.net.CookieManager().cookieStore」でCookieStoreインスタンスを取得することもできます。
ここから得られるCookieStoreインスタンスは「InMemoryCookieStore」というクラスでよしなにインメモリ上でCookieを管理してくれます。
よく自前でCookie管理のコードを書いているサンプルを見かけますが、Cookieのmax-ageの管理とかその他諸々考慮することがありますので、そこら辺の労力やバグを避けるためにも私は自前で書かずにやっています。
ここでは省略していますが、私はDIライブラリを使ってここら辺の設定を行なっています。

## CookieJarの実装クラスの作成

次に、上記で作成したクラスをOkhttpのCookiJar実装クラスのプロパティに持たせて、処理を委譲させるようにします。

```kotlin:CookieJarImpl.kt
class CookieJarImpl constructor(
    // Okhttp/WebViewのHTTP通信で共通で使用するCookie情報を保持する
    private val sharedCookieStore: CookieStore,
) : CookieJar {
    /*
    OkhttpでのHTTPレスポンス受信時にCookieを保存する処理
     */
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        sharedCookieStore.setCookie(
            url = url.toString(),
            cookies = cookies.map { it.toString() },
        )
    }
    
    /*
    OkhttpでのHTTPリクエスト作成時にCookieを読み込む処理
     */
    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        return sharedCookieStore.getCookie(url.toString())
            .mapNotNull { Cookie.parse(url, it) }
    }
}
```

あとは、OkHttpClient作成時に上記のCookieJarImplをCookieJarに設定させてやるだけです。

```kotlin
OkHttpClient.Builder().apply {
    cookieJar(CookieJarImplインスタンス)
}.build()
```

## OkhttpとWebViewのCookie管理を別のクラスに任せている理由
上記を見て分かる通り、全てのCookie情報の管理を一つのクラスに任せるのではなく、OkhttpでのCookie管理はjava.net.CookieStoreに、WebViewでのCookie管理はCookieManagerに分けています。

これは
- WebViewとOkhttpで全て同じCookie情報を共有したいわけではない
- 一つのクラスにCookie情報を管理させると処理が複雑になる
からです。


Webを漁っていると、以下のような実装例も見かけます。
WebViewで使用するCookieManagerをOkhttpでも使用するというやり方っぽいです。

https://gist.github.com/Jthomas54/d20108abff7a8502b8f4bfaed32d4fe3



OkhttpとWebViewのCookie情報設定の切り分けにあまり拘らないのであれば、アリな選択肢だと思います。

ただ、商用のアプリだとWebViewとネイティブ画面でのHTTP通信ではCookieの設定を分けたいという場面が多いため、使用する際は各自じっくり検討する必要はあるかもしれません。

また、WebViewで使用するCookieManagerで管理しているCookie情報は、アプリ領域に保存されているDBファイルに登録されている可能性があるため、アプリを終了しても情報が保持され続けている可能性があるかもしれません。

アプリ要件によっては、アプリ終了時にネイティブ（Okhttp）のHTTP通信で使用しているCookie情報は破棄するというものもあるかと思いますので、それを自分で制御できるようにするためにも私は上記の実装をとっています。

使用するライブラリのバージョン等によって異なるところがあるかもしれませんが、以上共有でした。