Dagger小技巧之OkHttp延迟初始化

2020-05-12  本文已影响0人  珞泽珈群

前言

偶然间看到了这个关于Dagger小技巧的系列,很实用,也不复杂,在此我搬运转述一下。本文并非翻译,只是概述,想要更详细地了解,请查看原文:
Dagger Party Tricks: Deferred OkHttp Initialization

其它技巧:
Dagger小技巧之私有依赖
Dagger小技巧之Kotlin扩展函数

目的

利用Dagger延迟OkHttp的初始化,并且把初始化过程放到后台线程,避免阻塞主线程。

问题

@Module
object ApiModule {
    @Provides
    fun provideCache(ctx: Context): Cache {
      return Cache(ctx.cacheDir, CACHE_SIZE)
    }

    @Provides
    fun provideClient(cache: Cache): OkHttpClient {
      return OkHttpClient.Builder()
          .cache(cache)
          .build()
    }

    @Provides
    fun provideRetrofit(client: OkHttpClient): Retrofit {
      return Retrofit.Builder()
          .baseUrl("https://example.com")
          .client(client)
          .build()
    }

    @Provides
    fun provideApi(retrofit: Retrofit): MyApi {
      return retrofit.create(MyApi::class.java)
    }
}

类似这样的代码,在使用Retrofit+Dagger的App中很常见,几乎是固定的套路。然后你就可以愉快地使用MyApi了,像是这样:

class MainController @Inject constructor(private val api: MyApi) {
    suspend fun onLoad() {
        val result = api.fetchStuff()
    }
}

那么问题来了,依赖注入何时被初始化的呢?毫无疑问,借助Retrofit的CallAdapter,网络请求都是在后台线程发出的(例如RxJava,Coroutines),但是依赖注入缺是在它被调用的线程初始化的,对于上例而言,当我们通过Dagger获取MainController时,MyApi才会被初始化创建(你可以把MyApi定义成@Singleton的,但MyApi总是存在一个初始化的过程),MyApi初始化也就意味着需要创建Cache,OkHttpClientRetrofit,这一切都发生在依赖注入被调用的线程,一般而言就是主线程,这并非什么不可接受的问题,但是的确存在着一定的开销,创建Cache可能会存在一些IO操作,创建OkHttpClient会使用TrustManagerFactory,也可能需要100ms左右,总之这一切都不是free的,最好还是远离主线程,放在后台线程。

解决方案

你可能会想着这么做:

class MainController @Inject constructor(private val api: Lazy<MyApi>) {
    suspend fun onLoad() {
        val result = api.get().fetchStuff()
    }
}

借助Dagger Lazy,我们就可以把对MyApi的初始化延迟到使用时了(onLoad()调用时),但是这并不是一种很好的选择,Lazy是可以把MyApi的初始化延迟到使用时,但是我们并不能保证使用时(onLoad()调用时)就不是发生在主线程,你当然可以选择在onLoad()调用时单独开个线程,但是这种依赖于使用时的“线程保证”是一种很奇怪的做法,因为你不确定MyApi会在哪里被第一次使用(或许还有个方法叫onLoad2())。

让我们把目光重新聚焦到Retrofit。

@Provides
fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
        .client(client)
        ...
}

众所周知,Retrofit依赖于OkHttp,但是这样说并不严谨,其实Retrofit仅仅依赖于Call.Factory接口,而OkHttpClient仅仅是Call.Factory的一种实现,上面这段代码只是如下代码的简写:

@Provides
fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
        .callFactory(client)
        ...
}

Call.Factory又是个SAM接口,所以可以使用一个lambda进行代理:

@Provides
fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
        .callFactory { client.newCall(it) }
        ...
}

这时候我们再使用Dagger Lazy:

@Provides
fun provideRetrofit(client: Lazy<OkHttpClient>): Retrofit {
    return Retrofit.Builder()
        .callFactory { client.get().newCall(it) }
        ...
}

最美妙的地方到了,Call.Factory是会在后台线程被调用的,而这正是我们想要的,并且这一切对于外部使用者而言是透明的,实在是严丝合缝的美妙。

我们以0代价,不仅做到了OkHttp的延迟初始化,并且把初始化放到了后台线程,还有这好事,你还在等啥。

上一篇 下一篇

猜你喜欢

热点阅读