Retrofit2.0配合Rxjava使用遇到的问题

2016.08.14 01:15 Sun| 571 visits | Text

最近使用Retrofit2.0配合Rxjava做一个测试应用时,发现一个问题:
按照Rxjava预期结果,调用获取Json方法,应该立即在主线程返回,在工作线程中进行访问网络等耗时操作,UI应该是流畅过度的。
但是,实际上第一次调用Rxjava获取json的时候(下面的Fire code),UI会卡大约3秒钟。而且只在第一次调用的时候卡3秒,之后就完美的按照使用Rxjava所预想的结果走了。调用方法,Main中立即返回,后台处理网络请求。

RestApi:

class RestApi {
    var weatherApi: WeatherApi
    init {
        val logIntercepter = HttpLoggingInterceptor()
        logIntercepter.level = HttpLoggingInterceptor.Level.BODY
        val okclient = OkHttpClient.Builder()
                .addInterceptor(logIntercepter)
                .retryOnConnectionFailure(true)
                .connectTimeout(15, TimeUnit.SECONDS)
                .build()
        val retrofit = Retrofit.Builder()
                .baseUrl(WeatherApi.HOST)
                .addConverterFactory(JacksonConverterFactory.create(ObjectMapper().registerModule(KotlinModule())))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(okclient)
                .build()

        weatherApi = retrofit.create(WeatherApi::class.java)
    }
}

interface WeatherApi {
    companion object {
        val HOST = "https://api.heweather.com/x3/"
        val KEY = "41054a8f1d1a4ac992b1683e47a50146" //TestKey
    }

    @GET("weather")
    fun getWeather(@Query("city") city: String, @Query("key") key: String) : Observable<Weather>

}

Fire code:

val start = System.currentTimeMillis()
Log.d("kkk","touch start")
restApi.getWeatherData("Qingdao", WeatherApi.KEY)
         .subscribeOn(Schedulers.io())
         .observeOn(AndroidSchedulers.mainThread())
         .subscribe { t -> helloText?.text = t.data.first().basic.city }
val end = System.currentTimeMillis()
Log.d("kkk", "cost time :" + (end - start))
Log.d("kkk","touch end")

第一次log:

08-13 11:02:31.906 30606-30606/com.supermario.enjoy.simpleweather D/kkk: touch start
08-13 11:02:35.276 30606-30606/com.supermario.enjoy.simpleweather D/kkk: cost time :3369
08-13 11:02:35.276 30606-30606/com.supermario.enjoy.simpleweather D/kkk: touch end

后续log:

08-13 11:02:47.438 30606-30606/com.supermario.enjoy.simpleweather D/kkk: touch start
08-13 11:02:47.439 30606-30606/com.supermario.enjoy.simpleweather D/kkk: cost time :1
08-13 11:02:47.439 30606-30606/com.supermario.enjoy.simpleweather D/kkk: touch end

代码中可以看到,我在Rxjava流中使用了 subscribeOn(Schedulers.io()) ,按理说所有的耗时操作应该都在工作线程中啊?一开始卡3秒是为什么?

我使用DDMS查看trace,尝试确认卡顿原因:

我擦,好像第一次请求的时候,retrofit在Android主线程进行一堆反射操作?应该是在生成Json类之类的。

Google无任何结果,好像其他人都没遇到这个问题似的。。。无奈到Rxjava下面提了issue,
Help! With retrofit, first call subscribe takes a long time
然后被大神教育了一顿,说我不该在github issue里面提交非bug的问题,应该去StackOverflow(赖我,太着急知道原因了)。但是大神最后还是跟我说了可能出问题的地方:Retrofit类的初始化没有被Rxjava管理。
貌似有道理。。我去试了下,,,大神说的不对啊。不管我是提前初始化,还是把Retrofit类初始化纳入Rxjava流中,这个操作时间都没有明显减少。。。SHIT!

再看看trace文件,大神的话还是提醒我了。Retrofit应该有一些初始化过程不在new的时候走,而是在第一次调用retrofit访问网络的时候。例如:按照ConverterFactory反射生产Json类的过程。至于为什么只有第一次卡,应该是相关信息生成一次就够了(我猜的)。

我们知道Retrofit配合Rxjava使用时,只要把接口定义成如下方式即可(kotlin代码):

 @GET("weather")
    fun getWeather(@Query("city") city: String, @Query("key") key: String) : Observable<Weather>

方法返回Observable。当不用Rxjava时,返回的是Call

不使用Rxjava时,需手动调用retrofit.execute()(同步)或者retrofit.equeue()(异步)。

所以我猜测Retrofit返回Observable时,自己先执行了execute(),然后根据execute方法结果创建了Observable对象返回给我们,所以导致有些初始化过程,无法通过Rxjava来控制寄宿线程。

如果是这样,解决方案就有了:Retrofit接口不返回Observable,而是返回Call。我们自己把Call执行再封装成Observable传递下去。代码如下:

Api 返回Call:

interface WeatherApi {
    companion object {
        val HOST = "https://api.heweather.com/x3/"
        val KEY = "41054a8f1d1a4ac992b1683e47a50146"
    }

    @GET("weather")
    fun getWeather(@Query("city") city: String, @Query("key") key: String) : Call<Weather>

}

手动封装Observable:

class RestApi {
    var weatherApi: WeatherApi
    init {
        val logInterceptor = HttpLoggingInterceptor()
        logInterceptor.level = HttpLoggingInterceptor.Level.BODY
        val okClient = OkHttpClient.Builder()
                .addInterceptor(logInterceptor)
                .retryOnConnectionFailure(true)
                .connectTimeout(15, TimeUnit.SECONDS)
                .build()
        val retrofit = Retrofit.Builder()
                .baseUrl(WeatherApi.HOST)
                .addConverterFactory(JacksonConverterFactory.create(ObjectMapper().registerModule(KotlinModule())))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(okClient)
                .build()

        weatherApi = retrofit.create(WeatherApi::class.java)
    }

    fun getWeatherData(city: String, key: String): Observable<Weather> {
        return Observable.fromCallable { weatherApi.getWeather("qingdao",WeatherApi.KEY).execute().body() }
    }

上面代码中的fromCallable操作符是跟那个提醒我的大神现学的。验证一下。
搞定~!不卡了。再看下trace:

耗时操作都到工作线程了。

有空去读读Retrofit源码确认下我的猜想,睡觉!