将 Cookie 与 Retrofit 和 Hilt 以及推荐的架构一起使用
Posted
技术标签:
【中文标题】将 Cookie 与 Retrofit 和 Hilt 以及推荐的架构一起使用【英文标题】:Using Cookies with Retrofit and Hilt and recommended architecture 【发布时间】:2021-08-29 04:54:41 【问题描述】:我对 android 和 Java / Kotlin 还很陌生,所以我一直在努力在推荐的架构中实现 cookie。我查看了很多地方,阅读了文档并观看了很多视频,每个人都有不同的实现方式,我仍然感到困惑。它们是如何组合在一起的?
【问题讨论】:
【参考方案1】:我原以为这是一个如此常见的用例,以至于我不敢相信答案并非遍布整个网络,但我必须努力将所有部分组合在一起。以下是从存储库向下对我有用的方法。我没有包括数据库方面的东西,因为这在很多地方都有很好的记录,我发现它很容易理解(如果有人需要我包括,请告诉我)。我中途切换到 Kotlin,因为我只能在 Kotlin 中找到部分答案。我的示例是登录用户并获取基本的个人资料详细信息。
存储库将登录详细信息发送到服务器并将响应保存在数据库中,然后提取该信息以另存为 LiveData
package com.example.myapplication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.myapplication.*
import com.example.myapplication.asDomainModel
import com.example.myapplication.asDBEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
import javax.inject.Inject
class LoginRepository @Inject constructor(
private val myDao: MyDao,
private val myNetwork: Network
)
private val _profile: MutableLiveData<Profile> = MutableLiveData()
val profile: LiveData<Profile>
get() = _profile
suspend fun login(name: String, password: String)
withContext(Dispatchers.IO)
// log in to server and get profile data
val profileNWEntity = myNetwork.login("login", name, password)
// process response
when (profileNWEntity.status)
"PROFLOGINOK" ->
// save profile in database then retrieve
myDao.insertProfile(profileNWEntity.asDBEntity())
_profile.postValue(myDao.getProfile(profileNWEntity.user).asDomainModel())
else ->
throw IOException (profileNWEntity.status)
改造端点定义登录过程
package com.example.myapplication
import com.example.myapplication.ProfileNWEntity
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface Network
@FormUrlEncoded
@POST("server_api")
suspend fun login(
@Field("action") action: String,
@Field("name") name: String,
@Field("pass") password: String
): ProfileNWEntity
实体 - Gson 用来解析网络响应和存储库以适应数据库
package com.example.myapplication
import com.example.myapplication.AccountDBEntity
import com.example.myapplication.ProfileDBEntity
/**
* Base profile response from network query
*/
data class ProfileNWEntity(
val user: Int,
val name: String,
val status: String
)
// map the profile from network to database format
fun ProfileNWEntity.asDBEntity(): ProfileDBEntity
return ProfileDBEntity(
id = user,
name = name
)
改造类以允许包含 cookie(连同下面包含的拦截器,这来自 tsuharesu 和 Nikhil Jha 在https://gist.github.com/nikhiljha/52d45ca69a8415c6990d2a63f61184ff 的工作)
package com.example.myapplication
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Inject
class RetrofitWithCookie @Inject constructor(
context: Context, // uses Hilt to inject the context to be passed to the interceptors
gson: Gson
)
private val mContext = context
private val gson = gson
fun createRetrofit(): Retrofit
val client: OkHttpClient
val builder = OkHttpClient.Builder()
builder.addInterceptor(AddCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
builder.addInterceptor(ReceivedCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
client = builder.build()
return Retrofit.Builder()
.baseUrl("myServer URL") // REQUIRED
.client(client) // VERY VERY IMPORTANT
.addConverterFactory(GsonConverterFactory.create(gson))
.build() // REQUIRED
接收拦截器捕获入站 cookie 并将它们保存在 sharedpreferences 中
package com.example.myapplication
import android.content.Context
import androidx.preference.PreferenceManager
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
import java.util.*
// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
class ReceivedCookiesInterceptor(context: Context?) : Interceptor
private val context: Context?
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response
val originalResponse = chain.proceed(chain.request())
if (!originalResponse.headers("Set-Cookie").isEmpty())
val cookies = PreferenceManager.getDefaultSharedPreferences(context)
.getStringSet("PREF_COOKIES", HashSet()) as HashSet<String>?
for (header in originalResponse.headers("Set-Cookie"))
cookies!!.add(header)
val memes = PreferenceManager.getDefaultSharedPreferences(context).edit()
memes.putStringSet("PREF_COOKIES", cookies).apply()
memes.commit()
return originalResponse
init
this.context = context
// AddCookiesInterceptor()
AddCookies 拦截器将 cookie 重新添加到未来的请求中
package com.example.myapplication
import android.content.Context
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ActivityContext
import okhttp3.Interceptor
import okhttp3.Response
import timber.log.Timber
import java.io.IOException
import java.util.*
// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
/**
* This interceptor put all the Cookies in Preferences in the Request.
* Your implementation on how to get the Preferences may ary, but this will work 99% of the time.
*/
class AddCookiesInterceptor(@ActivityContext context: Context?) : Interceptor
// We're storing our stuff in a database made just for cookies called PREF_COOKIES.
// I reccomend you do this, and don't change this default value.
private val context: Context?
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response
val builder = chain.request().newBuilder()
val preferences = PreferenceManager.getDefaultSharedPreferences(context).getStringSet(
PREF_COOKIES, HashSet()
) as HashSet<String>?
// Use the following if you need everything in one line.
// Some APIs die if you do it differently.
/*String cookiestring = "";
for (String cookie : preferences)
String[] parser = cookie.split(";");
cookiestring = cookiestring + parser[0] + "; ";
builder.addHeader("Cookie", cookiestring);
*/for (cookie in preferences!!)
builder.addHeader("Cookie", cookie)
Timber.d("adding cookie %s", cookie)
return chain.proceed(builder.build())
companion object
const val PREF_COOKIES = "PREF_COOKIES"
init
this.context = context
Hilt Module 将它们结合在一起
package com.example.myapplication
import android.content.Context
import com.example.myapplication.Network
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class NetworkModule
@Singleton
@Provides
fun provideNetwork(retrofit: Retrofit)
: Network = retrofit.create(Network::class.java)
@Singleton
@Provides
fun provideRetrofitWithCookie(
@ApplicationContext context: Context,
gson: Gson
): Retrofit = RetrofitWithCookie(context, gson).createRetrofit()
@Singleton
@Provides
fun provideGson(): Gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") // used for parsing other responses
.create()
【讨论】:
以上是关于将 Cookie 与 Retrofit 和 Hilt 以及推荐的架构一起使用的主要内容,如果未能解决你的问题,请参考以下文章
[如何在Webview和Retrofit2 Okhttp3之间共享cookie
Retrofit2.0 ,OkHttp3完美同步持久Cookie实现免登录