RxHttp

Project Url: kongpf8848/RxHttp
Introduction: 基于 RxJava2+Retrofit+OkHttp4.x 封装的网络请求类库,亮点多多,完美兼容 MVVM(ViewModel,LiveData),天生支持网络请求和生命周期绑定,天生支持多 BaseUrl,支持文件上传下载进度监听,支持断点下载,支持 Glide 和网络请求公用一个 OkHttpClient
More: Author   ReportBugs   
Tags:

基于 RxJava2+Retrofit 2.9.0+OkHttp 4.9.0 实现的轻量级,完美兼容 MVVM 架构的网络请求封装类库,小巧精致,简单易用,网络请求原来如此简单:smirk::smirk::smirk:

亮点

  • 代码量极少,类库体积不足 100kb,但足以胜任大部分 APP 的网络请求任务,浓缩的都是精华啊^

  • 完美兼容 MVVM,MVC 架构,兼容 Kotlin 和 Java,Kotlin+MVVM+RxHttp 结合起来使用更酸爽,MVVM 官方推荐,抱紧 Google 大腿就对了

  • 完美解决泛型类型擦除的棘手问题,还原泛型的真实类型

  • 天生支持网络请求和 Activity,Fragment 生命周期绑定,界面销毁时自动取消网络请求回调

  • 天生支持多 BaseUrl,支持动态传入 Url

  • 支持自定义 OkHttpClient.Builder,可高度自定义网络请求参数,支持 Https 证书单向校验(客户端校验服务端证书)

  • 支持 Glide 等和 Http 请求公用一个 OkHttpClient,充分利用 OkHttpClient 的线程池和连接池,大部分情况下一个 App 一个 OkHttpClient 就够了

  • 支持 GET,POST,PUT,DELETE 等请求方式,支持文件上传及进度监听,支持同时上传多个文件,支持 Uri 上传

  • 支持文件下载及进度监听,支持大文件下载,支持断点下载

使用要求

项目基于 AndroidX,Java8+,minSdkVersion>=21

使用

  • 在项目根目录的 build.gradle 文件中添加:
    allprojects {
      repositories {
          mavenCentral()
      }
    }
    
  • 在具体 Module 的 build.gradle 文件中添加:
    implementation 'io.github.kongpf8848:RxHttp:1.0.12'
    

配置(可选)

  RxHttpConfig.getInstance()
    /**
     * 失败重试次数
     */
    .maxRetries(3)
    /**
     * 每次失败重试间隔时间
     */
    .retryDelayMillis(200)
    /**
     * Https 证书校验,单向校验,即客户端校验服务端证书,null 则为不校验
     */
     //.certificate(AssetUtils.openFile(applicationContext,"xxx.cer"))
    /**
     * 设置 OkHttpClient.Builder(),RxHttp 支持自定义 OkHttpClient.Builder()
     */
    .getBuilder().apply {
        connectTimeout(60, TimeUnit.SECONDS)
        readTimeout(60, TimeUnit.SECONDS)
        writeTimeout(60, TimeUnit.SECONDS)
        /**
         * DEBUG 模式下,添加日志拦截器,建议使用 RxHttp 中的 FixHttpLoggingInterceptor,使用 OkHttp 的 HttpLoggingInterceptor 在上传下载的时候会有 IOException 问题
         */
        if (BuildConfig.DEBUG) {
            addInterceptor(FixHttpLoggingInterceptor().apply {
                level = FixHttpLoggingInterceptor.Level.BODY
            })
        }
    }

基础使用

  • GET/POST/PUT/DELETE/上传请求
   RxHttp.getInstance()
    /**
     * get:请求类型,可为 get,post,put,delete,upload,分别对应 GET/POST/PUT/DELETE/上传请求
     * context:上下文,可为 Context,Activity 或 Fragment 类型,当 context 为 Activity 或 Fragment 时网络请求和生命周期绑定
     */
    .get(context)
    /**
     * 请求 url,如 https://www.baidu.com
     */
    .url("xxx")
    /**
     *请求参数键值对,类型为 Map<String, Any?>?,如 hashMapOf("name" to "jack")
     */
    .params(map)
    /**
     *每个网络请求对应的 tag 值,可为 null,用于后续手动根据 tag 取消指定网络请求
     */
    .tag("xxx")
    /**
     * HttpCallback:网络回调,参数 xxx 为返回数据对应的数据模型,
     * 类似 RxJava 中的 Observer,onComplete 只有在 onNext 回调之后执行,如发生错误则只会回调 onError 而不会执行 onComplete
     */
    .enqueue(object : HttpCallback<xxx>() {
        /**
         * http 请求开始时回调
         */
        override fun onStart() {

        }

        /**
         * http 请求成功时回调
         */
        override fun onNext(response: xxx?) {

        }

        /**
         * http 请求失败时回调
         */
        override fun onError(e: Throwable?) {

        }

        /**
         * http 请求成功完成时回调
         */
        override fun onComplete() {

        }

        /**
         * 上传进度回调,请求类型为 upload 时才会回调
         */
        override fun onProgress(readBytes: Long, totalBytes: Long) {

        }
    })
  • 下载请求
   RxHttp.getInstance()
      /**
       * download:请求类型,下载请求
       * context:上下文,如不需要和生命周期绑定,应该传递 applicationContext
       */
      .download(context)
      /**
       * 保存路径
       */
      .dir(dir)
      /**
       *保存文件名称
       */
      .filename(filename)
      /**
       * 是否为断点下载,默认为 false
       */
      .breakpoint(true)
      /**
       * 下载地址,如 http://study.163.com/pub/ucmooc/ucmooc-android-official.apk
       */
      .url(url)
      /**
       * 请求 Tag
       */
      .tag(null)
      /**
       * 下载回调
       */
      .enqueue(object: DownloadCallback() {
          /**
           * 下载开始时回调
           */
          override fun onStart() {

          }

          /**
           * 下载完成时回调
           */
          override fun onNext(response: DownloadInfo?) {

          }

          /**
           * 下载失败时回调
           */
          override fun onError(e: Throwable?) {

          }

          /**
           * 下载完成之后回调
           */
          override fun onComplete() {

          }

          /**
           * 下载进度回调
           */
          override fun onProgress(readBytes: Long, totalBytes: Long) {

          }

      })
  • 取消请求
    /**
     * tag:Any?,请求 Tag,对应网络请求里的 Tag 值
     * 如不为 null,则取消指定网络请求,
     * 如为 null,则取消所有网络请求
     */
    RxHttp.getInstance().cancelRequest(tag)

项目实战

此处假设服务端返回的数据格式为{"code":xxx,"data":T,"msg":""},其中 code 为响应码,整型,等于 200 时为成功,其余为失败,data 对应的数据类型为泛型(boolean,int,double,String,对象{ },数组[ ]等类型)

 {
    "code": 200,
    "data":T,
    "msg": ""
}

对应的 Response 类为

class TKResponse<T>(val code:Int,val msg: String?, val data: T?) : Serializable {
    companion object{
        const val STATUS_OK=200
    }
    fun isSuccess():Boolean{
        return code== STATUS_OK
    }
}
  • MVC 项目

    • 定义 MVCHttpCallback,用于将网络请求结果回调给 UI 界面
```kotlin

abstract class MVCHttpCallback {

    private val type: Type

    init {
        val arg = TypeUtil.getType(javaClass)
        type = TypeBuilder
                .newInstance(TKResponse::class.java)
                .addTypeParam(arg)
                .build()
    }

    fun getType(): Type {
        return this.type
    }

    /**
     * 请求开始时回调,可以在此加载 loading 对话框等,默认为空实现
     */
    open fun onStart() {}

    /**
     * 抽象方法,请求成功回调,返回内容为泛型,对应 TKResponse 的 data
     */
    abstract fun onSuccess(result: T?)

    /**
     * 抽象方法,请求失败回调,返回内容为 code(错误码),msg(错误信息)
     */
    abstract fun onFailure(code: Int, msg: String?)

    /**
     * 上传进度回调,默认为空实现
     */
    open fun onProgress(readBytes: Long, totalBytes: Long) {}

    /**
     * 请求完成时回调,请求成功之后才会回调此方法,默认为空实现
     */
    open fun onComplete() {}

}


   * 定义网络接口,封装 GET/POST 等网络请求


   ```kotlin
   object MVCApi {

       /**
        * GET 请求
        * context:上下文
        * url:请求 url
        * params:参数列表,可为 null
        * tag:标识一个网络请求
        * callback:网络请求回调
        */
       inline fun <reified T> httpGet(
               context: Context,
               url: String,
               params: Map<String, Any?>?,
               tag: Any? = null,
               callback: MVCHttpCallback<T>
       ) {
           RxHttp.getInstance().get(context)
                   .url(url)
                   .params(params)
                   .tag(tag)
                   .enqueue(simpleHttpCallback(callback))
       }

       /**
        * POST 请求
        * context:上下文
        * url:请求 url
        * params:参数列表,可为 null
        * tag:标识一个网络请求
        * callback:网络请求回调
        */
       inline fun <reified T> httpPost(
               context: Context,
               url: String,
               params: Map<String, Any?>?,
               tag: Any? = null,
               callback: MVCHttpCallback<T>
       ) {
           RxHttp.getInstance().post(context)
                   .url(url)
                   .params(params)
                   .tag(tag)
                   .enqueue(simpleHttpCallback(callback))
       }

       ......

        inline fun <reified T> simpleHttpCallback(callback: MVCHttpCallback<T>): HttpCallback<TKResponse<T>> {
           return object : HttpCallback<TKResponse<T>>(callback.getType()) {
               override fun onStart() {
                   super.onStart()
                   callback.onStart()
               }

               override fun onNext(response: TKResponse<T>?) {
                   if (response != null) {
                       if (response.isSuccess()) {
                           callback.onSuccess(response.data)
                       } else {
                           return onError(ServerException(response.code, response.msg))
                       }

                   } else {
                       return onError(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
                   }

               }

               override fun onError(e: Throwable?) {
                   handleThrowable(e).run {
                       callback.onFailure(first, second)
                   }
               }

               override fun onComplete() {
                   super.onComplete()
                   callback.onComplete()
               }

               override fun onProgress(readBytes: Long, totalBytes: Long) {
                   super.onProgress(readBytes, totalBytes)
                   callback.onProgress(readBytes, totalBytes)
               }
           }
       }
 * 在 View 层如 Activity 中调用网络接口


 ```kotlin
 MVCApi.httpGet(
     context = baseActivity,
     url = TKURL.URL_GET,
     params = null,
     tag = null, 
     callback = object : MVCHttpCallback<List<Banner>>() {
     override fun onStart() {
         LogUtils.d(TAG, "onButtonGet onStart() called")
     }

     override fun onSuccess(result: List<Banner>?) {
         Log.d(TAG, "onButtonGet onSuccess() called with: result = $result")
     }

     override fun onFailure(code: Int, msg: String?) {
         Log.d(TAG, "onButtonGet onFailure() called with: code = $code, msg = $msg")
     }

     override fun onComplete() {
         Log.d(TAG, "onButtonGet onComplete() called")
     }

 })
 ```

 **具体使用可以参考 demo 代码,demo 中有详细的示例演示 MVC 项目如何使用 RxHttp**
  • MVVM 项目

    • 定义 Activity 基类 BaseMvvmActivity

      abstract class BaseMvvmActivity<VM : BaseViewModel, VDB : ViewDataBinding> : AppCompatActivity(){
      
       lateinit var viewModel: VM
       lateinit var binding: VDB
      
       protected abstract fun getLayoutId(): Int
      
       final override fun onCreate(savedInstanceState: Bundle?) {
           onCreateStart(savedInstanceState)
           super.onCreate(savedInstanceState)
           binding = DataBindingUtil.setContentView(this, getLayoutId())
           binding.lifecycleOwner = this
           createViewModel()
           onCreateEnd(savedInstanceState)
       }
      
       protected open fun onCreateStart(savedInstanceState: Bundle?) {}
       protected open fun onCreateEnd(savedInstanceState: Bundle?) {}
      
       /**
        * 创建 ViewModel
        */
       private fun createViewModel() {
           val type = findType(javaClass.genericSuperclass)
           val modelClass = if (type is ParameterizedType) {
               type.actualTypeArguments[0] as Class<VM>
           } else {
               BaseViewModel::class.java as Class<VM>
           }
           viewModel = ViewModelProvider(this).get(modelClass)
       }
      
       private fun findType(type: Type): Type?{
           return when(type){
               is ParameterizedType -> type
               is Class<*> ->{
               findType(type.genericSuperclass)
               }
               else ->{
               null
               }
           }
       }
      
      }
      
    • 定义 ViewModel 的基类 BaseViewModel

      open class BaseViewModel(application: Application) : AndroidViewModel(application) {
      
       /**
        * 网络仓库
        */
       protected val networkbaseRepository: NetworkRepository = NetworkRepository.instance
      
       /**
        * 上下文
        */
       protected var context: Context = application.applicationContext
      
      }
      
    • 定义网络仓库,封装网络接口 ```kotlin /**
    • MVVM 架构网络仓库
    • UI->ViewModel->Repository->LiveData(ViewModel)->UI */ class NetworkRepository private constructor() {

      companion object {

       val instance = NetworkRepository.holder
      

      }

      private object NetworkRepository {

       val holder = NetworkRepository()
      

      }

      inline fun wrapHttpCallback(): MvvmHttpCallback {

       return object : MvvmHttpCallback<T>() {
      
       }
      

      }

      inline fun newCallback(liveData: MutableLiveData>): HttpCallback> {

       val type = wrapHttpCallback<T>().getType()
       return object : HttpCallback<TKResponse<T>>(type) {
           override fun onStart() {
           liveData.value = TKState.start()
           }
      
           override fun onNext(response: TKResponse<T>?) {
           liveData.value = TKState.response(response)
           }
      
           override fun onError(e: Throwable?) {
           liveData.value = TKState.error(e)
           }
      
           override fun onComplete() {
      
           /**
            * 亲,此处不要做任何操作,不要给 LiveData 赋值,防止 onNext 对应的 LiveData 数据被覆盖,
            * 在 TKState 类 handle 方法里会特别处理回调的,放心好了
            */
           }
      
           override fun onProgress(readBytes: Long, totalBytes: Long) {
           liveData.value = TKState.progress(readBytes, totalBytes)
           }
       }
      

      }

      inline fun httpGet(

       context: Context,
       url: String,
       params: Map<String, Any?>?,
       tag: Any? = null
      

      ): MutableLiveData> {

       val liveData = MutableLiveData<TKState<T>>()
       RxHttp.getInstance()
           .get(context)
           .url(url)
           .params(params)
           .tag(tag)
           .enqueue(newCallback(liveData))
       return liveData
      

      }

    inline fun <reified T> httpPost(
        context: Context,
        url: String,
        params: Map<String, Any?>?,
        tag: Any? = null
    ): MutableLiveData<TKState<T>> {
        val liveData = MutableLiveData<TKState<T>>()
        RxHttp.getInstance().post(context)
            .url(url)
            .params(params)
            .tag(tag)
            .enqueue(newCallback(liveData))
        return liveData
    }

    inline fun <reified T> httpPostForm(
        context: Context,
        url: String,
        params: Map<String, Any?>?,
        tag: Any? = null
    ): MutableLiveData<TKState<T>> {
        val liveData = MutableLiveData<TKState<T>>()
        RxHttp.getInstance().postForm(context)
            .url(url)
            .params(params)
            .tag(tag)
            .enqueue(newCallback(liveData))
        return liveData
    }

    inline fun <reified T> httpPut(
        context: Context,
        url: String,
        params: Map<String, Any?>?,
        tag: Any? = null
    ): MutableLiveData<TKState<T>> {
        val liveData = MutableLiveData<TKState<T>>()
        RxHttp.getInstance().put(context)
            .url(url)
            .params(params)
            .tag(tag)
            .enqueue(newCallback(liveData))
        return liveData
    }

    inline fun <reified T> httpDelete(
        context: Context,
        url: String,
        params: Map<String, Any?>?,
        tag: Any? = null
    ): MutableLiveData<TKState<T>> {
        val liveData = MutableLiveData<TKState<T>>()
        RxHttp.getInstance().delete(context)
            .params(params)
            .url(url)
            .tag(tag)
            .enqueue(newCallback(liveData))
        return liveData
    }


    /**
     *上传
     *支持上传多个文件,map 中对应的 value 类型为 File 类型或 Uri 类型
     *支持监听上传进度
        val map =Map<String,Any>()
        map.put("model", "xiaomi")
        map.put("os", "android")
        map.put("avatar",File("xxx"))
        map.put("video",uri)
     */
    inline fun <reified T> httpUpload(
        context: Context,
        url: String,
        params: Map<String, Any?>?,
        tag: Any? = null
    ): MutableLiveData<TKState<T>> {
        val liveData = MutableLiveData<TKState<T>>()
        RxHttp.getInstance().upload(context)
            .url(url)
            .params(params)
            .tag(tag)
            .enqueue(newCallback(liveData))
        return liveData
    }


    /**
     * 下载
     * context:上下文,如不需要和生命周期绑定,应该传递 applicationContext
     * url:下载地址
     * dir:本地目录路径
     * filename:保存文件名称
     * callback:下载进度回调
     * md5:下载文件的 MD5 值
     * breakpoint:是否支持断点下载,默认为 true
     */
    fun httpDownload(context: Context, url: String, dir: String, filename: String, callback: DownloadCallback, md5: String? = null, breakPoint: Boolean = true, tag: Any? = null) {
        RxHttp.getInstance().download(context).dir(dir).filename(filename).breakpoint(breakPoint).md5(md5).url(url).tag(tag).enqueue(callback)
    }

}
 ```
 * 定义 TKState 类,用于将网络回调转化为 LiveData

 ```kotlin
 /**
  *将 HttpCallback 回调转化为对应的 LiveData
 */
class TKState<T> {

    var state: Int = 0
    var code = TKErrorCode.ERRCODE_UNKNOWN
    var msg: String? = null
    var data: T? = null
    var progress: Long = 0
    var total: Long = 0

    @JvmOverloads
    constructor(state: Int, data: T? = null, msg: String? = "") {
        this.state = state
        this.data = data
        this.msg = msg
    }

    constructor(state: Int, throwable: Throwable?) {
        this.state = state
        handleThrowable(throwable).run {
            this@TKState.code = first
            this@TKState.msg = second
        }
    }

    constructor(state: Int, progress: Long, total: Long) {
        this.state = state
        this.progress = progress
        this.total = total
    }

    fun handle(handleCallback: HandleCallback<T>.() -> Unit) {
        val callback = HandleCallback<T>()
        callback.apply(handleCallback)
        when (state) {
            START -> {
            callback.onStart?.invoke()
            }
            SUCCESS -> {
            callback.onSuccess?.invoke(data)
            }
            FAIL -> {
            callback.onFailure?.invoke(code, msg)
            }
            PROGRESS -> {
            callback.onProgress?.invoke(progress, total)
            }
        }
        if (state == SUCCESS || state == FAIL) {
            callback.onComplete?.invoke()
        }
    }

    open class HandleCallback<T> {
        var onStart: (() -> Unit)? = null
        var onSuccess: ((T?) -> Unit)? = null
        var onFailure: ((Int, String?) -> Unit)? = null
        var onComplete: (() -> Unit)? = null
        var onProgress: ((Long, Long) -> Unit)? = null

        fun onStart(callback: (() -> Unit)?) {
            this.onStart = callback
        }

        fun onSuccess(callback: ((T?) -> Unit)?) {
             this.onSuccess = callback
        }

        fun onFailure(callback: ((Int, String?) -> Unit)?) {
            this.onFailure = callback
        }

        fun onComplete(callback: (() -> Unit)?) {
            this.onComplete = callback
        }

        fun onProgress(callback: ((Long, Long) -> Unit)?) {
            this.onProgress = callback
        }
    }

    companion object {
        const val START = 0
        const val SUCCESS = 1
        const val FAIL = 2
        const val PROGRESS = 3

        fun <T> start(): TKState<T> {
         return TKState(START)
        }

        fun <T> response(response: TKResponse<T>?): TKState<T> {
            if (response != null) {
            if (response.isSuccess()) {
                return TKState(SUCCESS, response.data, null)
            } else {
                return error(ServerException(response.code, response.msg))
            }

            } else {
            return error(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
            }

        }

        fun <T> error(t: Throwable?): TKState<T> {
            return TKState(FAIL, t)
        }

        fun <T> progress(progress: Long, total: Long): TKState<T> {
            return TKState(PROGRESS, progress, total)
        }
    }

}
 ```

 * 经过一系列封装,参考 demo 代码,最后在 View 层如 Activity 中 ViewModel 调用 Repository 中的接口
 ```kotlin
  viewModel.testPost(hashMapOf(
            "name" to "jack",
            "location" to "shanghai",
            "age" to 28)
    )
    .observeState(this) {
        onStart {
        LogUtils.d(TAG, "onButtonPost() onStart called")
        }
        onSuccess {
        LogUtils.d(TAG, "onButtonPost() onSuccess called:${it}")
        }
        onFailure { code, msg ->
        ToastHelper.toast("onButtonPost() onFailure,code:${code},msg:${msg}")
        }
        onComplete {
        LogUtils.d(TAG, "onButtonPost() onComplete called")
        }
    }
 ```

 **具体使用可以参考 demo 代码,demo 中有详细的示例演示 MVVM 项目如何使用 RxHttp**

强烈建议下载 Demo 代码,Demo 中有详细的示例,演示 MVVM 及 MVC 架构如何使用 RxHttp,如有问题可私信我,简书 掘金

License

Copyright (C) 2021 kongpf8848

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools