如何在Retrofit中传递上下文,从DataStore取Token添加到请求头
解决方案
核心问题梳理
你遇到的问题本质是两个冲突点:
- DataStore的读操作是
suspend挂起函数,必须在协程环境执行 - 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
相关产品推荐
相关产品推荐

