前言
偶然间看到了这个关于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
,OkHttpClient
和Retrofit
,这一切都发生在依赖注入被调用的线程,一般而言就是主线程,这并非什么不可接受的问题,但是的确存在着一定的开销,创建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的延迟初始化,并且把初始化放到了后台线程,还有这好事,你还在等啥。
网友评论