RxJava + Retrofit 应用整理(Kotlin 版)

好久之前用 RxJava 搭配 Retrofit 抽过一个网路请求框架《RxJava + Retrofit 应用整理》,现在再来个 Kotlin 版本的 ~

一、框架搭建

网路请求框架基于以下版本:

	// Kotlin
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
    // FastJson
    implementation 'com.alibaba:fastjson:1.2.74'
    // RXJava
    implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    // Retrofit
    implementation "com.squareup.retrofit2:retrofit:2.6.2"
    implementation "com.squareup.retrofit2:converter-gson:2.6.2"
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
    implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"


主体类,除一些初始化操作外,也增加了如下内容:

1、在请求体中增加默认参数
2、自定义打印请求和响应信息(之前采用的是 okhttp3:logging-interceptor,有些适配问题,也不够灵活)

import android.text.TextUtils
import okhttp3.*
import okio.Buffer
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.Reader
import java.nio.charset.Charset
import java.util.*

class RetrofitHelper private constructor() {

    private val mRetrofit: Retrofit
    private val mAppInterface: AppInterface

    companion object {
    	@JvmStatic
        val instance: RetrofitHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            RetrofitHelper()
        }
        private val CONTENT_TYPE_JSON: MediaType? = MediaType.parse("application/json")
        const val BASE_URL: String = "https://xxx.com/"
    }

    init {
        val okHttpClientBuilder = OkHttpClient.Builder()
        setInterceptor(okHttpClientBuilder)
        mRetrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create()) // 添加 gson 关联
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 添加 Rxjava 关联
                .client(okHttpClientBuilder.build()) // 设置 OkHttpClient
                .build()
        mAppInterface = mRetrofit.create(AppInterface::class.java)
    }

    // 设置拦截器
    private fun setInterceptor(okHttpClientBuilder: OkHttpClient.Builder) {
        val interceptor = Interceptor { chain ->
            var request: Request = chain.request()
            if ("POST" == request.method()) {
                val body = request.body()
                // 适配 form 表单类型提交
                if (body is FormBody) {
                    val builder = FormBody.Builder()
                    // 添加传入参数
                    for (i in 0 until body.size()) {
                        builder.add(body.encodedName(i), body.encodedValue(i))
                    }
                    // 追加默认参数
                    builder.add("time", System.currentTimeMillis().toString())
                    builder.add("version", DeviceUtil.getAppVersionName())
                    // 构造新的请求体
                    request = request.newBuilder().post(builder.build()).build()
                } else {
                    val defaultParamMaps = HashMap<String, Any?>()
                    val bodyString = bodyToString(body)
                    if (!TextUtils.isEmpty(bodyString)) {
                        val paramMaps = JSON.parse(bodyString) as Map<*, *>
                        paramMaps.forEach {
                            defaultParamMaps[it.key.toString()] = it.value
                        }
                    }
                    defaultParamMaps["time"] = System.currentTimeMillis()
                    defaultParamMaps["version"] = DeviceUtil.getAppVersionName()
                    // 默认json类型提交
                    val requestBody = RequestBody.create(CONTENT_TYPE_JSON, JSON.toJSONString(defaultParamMaps))
                    request = request.newBuilder().post(requestBody).build()
                }
            }

            logRequestInfo(request)
            val response = chain.proceed(request)
            logResponseInfo(response)

            response
        }
        okHttpClientBuilder.addInterceptor(interceptor)
    }

    // 打印请求数据
    private fun logRequestInfo(request: Request) {
        if (!DebugUtil.IS_DEBUG) {
            return
        }
        val log = StringBuilder()
        log.append("\n \n \n>>>>>>>>>>>>>>>>>>>>>> Http-Request >>>>>>>>>>>>>>>>>>>>>>")
        log.append("\n>>> ")
        log.append("\n>>> Content-Type = ${request.body()?.contentType()}")
        log.append("\n>>> $request")
        log.append("\n>>> ${bodyToString(request.body())}")
        log.append("\n>>> ")
        log.append("\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n ")
        LogUtil.http(log.toString().replace(", ", "\n>>>         "))
    }

    // 打印响应数据
    private fun logResponseInfo(response: Response) {
        if (!DebugUtil.IS_DEBUG) {
            return
        }
        val charset: Charset
        charset = Charset.forName("UTF-8")
        val responseBody = response.peekBody(Long.MAX_VALUE)
        val jsonReader: Reader = InputStreamReader(responseBody.byteStream(), charset)
        val reader = BufferedReader(jsonReader)
        val log = java.lang.StringBuilder()
        log.append("\n \n \n>>>>>>>>>>>>>>>>>>>>>> Http-Response >>>>>>>>>>>>>>>>>>>>>>")
        log.append("\n>>> ")
        log.append("\n>>> url=${response.request().url()}")
        var line: String? = reader.readLine()
        while (line != null) {
            log.append("\n>>> $line")
            line = reader.readLine()
        }
        log.append("\n>>> ")
        log.append("\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n ")
        LogUtil.http(log.toString())
    }

    private fun bodyToString(request: RequestBody?): String? {
        if (request == null) {
            return ""
        } else {
            val buffer = Buffer()
            request.writeTo(buffer)
            return buffer.readUtf8()
        }
    }

    // ------------------------------------------------------------

    // TODO 如果有其它服务器地址,可以在mRetrofit.create前修改baseUrl
    // mRetrofit.newBuilder().baseUrl(AppInterface.BASE_URL).build()

    fun getAppInterface(): AppInterface {
        return mAppInterface
    }

}

Api 接口,retrofit 会帮忙生成代码的:

import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*

interface AppInterface {

	/**
     * 采用 @Url 覆盖 baseUrl
     */
    @GET
    fun getXxx(@Url url: String?): Call<ResponseBean<Any?>>

    /**
     * content-type application/x-www-form-urlencoded
     */
    @FormUrlEncoded
    @POST("xxx/xxx/xxx")
    fun checkForm(@FieldMap map: Map<String, @JvmSuppressWildcards Any?>?): Observable<ResponseBean<String>>
    
    @POST("xxx/xxx/xxx")
    fun checkInfo(@Body xxxRequestBean: XxxRequestBean?): Observable<ResponseBean<String>>

    /**
     * 文件下载
     * 采用 @Url 覆盖 baseUrl
     */
    @GET
    fun downloadFileDynamicRepos(@Url fileUrl: String): Call<ResponseBody>

    /**
     * 多文件下载
     * 采用 @Url 覆盖 baseUrl
     */
    @Streaming
    @GET
    fun downloadFileDynamicRepos(@Url fileUrlList: List<String>): Call<ResponseBody>

}

不知怎的,就是很喜欢用 fastjson 的注解,然后在代码混淆规则中,增加忽略被注解字段的混淆规则,妈妈再也不用担心开发忘记加 bean 类的混淆了:

import com.alibaba.fastjson.annotation.JSONField;

public class ResponseBean<T> {
    @JSONField(name = "code")
    public int code;
    @JSONField(name = "msg")
    public String msg;
    @JSONField(name = "data")
    public T data;
}

实际开发中,后台有统一格式的返回 bean,比如当 code=200 时能成功获取到所需数据,其他 code 则要有对应的情形提示或者操作,我们不可能每次都去写这些判断,所以要抽取统一的响应处理逻辑:

import io.reactivex.Observer
import io.reactivex.disposables.Disposable

interface ResponseObserver<T> : Observer<ResponseBean<T>> {

    override fun onSubscribe(d: Disposable) {

    }

    override fun onNext(t: ResponseBean<T>) {
        if (200 == t.code) {
            onResponseSuccess(t.data)
        } else {
            onResponseError(t.msg)
        }
    }

    fun onResponseError(msg: String?) {
        ToastUtil.showLong(msg)
    }

    fun onResponseSuccess(data: T?)

    override fun onComplete() {

    }

    override fun onError(e: Throwable) {
        ToastUtil.showLong(e.toString())
        onResponseError(e.toString())
    }
}

通过 Kotlin 的扩展功能,可以把一些通用操作抽取出来,使用时更为简洁:

import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers

fun <T> Observable<ResponseBean<T>>.defaultHttpRequest(observer: Observer<ResponseBean<T>>) {
    this
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer)
}

fun RetrofitHelper.Companion.getDefaultAppInterface(): AppInterface {
    return this.instance.getAppInterface()
}

使用示例:

	RetrofitHelper.instance.getAppInterface()
        	.checkInfo(xxxRequestBean)
        	.subscribeOn(Schedulers.io())
        	.observeOn(AndroidSchedulers.mainThread())
        	.subscribe(object : ResponseObserver<String> {
            	override fun onResponseSuccess(data: String?) {
					... ...
            	}
        	})

	RetrofitHelper.instance.getAppInterface()
            .downloadFileDynamicRepos(fileUrl)
            .enqueue(object : Callback<ResponseBody> {
                override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                    if (response.isSuccessful && response.body() != null) {
                        response.body()?.let {
                            val bytes = it.bytes()
                            ... ...
                        }
                    } else {
                        ... ...
                    }
                }

                override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                    ... ...
                }
            })

	RetrofitHelper.instance.getAppInterface()
            .getXxx("https://xxx/xxx/xxx")
            .enqueue(object : Callback<ResponseBean<Any?>> {
                override fun onResponse(call: Call<ResponseBean<Any?>>, response: Response<ResponseBean<Any?>>) {
                    if (response.isSuccessful && response.body() != null) {
                        response.body()?.let {
                             val responseMap = it.data as Map<*, *>
                             ... ...
                        }
                    }
                }

                override fun onFailure(call: Call<ResponseBean<Any?>>, t: Throwable) {
					... ...
                }
            })


没什么,就是觉得很想把 “.instance.” 抽出来,闲的 ~

  	RetrofitHelper.getDefaultAppInterface()
                .checkInfo(xxxRequestBean)
                .defaultHttpRequest(object : ResponseObserver<String> {
                    override fun onResponseSuccess(data: String?) {
						... ...
                    }
                })

代码混淆:

### Retrofit + RxJava + OkHttp
-dontwarn javax.annotation.**
-dontwarn javax.inject.**
-dontwarn okhttp3.logging.**
-keep class okhttp3.internal.**{*;}
-dontwarn okio.**
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontwarn okhttp3.**
-dontwarn sorg.codehaus.mojo.animal_sniffer.**
-dontwarn org.codehaus.**
-dontwarn java.nio.**
-dontwarn java.lang.invoke.**
-keep class rx.schedulers.Schedulers {
    public static <methods>;
}
-keep class rx.schedulers.Schedulers {
    public static ** test();
}
-keep class rx.schedulers.ImmediateScheduler {
    public <methods>;
}
-keep class rx.schedulers.TestScheduler {
    public <methods>;
}
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.stream.** { *; }
-keepattributes EnclosingMethod

后面有机会的话,想结合 Jetpack 和 Kotlin 协程弄一些不一样的东东 ~


参考文章:
1、扩展 - Kotlin 语言中文站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值