You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Retrofit中传递上下文,从DataStore取Token添加到请求头

解决方案

核心问题梳理

你遇到的问题本质是两个冲突点:

  1. DataStore的读操作是suspend挂起函数,必须在协程环境执行
  2. OkHttp拦截器默认是同步执行逻辑,没有内置协程环境
    其次你原有代码存在几个错误:
  • Retrofit服务接口不能声明成员变量(你写的var context: Context完全不需要,会导致Retrofit初始化失败)
  • 用GlobalScope异步拿Token,会导致OkHttp初始化完成时Token还没返回,拿不到有效值
  • DataStoreRepository初始化需要Context,你调用getInstance()时没有传参

第一步:修正DataStoreRepository避免内存泄漏

先优化DataStoreRepository的单例创建逻辑,自动转ApplicationContext防止内存泄漏,同时增加内存缓存减少IO读取次数:

class DataStoreRepository private constructor(context: Context) {
    private val dataStore: DataStore<Preferences> = context.createDataStore(
        name = "token_store"
    )

    companion object {
        private val TOKEN = preferencesKey<String>("TOKEN")
        private var instance: DataStoreRepository? = null
        // 自动转ApplicationContext避免内存泄漏
        fun getInstance(context: Context): DataStoreRepository {
            return instance ?: synchronized(this) {
                instance ?: DataStoreRepository(context.applicationContext).also { instance = it }
            }
        }
    }

    // 内存缓存Token,避免每次请求都读磁盘
    private var cachedToken: String? = null

    suspend fun savetoDataStore(token: String) {
        cachedToken = token
        dataStore.edit {
            it[TOKEN] = token
        }
    }

    suspend fun getToken(): String? {
        if (cachedToken != null) return cachedToken
        val preferences: Preferences = dataStore.data.first()
        cachedToken = preferences[TOKEN]
        Log.d("datastore", "token retrieved: ${preferences[TOKEN]} +++++++++++")
        return cachedToken
    }
}

第二步:重构RoomAPIService,在拦截器中同步获取Token

在拦截器中用runBlocking阻塞当前OkHttp线程(OkHttp的请求本身在子线程,不会卡UI),执行挂起函数拿Token,不需要每个接口单独传Authorization头:

interface RoomAPIService {
    // 不需要再手动传Authorization头,拦截器会统一添加
    @GET("rooms")
    fun getAllRooms(): Call<List<Room>>

    companion object {
        private var retrofitService: RoomAPIService? = null
        // 接收Context参数用于初始化DataStore
        fun getInstance(context: Context) : RoomAPIService {
            return retrofitService ?: synchronized(this) {
                val dataStoreRepo = DataStoreRepository.getInstance(context)
                val httpClient = OkHttpClient.Builder()
                    .addInterceptor { chain ->
                        // 用runBlocking执行挂起函数拿Token,阻塞当前请求线程直到拿到结果
                        val token = runBlocking(Dispatchers.IO) {
                            dataStoreRepo.getToken()
                        }
                        val requestBuilder = chain.request().newBuilder()
                        // Token不为空才添加头,避免传无效值
                        token?.takeIf { it.isNotEmpty() }?.let {
                            requestBuilder.addHeader("Authorization", "Bearer $it")
                        }
                        val request = requestBuilder.build()
                        chain.proceed(request)
                    }
                    .build()
                // 补全你原有Retrofit的配置即可
                val retrofit = Retrofit.Builder()
                    .baseUrl("你的API基础地址")
                    .client(httpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                val service = retrofit.create(RoomAPIService::class.java)
                retrofitService = service
                service
            }
        }
    }
}

第三步:修正MainActivity的逻辑

你从Intent拿到Token后,先存到DataStore中,后续所有请求都会自动携带Token:

class MainActivity : AppCompatActivity() {
    private lateinit var logoutBtn: Button
    private lateinit var bottomNavigation: BottomNavigationView
    private lateinit var binding: ActivityMainBinding
    lateinit var viewModel: RoomViewModel
    // 懒加载初始化Retrofit服务,避免阻塞onCreate
    private val retrofitService by lazy { RoomAPIService.getInstance(this) }
    val adapter = RoomsAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        logoutBtn = binding.logoutBtn
        bottomNavigation = binding.bottomNavigation
        viewModel = ViewModelProvider(this, RoomViewModeFactory(RoomRepository(retrofitService))).get(RoomViewModel::class.java)
        binding.recyclerview.adapter = adapter

        val token = intent.getStringExtra("token")
        Log.d("tokenCheck","checkToken: $token")
        if (token != null) {
            // 用和页面生命周期绑定的lifecycleScope存Token,避免内存泄漏
            lifecycleScope.launch {
                DataStoreRepository.getInstance(this@MainActivity).savetoDataStore(token)
                // 存完之后再请求接口,不需要手动传Token
                viewModel.getAllRooms()
            }
        }
    }
}

额外注意事项

  • 如果Token为空的场景需要跳转到登录页,可以在拦截器里判断Token为空时直接返回401响应,统一处理登录态失效逻辑
  • 如果Token会动态更新,记得更新DataStore时同步更新内存缓存
  • 不要用GlobalScope处理业务逻辑,优先用lifecycleScope、viewModelScope等绑定生命周期的协程作用域

内容的提问来源于stack exchange,提问作者Fahima Mokhtari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:36:03