改装Single <t>阻止UI线程

时间:2018-10-16 17:32:22

标签: android kotlin retrofit rx-java2

使用Single blocks UI线程改造第一个请求。以下是相关代码和更多文本:

RetrofitProvider

object RetrofitProvider {

private val TAG: String = RetrofitProvider::class.java.simpleName

val retrofit: Retrofit by lazy {
    val httpClient = OkHttpClient.Builder()
        .addInterceptor {
            val request = it.request()
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "${request.method()}: ${request.url()}")
            }

            it.proceed(request)
        }
        .build()

    Retrofit.Builder()
        .client(httpClient)
        .baseUrl("http://192.168.0.10:3000")
        .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
        .addConverterFactory(JacksonConverterFactory.create(jacksonObjectMapper()))
        .build()
}
}

ProductApi

interface ProductApi {

@GET("/products")
fun getProducts(): Single<List<Product>>

}

MainViewModel

    fun fetchProducts() {
    productData.value = Resource.Loading()
    productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
        .subscribeOn(Schedulers.io())
        .subscribe(
            {
                productData.postValue(Resource.Success(it))
            },
            {
                productData.postValue(Resource.Fail(it.message))
            })
        .addTo(disposableContainer)
}

MainFragment

...
        button.setOnClickListener {
        Toast.makeText(requireContext(), "click", Toast.LENGTH_SHORT).show()
        mainViewModel.fetchProducts()
    }
...

应用程序流程很简单,单击MainFragment上的按钮将调用MainViewModel的fetchProducts(),该fetchProducts()使用改造来获取一些东西。

productApi.getProducts()发生在UI线程上并显着阻塞(约半秒钟),即使Toast延迟了,即使在单击按钮时应立即显示, getProducts()调用。

productApi.getProducts()本身,没有订阅不会发送网络请求(我在服务器端检查过),它只是准备Single。

重要说明,在随后单击按钮时不会发生延迟。我只是第一次觉得创建Single <>是昂贵的操作。

所以我的问题是,为什么UI线程在首次请求时被阻止,以及如何解决它不难看/被黑客入侵的问题。

Observable的行为相同,但是Completable的运行速度更快,但是我需要数据,因此不能使用Completable。

3 个答案:

答案 0 :(得分:1)

我认为您的问题在于Retrofit对象的延迟初始化。 它将推迟到最后一个可能的时刻,所以我想您是第一次单击该按钮时,会创建昂贵的翻新按钮(这是在主线程上完成的)。

我的建议是删除延迟的初始化并尝试再次运行该应用程序。

答案 1 :(得分:0)

返回Completable也会阻塞UI线程,但是比返回SingleObservable的时间要短,因此似乎没有任何影响,但是确实有影响。

在后台线程上调用API调用不会阻塞您的UI,因为不会在UI线程上创建转换器。

像这样的技巧。

    Completable.complete()
        .observeOn(Schedulers.io())
        .subscribe {
            productApi.getProducts()
                .subscribe(
                    {
                        productData.postValue(Resource.Success(it))
                    },
                    {
                        productData.postValue(Resource.Fail(it.message))
                    }
                )
                .addTo(disposableContainer)
        }
        .addTo(disposableContainer)

除了使用转换器之外,您还可以做的另一件事是围绕Retrofit API创建一个包装类,该包装类将在后台线程上以可观察到的拟合方式对其进行调用。

    fun getProducts() = Single.create<List<Product>> { emitter ->
        try {
            val response = productApi.getProducts().execute()
            if (!response.isSuccessful) {
                throw HttpException(response)
            }

            emitter.onSuccess(response.body()!!)
        } catch (e: Exception) {
            emitter.onError(e)
        }
    }.observeOn(Schedulers.io())

答案 2 :(得分:0)

例如,当您调用RxJava操作时,您可以告诉它执行操作以及在何处获得结果的默认请求是您预订的位置 为了更改它,您需要添加两行

observeOn(Where you will receive the result)
subscribeOn(Where the action will be executed)

在您的情况下,应该是这样的

productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
  .observeOn(AndroidSchedulers.mainThread())
  .subscribeOn(Schedulers.io()) //or .subscribeOn(Schedulers.newThread())
  .subscribe({Success},{Failure})

我制作了一个library,其中包含许多用于Kotlin中Android开发的实用程序/扩展。

其中一个软件包可以使您轻松避免此问题。

您需要做的就是输入:

yourObservable //or any other reactive type
   .runSafeOnMain() //it will perform you action in another thread and it will return the result in main
   .subscribe({}, {])