Retrofit配合RxJava实现网络请求

前言

Retrofit是目前使用非常广泛的网络请求框架,它基于OkHttp实现,通过注解配置需求,使用简单方便,而且提供了RxJava支持,本篇文章将介绍Retrofit的一些简单用法,以及和RxJava配合使用来实现网络请求的过程。

导入依赖

//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
//RxJava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.4'

配置Retrofit

Retrofit的创建过程如下

val builder = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(factory)

下面简单说明一下Retrofit.Builder的几个方法:

baseUrl:配置请求的URL;

addCallAdapterFactory:添加CallAdapter,常用的如DefaultCallAdapterFactory支持Call类型的调用方式,而RxJava2CallAdapterFactory支持Observable类型的调用方式,也就是支持RxJava调用;

addConverterFactory:添加数据类型转换器,将后台返回的数据直接转换成实体对象,比如常用的GsonConverterFactory.create()就是用来支持gson解析,当然,我们也可以自定义转换器将返回的数据重新组装,转换成指定的实体类;

client:配置OkHttpClient,可以看到Retrofit的底层实现就是OkHttp,配置OkHttpClient时可以设置接口超时时长,管理cookie,配置缓存,添加拦截器等等;

addInterceptor:添加拦截器,拦截器的用处非常广泛,比如将后台返回的数据转换成实体类也可以通过拦截器来实现,拦截器还可以用来缓存get请求结果,打印后台返回日志的,后面将介绍几个实用的自定义拦截器;

完整代码:

class RetrofitFactory {

    companion object {
        //单例对象
        val instance = SingleTonHolder.holder
    }

    //静态内部类单例模式
    private object SingleTonHolder {
        val holder = RetrofitFactory()
    }

    fun <T> createService(
        clazz: Class<T>,
        baseUrl: String,
        factory: Converter.Factory = GsonConverterFactory.create(),
        interceptor: Interceptor? = null,
        timeout: Long = 30L,    //默认超时时长
        cache: Boolean = false  //是否进行缓存
    ): T {
        val builder = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(factory)
        if (interceptor != null) {
            builder.client(getOkHttpBuilder(timeout, cache).addInterceptor(interceptor).build())
        } else {
            builder.client(getOkHttpBuilder(timeout, cache).build())
        }
        return builder.build().create(clazz)
    }

    /**
     * 设置OkHttpClient.Builder
     *
     * @param timeout 接口超时时长
     * @param cache   是否进行缓存
     */
    private fun getOkHttpBuilder(timeout: Long, cache: Boolean): OkHttpClient.Builder {
        //添加一个log拦截器,打印所有的log
//        val httpLoggingInterceptor = HttpLoggingInterceptor()
        val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLogInterceptor())
        //可以设置请求过滤的水平,body,basic,headers
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        val builder = OkHttpClient.Builder()
            .connectTimeout(timeout, TimeUnit.SECONDS)
            .readTimeout(timeout, TimeUnit.SECONDS)
//            .retryOnConnectionFailure(true)  //设置出现错误进行重新连接
            .cookieJar(CookieJarManage.instance)  //cookie持久化
        if (cache) {
            val cacheDir = File(BaseApplication.instance.cacheDir, "httpCache")  //设置缓存路径
            val cacheSize: Long = 10 * 1024 * 1024  //设置缓存大小为10M
            builder.cache(Cache(cacheDir, cacheSize))
                .addInterceptor(HttpCacheInterceptor())
        }
        if (Config.DEBUG) {
            builder.sslSocketFactory(SSLSocketFactoryUtil.getPassAnySSLSocketFactory())  //不校验证书
                .hostnameVerifier { p0, p1 -> true }  //不校验服务器返回的信息
                .addInterceptor(httpLoggingInterceptor) //打印日志,以便调试
        }
        return builder
    }

    //清除缓存
    fun clearCache(): Boolean {
        val cacheDir = File(BaseApplication.instance.cacheDir, "httpCache")
        return FileUtil.deleteDirectory(cacheDir)
    }

}

OkHttpClient的几个方法:

cookieJar:管理cookie

class CookieJarManage : CookieJar {

    companion object {
        //单例对象
        val instance = SingleTonHolder.holder
    }

    //静态内部类单例模式
    private object SingleTonHolder {
        val holder = CookieJarManage()
    }

    private val cookieStore = HashMap<String, MutableList<Cookie>>()

    //网路访问后将服务器返回的cookies和对应的url存储在cookieStore中
    override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
        cookieStore.put(url.host(), cookies)
    }

    //网路访问开始的时候,根据访问的url去查找cookie,然后将cokies放到请求头里面
    override fun loadForRequest(url: HttpUrl): MutableList<Cookie> =
        cookieStore.get(url.host()) ?: mutableListOf<Cookie>() //cookieStore.get(url)为null时返回mutableListOf<Cookie>()

}

sslSocketFactory:校验证书,这里选择不校验证书,代码如下:

object SSLSocketFactoryUtil {

    fun getPassAnySSLSocketFactory(): SSLSocketFactory {
        val sslContext = SSLContext.getInstance("TLS")
        return sslContext.apply {
            init(null, arrayOf<TrustManager>(TrustAllManager()), SecureRandom())
        }.socketFactory
    }

    private class TrustAllManager : X509TrustManager {
        override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {
        }

        override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {
        }

        override fun getAcceptedIssuers(): Array<X509Certificate> {
            return emptyArray<X509Certificate>()
        }

    }

}

addInterceptor:添加拦截器,上面的诸如HttpLogInterceptor、HttpCacheInterceptor等自定义拦截器将在下面介绍。

自定义拦截器

日志拦截器HttpLogInterceptor

构建HttpLoggingInterceptor传入自定义拦截器,自定义服务器请求报文和响应报文输出格式,比如将输出日志json格式化,代码如下

class HttpLogInterceptor : HttpLoggingInterceptor.Logger {

    companion object {
        const val TAG = "HttpLogger"
    }

    private val mMessage = StringBuilder()

    override fun log(message: String) {
        var message = message
        // 请求或者响应开始
        if (message.startsWith("--> POST")) {
            mMessage.setLength(0)
        }
        // 以{}或者[]形式的说明是响应结果的json数据,需要进行格式化
        if (message.startsWith("{") && message.endsWith("}")
            || message.startsWith("[") && message.endsWith("]")
        ) {
            message = formatJson(message)
        }
        mMessage.append(message)
        mMessage.append("\n")
        // 响应结束,打印整条日志
        if (message.startsWith("<-- END HTTP")) {
            val spacingFlag =
                "=============================================================================================\n"
            val content = " \n$spacingFlag$mMessage$spacingFlag"
            printLog(TAG, content)
        }
    }

    private fun printLog(tag: String, msg: String) {  //信息太长,分段打印
        var msg = msg
        //因为String的length是字符数量不是字节数量所以为了防止中文字符过多,
        //把4*1024的MAX字节打印长度改为2001字符数
        val max_str_length = 2001 - tag.length
        //大于4000时
        while (msg.length > max_str_length) {
            Log.i(tag, msg.substring(0, max_str_length))
            msg = msg.substring(max_str_length)
        }
        //剩余部分
        Log.i(tag, msg)
    }

    //格式化Json字符串
    private fun formatJson(strJson: String): String {
        // 计数tab的个数
        var tabNum = 0
        val jsonFormat = StringBuilder()
        val length = strJson.length

        var last: Char = 0.toChar()
        for (i in 0 until length) {
            val c = strJson[i]
            if (c == '{') {
                tabNum++
                jsonFormat.append(c + "\n")
                jsonFormat.append(getSpaceOrTab(tabNum))
            } else if (c == '}') {
                tabNum--
                jsonFormat.append("\n")
                jsonFormat.append(getSpaceOrTab(tabNum))
                jsonFormat.append(c)
            } else if (c == ',') {
                jsonFormat.append(c + "\n")
                jsonFormat.append(getSpaceOrTab(tabNum))
            } else if (c == ':') {
                jsonFormat.append("$c ")
            } else if (c == '[') {
                tabNum++
                val next = strJson[i + 1]
                if (next == ']') {
                    jsonFormat.append(c)
                } else {
                    jsonFormat.append(c + "\n")
                    jsonFormat.append(getSpaceOrTab(tabNum))
                }
            } else if (c == ']') {
                tabNum--
                if (last == '[') {
                    jsonFormat.append(c)
                } else {
                    jsonFormat.append("\n" + getSpaceOrTab(tabNum) + c)
                }
            } else {
                jsonFormat.append(c)
            }
            last = c
        }
        return jsonFormat.toString()
    }

    private fun getSpaceOrTab(tabNum: Int): String {
        val sbTab = StringBuilder()
        for (i in 0 until tabNum) {
            sbTab.append('\t')
        }
        return sbTab.toString()
    }

}

现在控制台输出日志格式如下,相对来说要清晰醒目的多

    =============================================================================================
    --> GET http://wthrcdn.etouch.cn/weather_mini?city=%E5%8C%97%E4%BA%AC
    --> END GET
    <-- 200 OK http://wthrcdn.etouch.cn/weather_mini?city=%E5%8C%97%E4%BA%AC (119ms)
    Server: Tengine/2.3.2
    Date: Sat, 13 Nov 2021 12:39:34 GMT
    Connection: keep-alive
    Access-Control-Allow-Headers: *
    Access-Control-Allow-Methods: *
    Access-Control-Allow-Origin: *
    Cache-Control: must-revalidate, max-age=300
    Age: 0
    X-Via-Ucdn: HIT by 36.158.229.109, HIT by 180.97.190.59
    
    {
    	"data": {
    		"yesterday": {
    			"date": "12日星期五",
    			"high": "高温 15℃",
    			"fx": "西北风",
    			"low": "低温 0℃",
    			"fl": "<![
    				CDATA[
    					2级
    				]
    			]>",
    			"type": "晴"
    		},
    		"city": "北京",
    		"forecast": [
    			{
    				"date": "13日星期六",
    				"high": "高温 18℃",
    				"fengli": "<![
    					CDATA[
    						2级
    					]
    				]>",
    				"low": "低温 1℃",
    				"fengxiang": "西北风",
    				"type": "晴"
    			},
    			{
    				"date": "14日星期天",
    				"high": "高温 17℃",
    				"fengli": "<![
    					CDATA[
    						2级
    					]
    				]>",
    				"low": "低温 2℃",
    				"fengxiang": "西北风",
    				"type": "晴"
    			},
    			{
    				"date": "15日星期一",
    				"high": "高温 15℃",
    				"fengli": "<![
    					CDATA[
    						1级
    					]
    				]>",
    				"low": "低温 -1℃",
    				"fengxiang": "东北风",
    				"type": "晴"
    			},
    			{
    				"date": "16日星期二",
    				"high": "高温 16℃",
    				"fengli": "<![
    					CDATA[
    						1级
    					]
    				]>",
    				"low": "低温 -1℃",
    				"fengxiang": "西南风",
    				"type": "晴"
    			},
    			{
    				"date": "17日星期三",
    				"high": "高温 11℃",
    				"fengli": "<![
    					CDATA[
    						1级
    					]
    				]>",
    				"low": "低温 0℃",
    				"fengxiang": "西风",
    				"type": "多云"
    			}
    		],
    		"ganmao": "感冒易发期,外出请适当调整衣物,注意补充水分。",
    		"wendu": "14"
    	},
    	"status": 1000,
    	"desc": "OK"
    }
    <-- END HTTP (960-byte body)
    =============================================================================================

网络缓存拦截器HttpCacheInterceptor

如果希望能够缓存接口请求结果,使得在无网络连接的情况下请求接口地址也能返回数据,也可以通过自定义拦截器来实现,原理就是使用maxAge设置在线缓存,maxStale设置离线缓存,需要注意的是这种方式只支持缓存GET请求结果,并不支持POST请求方式,代码如下:

class HttpCacheInterceptor(
    private val maxAge: Int = 0,  //缓存过期时间,单位是秒,默认不使用缓存
    private val maxStale: Int = 3 * 24 * 60 * 60  //缓存过期时间,在请求头设置有效,在响应头设置无效,默认缓存3天
) : Interceptor {

    companion object {
        private const val TAG = "HttpCacheInterceptor"
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        val isConnected = NetworkUtil.isConnected()
        LogUtil.w(TAG, "网络是否连接:${isConnected}")
        if (!isConnected) {  //无网络的情况
            LogUtil.w(TAG, "从缓存获取数据,max-stale=$maxStale")
            val control = CacheControl.Builder()
                .onlyIfCached()
                .maxStale(maxStale, TimeUnit.SECONDS)
                .build()
            request = request.newBuilder()
                .cacheControl(control)  //从缓存读取
                .build()
        }
        var response = chain.proceed(request)
        if (isConnected) {  //有网络的情况
            LogUtil.w(TAG, "max-age=$maxAge")
            response = response.newBuilder()
                .removeHeader("Pragma")
                .header("Cache-Control", "public, max-age=" + maxAge)  //如果想要不缓存,maxAge直接设置为0
                .build()
        }
        return response
    }
}

上面的拦截器默认有网络时不使用缓存,也就是说有网络情况下每次请求都是实时请求的结果,无网络时使用缓存,缓存过期时间为3天。如果需要在有网络时也使用缓存避免频繁的接口请求,可设置maxAge不为0,如设置为5则意味着缓存过期时间为5秒。

限制最多重试次数的拦截器MaxRetryInterceptor

限制最多请求接口次数,代码如下:

class MaxRetryInterceptor(private val maxRetryCount: Int) : Interceptor {

    companion object {
        private const val TAG = "MaxRetryInterceptor"
    }

    private var retryCount = 1

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        LogUtil.w(TAG, "retry count:$retryCount")
        var response = chain.proceed(request)  //默认请求1次
        while (!response.isSuccessful && retryCount < maxRetryCount) {
            retryCount++
            LogUtil.w(TAG, "retry count:$retryCount")
            response = chain.proceed(request)  //重试
        }
        return response
    }
}

使用

GET请求

进行GET请求的参数类注解有@Query @QueryMap @QueryName等,下面使用一个天气接口进行GET请求:

--> GET http://wthrcdn.etouch.cn/weather_mini?city=北京
--> END GET

@Query

定义接口地址:

//获取天气信息,@Query,GET请求
@GET("weather_mini")
fun getWeatherByQuery(@Query("city") city: String): Observable<WeatherBean>

配置参数及使用:

//获取天气信息,@Query,GET请求
private fun getWeatherByQuery(city: String) {
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.WEATHER_URL)
        .getWeatherByQuery(city)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(WeatherObserver())
}

相关类介绍,实体类定义如下:

data class WeatherBean(
    val status: Int,
    val desc: String,
    val data: DataBean?
) : Serializable {

    data class DataBean(
        val yesterday: YesterdayBean,
        val city: String,
        val forecast: List<ForecastBean>,
        val ganmao: String,
        val wendu: String
    ) : Serializable {

        data class YesterdayBean(
            val date: String,
            val high: String,
            val fx: String,
            val low: String,
            val fl: String,
            val type: String
        ) : Serializable

        data class ForecastBean(
            val date: String,
            val high: String,
            val fengli: String,
            val low: String,
            val fengxiang: String,
            val type: String
        ) : Serializable

    }

    fun isSuccess(): Boolean = status == 1000
}

UrlConstant定义如下:

object UrlConstant {
    const val BAIDU_URL = "https://www.baidu.com/"
    const val WEATHER_URL = "http://wthrcdn.etouch.cn/"
    const val NEWS_URL = "https://api.apiopen.top/"
}

SchedulerUtil简化了在子线程请求网络,然后切换到主线程执行的过程:

object SchedulerUtil {

    fun <T> ioToMain(): ObservableTransformer<T, T> {
        return ObservableTransformer { observable ->
            observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }

}

再看一下WeatherObserver,它返回了一个Observer,即RxJava中的观察者:

private fun WeatherObserver(): Observer<WeatherBean> {
    return object : Observer<WeatherBean> {
        override fun onComplete() {
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(bean: WeatherBean) {
            if (bean.isSuccess()) {
                val result = JsonUtil.formatJson(Gson().toJson(bean))
                alert(result)
            } else {
                showToast(bean.desc)
            }
        }
        override fun onError(e: Throwable) {
            showToast(ExceptionUtil.convertExceptopn(e))
            e.printStackTrace()
        }
    }
}

@QueryMap

定义接口地址:

//获取天气信息,@QueryMap,GET请求
@GET("weather_mini")
fun getWeatherByQueryMap(@QueryMap map: HashMap<String, Any>): Observable<WeatherBean>

配置参数及使用:

//获取天气信息,@QueryMap,GET请求
private fun getWeatherByQueryMap(city: String) {
    val map = HashMap<String, Any>()
    map.put("city", city)
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.WEATHER_URL)
        .getWeatherByQueryMap(map)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(WeatherObserver())
}

可以看到,QueryMap可以将多个Query参数直接封装成一个HashMap传入,但是最后结果和Query是一样的。

无Query的情况

比如只是简单的访问https://www.baidu.com/

定义接口:

//简单的访问IP地址,Retrofit + RxJava
@GET("/")
fun accessUrlRxJava(): Observable<ResponseBody>

调用,传入地址即可:

//访问网址,Retrofit + RxJava
private fun accessUrlRxJava(url: String) {
    RetrofitFactory.instance.createService(ApiService::class.java, url)
        .accessUrlRxJava()
        .compose(SchedulerUtil.ioToMain())
        //.subscribeOn(Schedulers.io())
        //.observeOn(AndroidSchedulers.mainThread())
        .subscribe(object : Observer<ResponseBody> {
            override fun onComplete() {
            }
            override fun onSubscribe(d: Disposable) {
            }
            override fun onNext(response: ResponseBody) {
                showToast("访问成功!")
                alert(response.string())
            }
            override fun onError(e: Throwable) {
                showToast(ExceptionUtil.convertExceptopn(e))
                e.printStackTrace()
            }
        })
}

POST请求

进行POST请求的参数类注解有@Field @FieldMap @Body等,以请求网易新闻列表为例:

--> POST https://api.apiopen.top/getWangYiNews
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
count=2&page=1
--> END POST (14-byte body)

@Field

定义接口地址:

//获取网易新闻,@Field,POST请求
@FormUrlEncoded
@POST("getWangYiNews")
fun getWangYiNewsByField(@Field("page") page: String, @Field("count") count: String): Observable<NewsListBean>

配置参数及使用:

//获取网易新闻,@Field,POST请求
private fun getWangYiNewsByField(page: String, count: String) {
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.NEWS_URL)
        .getWangYiNewsByField(page, count)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(NewsObserver())
}

相关类介绍,实体类定义如下:

data class NewsListBean(
    val code: Int,
    val message: String,
    val result: MutableList<ResultBean>?
) : Serializable {
    data class ResultBean(
        val path: String,
        val image: String,
        val title: String,
        val passtime: String
    ) : Serializable

    fun isSuccess(): Boolean = code == 200
}

NewsObserver代码如下:

private fun NewsObserver(): Observer<NewsListBean> {
    return object : Observer<NewsListBean> {
        override fun onComplete() {
        }
        override fun onSubscribe(d: Disposable) {
        }
        override fun onNext(bean: NewsListBean) {
            if (bean.isSuccess()) {
                val result = JsonUtil.formatJson(Gson().toJson(bean))
                alert(result)
            } else {
                showToast(bean.message)
            }
        }
        override fun onError(e: Throwable) {
            showToast(ExceptionUtil.convertExceptopn(e))
            e.printStackTrace()
        }
    }
}

@FieldMap

定义接口地址:

//获取网易新闻,@FieldMap,POST请求
@FormUrlEncoded
@POST("getWangYiNews")
fun getWangYiNewsByFieldMap(@FieldMap map: HashMap<String, Any>): Observable<NewsListBean>

配置参数及使用:

//获取网易新闻,@FieldMap,POST请求
private fun getWangYiNewsByFieldMap(page: String, count: String) {
    val map = HashMap<String, Any>()
    map.put("page", page)
    map.put("count", count)
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.NEWS_URL)
        .getWangYiNewsByFieldMap(map)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(NewsObserver())
}

@Body

定义接口地址:

//获取网易新闻,@Body,POST请求
@POST("getWangYiNews")
fun getWangYiNewsByBody(@Body requestBody: RequestBody): Observable<NewsListBean>

配置参数及使用:

//获取网易新闻,@Body,POST请求
private fun getWangYiNewsByBody(page: String, count: String) {
    val map = HashMap<String, Any>()
    map.put("page", page)
    map.put("count", count)
    val sb = StringBuilder()
    map.forEach {
        sb.append("${it.key}=${it.value}&")
    }
    val data = sb.toString().substring(0, sb.toString().length - 1)
    //val data = "page=$page&count=$count"
    val body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), data)
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.NEWS_URL)
        .getWangYiNewsByBody(body)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(NewsObserver())
}

@Body是以表单形式提交POST请求

缓存GET请求

//测试缓存机制
private fun testCache(city: String) {
    RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.WEATHER_URL, cache = true)
        .getWeatherByQuery(city)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(WeatherObserver())
}

RetrofitFactory相关缓存设置代码:

/**
 * 设置OkHttpClient.Builder
 *
 * @param timeout 接口超时时长
 * @param cache   是否进行缓存
 */
private fun getOkHttpBuilder(timeout: Long, cache: Boolean): OkHttpClient.Builder {
    //添加一个log拦截器,打印所有的log
      val httpLoggingInterceptor = HttpLoggingInterceptor()
    val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLogInterceptor())
    //可以设置请求过滤的水平,body,basic,headers
    httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
    val builder = OkHttpClient.Builder()
        .connectTimeout(timeout, TimeUnit.SECONDS)
        .readTimeout(timeout, TimeUnit.SECONDS)
          .retryOnConnectionFailure(true)  //设置出现错误进行重新连接
        .cookieJar(CookieJarManage.instance)  //cookie持久化
    if (cache) {
        val cacheDir = File(BaseApplication.instance.cacheDir, "httpCache")  //设置缓存路径
        val cacheSize: Long = 10 * 1024 * 1024  //设置缓存大小为10M
        builder.cache(Cache(cacheDir, cacheSize))
            .addInterceptor(HttpCacheInterceptor())
    }
    if (Config.DEBUG) {
        builder.sslSocketFactory(SSLSocketFactoryUtil.getPassAnySSLSocketFactory())  //不校验证书
            .hostnameVerifier { p0, p1 -> true }  //不校验服务器返回的信息
            .addInterceptor(httpLoggingInterceptor) //打印日志,以便调试
    }
    return builder
}
//清除缓存
fun clearCache(): Boolean {
    val cacheDir = File(BaseApplication.instance.cacheDir, "httpCache")
    return FileUtil.deleteDirectory(cacheDir)
}

设置最多尝试请求3次

//最多重试3次
private fun testMaxRetry(city: String) {
    RetrofitFactory.instance.createService(
        ApiService::class.java,
        UrlConstant.WEATHER_URL,
        interceptor = MaxRetryInterceptor(3)
    ).getWeatherByQuery(city)
        .compose(SchedulerUtil.ioToMain())
        .subscribe(WeatherObserver())
}

源码地址

RxJava的进阶用法

手动创建Observable

使用create操作符来创建一个Observable,示例如下:

Observable.create<WeatherBean> {
    Thread.sleep(5000)  //模拟请求耗时
    if (Random.nextBoolean()) {
        //模拟请求成功
        it.onNext(WeatherBean(1002, "invilad-citykey", null))
        it.onComplete()
    } else {
        //模拟请求失败
        it.onError(Throwable("请求异常"))
    }
}.compose(SchedulerUtil.ioToMain()).subscribe(object : Observer<WeatherBean> {
    override fun onSubscribe(d: Disposable) {
        println("===开始请求")
    }
    override fun onNext(t: WeatherBean) {
        println("===" + Gson().toJson(t))
    }
    override fun onError(e: Throwable) {
        println("===" + e.message)
    }
    override fun onComplete() {
        println("===请求完成")
    }
})
2021-12-27 20:09:22.894 20272-20272/com.android.httpdemo I/System.out: ===开始请求
2021-12-27 20:09:27.993 20272-20272/com.android.httpdemo I/System.out: ==={"desc":"invilad-citykey","status":1002}
2021-12-27 20:09:27.993 20272-20272/com.android.httpdemo I/System.out: ===请求完成


2021-12-27 20:09:44.780 20272-20272/com.android.httpdemo I/System.out: ===开始请求
2021-12-27 20:09:49.783 20272-20272/com.android.httpdemo I/System.out: ===请求异常

合并接口请求

当需要合并多个接口请求时,可以使用zip、merge、concat等操作符来实现。以下面接口为例:

//获取天气
private fun getWeather(city: String): Observable<WeatherBean> {
    return RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.WEATHER_URL)
        .getWeatherByQuery(city)
        .compose(SchedulerUtil.ioToMain())
}
//获取第一条新闻
private fun getNews(page: String, count: String): Observable<NewsListBean> {
    return RetrofitFactory.instance.createService(ApiService::class.java, UrlConstant.NEWS_URL)
        .getWangYiNewsByField(page, count)
        .compose(SchedulerUtil.ioToMain())
}

使用zip:

Observable.zip<WeatherBean, NewsListBean, NewsListBean>(
    getWeather("北京"),
    getNews("0", "1"),
    BiFunction { t1, t2 ->
        println("===${Gson().toJson(t1)}")  //处理返回的天气信息
        t2  //返回新闻信息
    }
).subscribe(object : Observer<NewsListBean> {
    override fun onSubscribe(d: Disposable) {
        println("===开始请求")
    }
    override fun onNext(t: NewsListBean) {
        println("===${Gson().toJson(t)}")
    }
    override fun onError(e: Throwable) {
        println("===请求异常")
    }
    override fun onComplete() {
        println("===请求完成")
    }
})
2021-12-27 20:23:48.671 22070-22070/com.android.httpdemo I/System.out: ===开始请求
2021-12-27 20:23:49.013 22070-22070/com.android.httpdemo I/System.out: ==={"data":{"city":"北京","forecast":[{"date":"27日星期一","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"西北风","high":"高温 6℃","low":"低温 -8℃","type":"晴"},{"date":"28日星期二","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"西北风","high":"高温 6℃","low":"低温 -7℃","type":"晴"},{"date":"29日星期三","fengli":"\u003c![CDATA[3级]]\u003e","fengxiang":"西北风","high":"高温 4℃","low":"低温 -6℃","type":"晴"},{"date":"30日星期四","fengli":"\u003c![CDATA[2级]]\u003e","fengxiang":"西北风","high":"高温 4℃","low":"低温 -8℃","type":"晴"},{"date":"31日星期五","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"东北风","high":"高温 3℃","low":"低温 -8℃","type":"多云"}],"ganmao":"感冒高发期,尽量避免外出,外出戴口罩防护。","wendu":"-1","yesterday":{"date":"26日星期日","fl":"\u003c![CDATA[2级]]\u003e","fx":"西北风","high":"高温 -1℃","low":"低温 -8℃","type":"晴"}},"desc":"OK","status":1000}
2021-12-27 20:23:49.024 22070-22070/com.android.httpdemo I/System.out: ==={"code":200,"message":"成功!","result":[{"image":"http://dingyue.ws.126.net/2021/0201/b63f2e50j00qntwfh0020c000hs00npg.jpg?imageView\u0026thumbnail\u003d140y88\u0026quality\u003d85","passtime":"2021-02-02 10:00:51","path":"https://www.163.com/dy/article/G1OBC8LO0514BCL4.html","title":"被指偷拿半卷卫生纸 63岁女洗碗工服药自杀 酒店回应"}]}
2021-12-27 20:23:49.024 22070-22070/com.android.httpdemo I/System.out: ===请求完成

在RxJava2中,合并2个Observable使用BiFunction,3个使用Function3,4个使用Function4,5个使用Function5,以此类推。使用zip时,只要有一个请求出错,便会回调onError,不会回调onNext,即使有其他请求成功,这是因为这些请求是合并后同时发送的。

使用merge:

Observable.merge(
    getWeather("北京"),
    getNews("0", "1")
).subscribe(object : Observer<Any> {
    override fun onSubscribe(d: Disposable) {
        println("===开始请求")
    }
    override fun onNext(t: Any) {
        if (t is WeatherBean) {
            println("===天气:${Gson().toJson(t)}")
        } else if (t is NewsListBean) {
            println("===新闻:${Gson().toJson(t)}")
        }
    }
    override fun onError(e: Throwable) {
        println("===请求异常")
    }
    override fun onComplete() {
        println("===请求完成")
    }
})
2021-12-27 20:36:24.046 23040-23040/com.android.httpdemo I/System.out: ===开始请求
2021-12-27 20:36:24.225 23040-23040/com.android.httpdemo I/System.out: ===天气:{"data":{"city":"北京","forecast":[{"date":"27日星期一","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"西北风","high":"高温 6℃","low":"低温 -8℃","type":"晴"},{"date":"28日星期二","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"西北风","high":"高温 6℃","low":"低温 -7℃","type":"晴"},{"date":"29日星期三","fengli":"\u003c![CDATA[3级]]\u003e","fengxiang":"西北风","high":"高温 4℃","low":"低温 -6℃","type":"晴"},{"date":"30日星期四","fengli":"\u003c![CDATA[2级]]\u003e","fengxiang":"西北风","high":"高温 4℃","low":"低温 -8℃","type":"晴"},{"date":"31日星期五","fengli":"\u003c![CDATA[1级]]\u003e","fengxiang":"东北风","high":"高温 3℃","low":"低温 -8℃","type":"多云"}],"ganmao":"感冒高发期,尽量避免外出,外出戴口罩防护。","wendu":"-1","yesterday":{"date":"26日星期日","fl":"\u003c![CDATA[2级]]\u003e","fx":"西北风","high":"高温 -1℃","low":"低温 -8℃","type":"晴"}},"desc":"OK","status":1000}
2021-12-27 20:36:25.687 23040-23040/com.android.httpdemo I/System.out: ===新闻:{"code":200,"message":"成功!","result":[{"image":"http://dingyue.ws.126.net/2021/0201/b63f2e50j00qntwfh0020c000hs00npg.jpg?imageView\u0026thumbnail\u003d140y88\u0026quality\u003d85","passtime":"2021-02-02 10:00:51","path":"https://www.163.com/dy/article/G1OBC8LO0514BCL4.html","title":"被指偷拿半卷卫生纸 63岁女洗碗工服药自杀 酒店回应"}]}
2021-12-27 20:36:25.688 23040-23040/com.android.httpdemo I/System.out: ===请求完成

concat的用法和merge的用法几乎一模一样,区别在于concat是按顺序发送接口请求的,返回时也是按顺序返回,而merge是无序的,也就是可能出现第二个接口先请求先返回。

使用merge或concat时,如果其中一个请求出错,并不会影响到其他请求,这一点区别于zip。

嵌套接口请求

当接口出现嵌套时,比如第二个接口需要用到第一个接口的返回结果,此时可以使用flatMap来实现:

getWeather("北京").flatMap {
    val isSuccess = it.status == 1000  //这里假设需要用到getWeather返回的这个参数
    val count = if (isSuccess) "1" else "2"
    getNews("0", count)  //使用第一个接口的参数调用第二个接口
}.subscribe(object : Observer<NewsListBean> {
    override fun onSubscribe(d: Disposable) {
        println("===开始请求")
    }
    override fun onNext(t: NewsListBean) {
        println("===${Gson().toJson(t)}")
    }
    override fun onError(e: Throwable) {
        println("===请求异常")
    }
    override fun onComplete() {
        println("===请求完成")
    }
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值