如何在Retrofit Kotlin单例中使用Android Context访问cacheDir?最优实现方案问询
最优解决方案:依赖注入或Application Context初始化
你的问题核心在于Retrofit单例需要访问cacheDir,而这个属性属于Android Context,直接在单例对象中获取会因为Context的生命周期问题导致潜在内存泄漏,同时也不符合依赖倒置原则。下面是两种最优的解决思路,按推荐优先级排序:
方案一:使用Hilt(依赖注入框架,推荐用于现代Android项目)
这是目前Android开发中解耦依赖、管理全局对象的最佳实践,能自动处理Context的生命周期,彻底避免内存泄漏问题,同时让代码更易于测试和维护。
步骤与代码实现:
- 先在项目中配置Hilt(如果还没配置):在项目级
build.gradle和模块级build.gradle添加Hilt依赖,启用Hilt插件。 - 创建
NetworkModule,通过Hilt提供全局的网络相关实例,注入@ApplicationContext获取全局Context:
import android.content.Context import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import okhttp3.Cache import okhttp3.CacheControl import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.GET import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Singleton private const val BASE_URL = "https://5c5c8ba5345018a0014aa1b24.mockapi.io/api/test" private const val cacheSize = 1 * 1024 * 1024 // 1 MB @Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideMoshi(): Moshi { return Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() } @Provides @Singleton fun provideCache(@ApplicationContext context: Context): Cache { val httpCacheDirectory = File(context.cacheDir, "http-cache") return Cache(httpCacheDirectory, cacheSize.toLong()) } @Provides @Singleton fun provideNetworkCacheInterceptor(): Interceptor { return Interceptor { chain -> val response = chain.proceed(chain.request()) val cacheControl = CacheControl.Builder() .maxAge(1, TimeUnit.MINUTES) .build() response.newBuilder() .header("Cache-Control", cacheControl.toString()) .build() } } @Provides @Singleton fun provideLoggingInterceptor(): HttpLoggingInterceptor { return HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY) } @Provides @Singleton fun provideOkHttpClient( cache: Cache, networkCacheInterceptor: Interceptor, loggingInterceptor: HttpLoggingInterceptor ): OkHttpClient { val builder = OkHttpClient.Builder() .cache(cache) .addNetworkInterceptor(networkCacheInterceptor) // 仅在Debug模式添加日志拦截器,避免生产环境泄露数据 if (BuildConfig.DEBUG) { builder.addInterceptor(loggingInterceptor) } return builder.build() } @Provides @Singleton fun provideRetrofit(moshi: Moshi, okHttpClient: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create(moshi)) .client(okHttpClient) .build() } @Provides @Singleton fun provideWeatherForecastsApiService(retrofit: Retrofit): WeatherForecastsApiService { return retrofit.create(WeatherForecastsApiService::class.java) } } interface WeatherForecastsApiService { @GET("/") // BASE_URL已包含完整路径,此处用"/"即可 suspend fun getWeatherForecasts(): List<WeatherForecastEntity> } // 在自定义Application类中添加Hilt注解 @HiltAndroidApp class MyApplication : Application()
使用方式:
在需要调用API的组件(如ViewModel、Activity)中,通过@Inject直接注入服务实例:
class WeatherViewModel @ViewModelInject constructor( private val apiService: WeatherForecastsApiService ) : ViewModel() { // 调用apiService.getWeatherForecasts()发起请求 }
方案二:通过Application Context初始化单例(无DI框架时的最优解)
如果暂时不想引入依赖注入框架,可以在自定义Application类中完成Retrofit的初始化,确保使用Application Context(全局唯一,不会因组件销毁引发内存泄漏)。
修改后的代码:
import android.app.Application import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import okhttp3.Cache import okhttp3.CacheControl import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.GET import java.io.File import java.util.concurrent.TimeUnit private const val BASE_URL = "https://5c5c8ba5345018a0014aa1b24.mockapi.io/api/test" private const val cacheSize = 1 * 1024 * 1024 // 1 MB class MyApplication : Application() { companion object { lateinit var retrofitService: WeatherForecastsApiService private set } override fun onCreate() { super.onCreate() // 初始化Moshi val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() // 初始化缓存(使用Application的cacheDir) val httpCacheDirectory = File(cacheDir, "http-cache") val cache = Cache(httpCacheDirectory, cacheSize.toLong()) // 网络缓存拦截器 val networkCacheInterceptor = Interceptor { chain -> val response = chain.proceed(chain.request()) val cacheControl = CacheControl.Builder() .maxAge(1, TimeUnit.MINUTES) .build() response.newBuilder() .header("Cache-Control", cacheControl.toString()) .build() } // 日志拦截器 val loggingInterceptor = HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY) // 构建OkHttpClient val httpClient = OkHttpClient.Builder() .cache(cache) .addNetworkInterceptor(networkCacheInterceptor) .apply { if (BuildConfig.DEBUG) { addInterceptor(loggingInterceptor) } } .build() // 初始化Retrofit val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create(moshi)) .client(httpClient) .build() retrofitService = retrofit.create(WeatherForecastsApiService::class.java) } } interface WeatherForecastsApiService { @GET("/") suspend fun getWeatherForecasts(): List<WeatherForecastEntity> } // 使用方式 // MyApplication.retrofitService.getWeatherForecasts()
关键注意事项:
- 必须在
AndroidManifest.xml中注册自定义Application:
<application android:name=".MyApplication" ...> ... </application>
- 绝对不要用Activity或Fragment的Context初始化单例,否则会导致组件被单例持有无法回收,引发内存泄漏。
为什么不推荐静态传入Context?
如果直接给单例加init(context: Context)方法,很容易不小心传入Activity Context,导致Activity被单例长期持有,进而引发内存泄漏。上面两种方案都从根源上规避了这个风险,是更安全可靠的选择。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

