Android/Kotlin:在 Retrofit Interceptor 中访问 sharedPreferences

Posted

技术标签:

【中文标题】Android/Kotlin:在 Retrofit Interceptor 中访问 sharedPreferences【英文标题】:Accessing sharedPreferences inside Retrofit Interceptor 【发布时间】:2020-01-20 06:52:02 【问题描述】:

这是一个 Retrofit 拦截器,用于在请求中自动注入令牌。我正在尝试从 sharedPreferences 获取此令牌,但 getSharedPreferences 在那里不可用。

如何从 Interceptor 中的 sharedpreferences 中检索我的令牌?

import android.preference.PreferenceManager
import okhttp3.Interceptor
import okhttp3.Response

class ServiceInterceptor: Interceptor 

    var token : String = "";

    override fun intercept(chain: Interceptor.Chain): Response 
        var request = chain.request()

        if(request.header("No-Authentication") == null)

            if (request.url.toString().contains("/user/signin") === false) 
                // Add Authorization header only if it's not the user signin request.

                // Get token from shared preferences
                val sharedPreference = PreferenceManager.getSharedPreferences()
                token = sharedPreference.getString("token")

                if (!token.isNullOrEmpty()) 
                    val finalToken = "Bearer " + token
                    request = request.newBuilder()
                        .addHeader("Authorization", finalToken)
                        .build()
                

            

        

        return chain.proceed(request)
    


【问题讨论】:

也许您可能想在ServiceInterceptors 构造函数中传递实例 【参考方案1】:

Kotlin 中有一个简单的解决方案——只需复制并粘贴代码到一个新的 AppPreferences.kt 文件中并按照 4 个 TODO 步骤操作 strong> 在代码中概述:

import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.core.content.edit

object AppPreferences 
    private var sharedPreferences: SharedPreferences? = null

    // TODO step 1: call `AppPreferences.setup(applicationContext)` in your MainActivity's `onCreate` method
    fun setup(context: Context) 
        // TODO step 2: set your app name here
        sharedPreferences = context.getSharedPreferences("<YOUR_APP_NAME>.sharedprefs", MODE_PRIVATE)
    

    // TODO step 4: replace these example attributes with your stored values
    var heightInCentimeters: Int?
        get() = Key.HEIGHT.getInt()
        set(value) = Key.HEIGHT.setInt(value)

    var birthdayInMilliseconds: Long?
        get() = Key.BIRTHDAY.getLong()
        set(value) = Key.BIRTHDAY.setLong(value)

    private enum class Key 
        HEIGHT, BIRTHDAY; // TODO step 3: replace these cases with your stored values keys

        fun getBoolean(): Boolean? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getBoolean(name, false) else null
        fun getFloat(): Float? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getFloat(name, 0f) else null
        fun getInt(): Int? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getInt(name, 0) else null
        fun getLong(): Long? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getLong(name, 0) else null
        fun getString(): String? = if (sharedPreferences!!.contains(name)) sharedPreferences!!.getString(name, "") else null

        fun setBoolean(value: Boolean?) = value?.let  sharedPreferences!!.edit  putBoolean(name, value)   ?: remove()
        fun setFloat(value: Float?) = value?.let  sharedPreferences!!.edit  putFloat(name, value)   ?: remove()
        fun setInt(value: Int?) = value?.let  sharedPreferences!!.edit  putInt(name, value)   ?: remove()
        fun setLong(value: Long?) = value?.let  sharedPreferences!!.edit  putLong(name, value)   ?: remove()
        fun setString(value: String?) = value?.let  sharedPreferences!!.edit  putString(name, value)   ?: remove()

        fun exists(): Boolean = sharedPreferences!!.contains(name)
        fun remove() = sharedPreferences!!.edit  remove(name) 
    

现在,您可以从应用中的任何位置获取值,如下所示:

val heightInCentimeters: Int? = AppPreferences.heightInCentimeters
val heightOrDefault: Int = AppPreferences.heightInCentimeters ?: 170

SharedPreferences 设置值同样简单:

AppPreferences.heightInCentimeters = 160 // sets a new value

以上内容摘自我的FitnessTracker project。有关完整示例,请参阅 this file。

【讨论】:

【参考方案2】:

正如 coroutineDispatcher 所说,您应该将共享首选项传递给拦截器的构造函数并持有对它们的引用。

试试这个:

class ServiceInterceptor(private val prefs: SharedPreferences): Interceptor 
    
    val token: String get() = prefs.getString("token")
    
    override fun intercept(chain: Interceptor.Chain): Response 
        var request = chain.request()
    
        if(request.header("No-Authentication") == null)
    
            if (request.url.toString().contains("/user/signin") === false) 
                // Add Authorization header only if it's not the user signin request.
                request = token
                    .takeUnless  it.isNullOrEmpty 
                    ?.let 
                        request.newBuilder()
                            .addHeader("Authorization", "Bearer $it")
                            .build()
                    
                    ?: request
            
        
        return chain.proceed(request)
    
    

拦截器现在接受对共享首选项的引用,因此依赖关系已被反转,它可以通过存根传递的 SharedPreferences 来轻松进行测试。

它可以像这样被实例化:

ServiceInterceptor(PreferenceManager.getSharedPreferences())

【讨论】:

【参考方案3】:

您可以为SharedPreference 创建一个singleton class,然后您可以从任何您想要的class 访问它。

示例

class SessionManager private constructor(context:Context) 
  private val prefs:SharedPreferences
  private val editor:SharedPreferences.Editor
  var token:String
  get() 
    return prefs.getString("token", "")
  
  set(token) 
    editor.putString("token", token)
    editor.apply()
  
  init
    prefs = context.getSharedPreferences("Your_Preference_name", Context.MODE_PRIVATE)
    editor = prefs.edit()
  
  companion object 
    private val jInstance:SessionManager
    @Synchronized fun getInstance(context:Context):SessionManager 
      if (jInstance != null)
      
        return jInstance
      
      else
      
        jInstance = SessionManager(context)
        return jInstance
      
    
  

现在你必须通过constructor 中的ServiceInterceptor 中的context,你可以访问SharedPreference,如下所示。

val token = SessionManager.getInstance(context).token;

【讨论】:

【参考方案4】:

试试这个

val token = PreferenceManager.getSharedPreferences().getToken("","")

builder.addInterceptor  chain ->
            val original = chain.request()
            val requestBuilder = original.newBuilder()
                .addHeader("Authorization", "Bearer $token")
            val request = requestBuilder.build()
            chain.proceed(request)
        
        return builder.build()

【讨论】:

以上是关于Android/Kotlin:在 Retrofit Interceptor 中访问 sharedPreferences的主要内容,如果未能解决你的问题,请参考以下文章

Android Kotlin Retrofit + SimpleXMLConverter ElementList 解析不正确

Android Kotlin Retrofit Post Request 输入的数据未发送

Android Kotlin Retrofit 与Flow。两个Flow用LiveData来进行分解

Android@Kotlin 在Android studio 中配置Kotlin

Android@Kotlin 在Android studio 中配置Kotlin

Android@Kotlin 在Android studio 中配置Kotlin