30422


Realm + Retrofit + OkHttp を利用して API をコールするアプリを作ろうと設計しています。ログインが成功すると
accessToken と refreshToken をサーバーから返却する仕組みにしていて、返却された値を Realm に保存し、次回以降は Realm
から accessToken を取得することを考えています。

accessToken には有効期限があり、有効期限が切れた accessToken を用いて API をコールすると 401
エラーが返却され、refreshToken を利用して accessToken を更新する仕様で進めているのですが、 Realm
に保存した情報が上手く取得できず、どこに問題があるか教えていただけないでしょうか？

（現象）

  1. accessToken の有効期限が切れた状態で、Retrofit を用いて 2 つの非同期なリクエストを同時に投げる（リクエストA, B）
  2. accessToken の有効期限が切れている為 A, B とも 401エラーが返却される
  3. TokenAuthenticator の authenticate メソッドが A , B とも呼ばれる
  4. A の updateToken は成功（タイミングによっては B が成功）。Realm の accessToken と refreshToken が更新される。 B の updateToken は A のリクエストが成功した為、失敗する（すでにサーバーが保持している token 情報が更新されている為）
  5. OkHttp の仕様で B の authenticate 呼ばれ続けるが、Realm 内の token 情報が更新されていても ソース内の ※1 では古い token 情報（【4】で更新される前の情報）が常に返却されてしまう

環境は Kotlin 1.0.4, Realm 2.2.0, OkHttp 3.3.1。ソースの抜粋は以下になります。

・Token.kt

[code]

    open class Token(
        // token 情報は 1件のみ保存すれば良いので id = 1 を固定で指定
        @PrimaryKey open var id: Int = 1,
        open var accecctoken: String = "",
        open var refreshToken: String = "",
    ) : RealmObject()
    
[/code]

・TokenAuthenticator.kt

[code]

    open class TokenAuthenticator : Authenticator {
        // 401 エラーの際に呼び出される
        override fun authenticate(route: Route, response: Response): Request? {
            // 保存してある Token 情報を取得
            val realm = Realm.getDefaultInstance()
            val token = realm?.where(Token::class.java)?.findFirst() ?: return null （※1）
            realm.close()
    
            // refreshToken を利用して token 更新のリクエストを同期処理で行う
            this.updateToken(token.refreshToken)
    
            // updateToken メソッドで token が更新されるので、新しい token をセットする
            val newAccessToken = "Bearer ${token.accessToken}"
            return response.request().newBuilder().header("www-Authorization", newAccessToken)?.build()
        }
    
        private fun updateToken(refreshToken: String) {
            val call = // retrofit で token を取得する call を設定
            // retrofit で新しい Token を取得する。取得に失敗したら return する
            val newToken: Token? = call.execute().body() ?: return
    
            // 取得した結果を Realm に保存する
            Realm.getDefaultInstance().use { realm ->
                realm.executeTransaction {
                    realm.copyToRealmOrUpdate(newToken)
                }
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2016-11-14T08:20:50.080
30394
2016-11-15T09:10:53.147
2016-11-15T08:10:08.290
19558
19558
question
android java realm
更新が完了したRealmの情報が取得できない（更新前の情報が取得される）

[code]

        val realm = Realm.getDefaultInstance()
        val token = realm?.where(Token::class.java)?.findFirst() ?: return null (※1)
        realm.close()
    
[/code]

の部分で、検索結果が`null`だと`Realm`インスタンスをクローズせずにreturnしてしまっています。  
`getDefaultInstance()`したら必ずクローズして下さい。これがおそらく問題の原因です。

次回ここにきて`Realm.getDefaultInstance()`が実行される際、新規の`Realm`インスタンスが作られるのではなくキャッシュされている`Realm`インスタンスが使われることになるのですが、キャッシュされているためいつまでも`Realm`インスタンス作成時点のデータを読み続けてしまいます。ちゃんとクローズを行っていれば、`Realm.getDefaultInstance()`を呼んだ時点で新たに`Realm`インスタンスがつくられ、自動的にその時点の最新のデータを見てくれます。
UIスレッドのように`Looper`が存在するスレッドの場合はイベントループ中に自動的に最新データが読まれるように更新されますが、今回のように`Looper`が存在しないスレッドの場合は新規に`Realm`インスタンスが生成されるタイミングか、特定にAPI呼び出しの場合しか読み取るデータが最新になりません。

ここ以外にもクローズ漏れがあると解決しないので確認してみて下さい。  
クローズ漏れがあるかどうかは
`Realm#getLocalInstanceCount(RealmConfiguration)`を使って確認することができます。


CC BY-SA 3.0
2016-11-15T09:10:53.147
30422
2016-11-15T09:10:53.147
319
30394
answer