RxHttp - 輕量級、可擴充套件、易使用、完美相容MVVM、MVC架構的網路封裝類庫

南極冰川雪發表於2021-02-23

前言

RxHttp是基於RxJava2+Retrofit 2.9.0+OkHttp 4.9.0實現的輕量級,完美相容MVVM架構的網路請求封裝類庫,小巧精緻,簡單易用,輕輕鬆鬆搞定網路請求。

GitHub

https://github.com/kongpf8848/RxHttp

亮點

  • 程式碼量極少,類庫大小不足100kb,但足以勝任大部分APP的網路請求任務,濃縮的都是精華啊_^_

  • 完美相容MVVM,MVC架構,相容Kotlin和Java,Kotlin+MVVM+RxHttp組合使用更酸爽,MVVM官方推薦,抱緊Google大腿就對了

  • 完美解決泛型型別擦除的棘手問題,還原泛型的真實型別

  • 天生支援網路請求和Activity,Fragment生命週期繫結,介面銷燬時自動取消網路請求回撥

  • 天生支援多BaseUrl,支援動態傳入Url

  • 支援自定義OkHttpClient.Builder,可高度自定義網路請求引數

  • 支援Glide等和網路請求公用一個OkHttpClient,充分利用OkHttpClient的執行緒池和連線池,大部分情況下一個App一個OkHttpClient就夠了

  • 支援GET,POST,PUT,DELETE等請求方式,支援檔案上傳及進度監聽,支援同時上傳多個檔案,支援Uri上傳

  • 支援檔案下載及進度監聽,支援大檔案下載,支援斷點下載

使用要求

專案基於AndroidX,Java8+,minSdkVersion>=21

使用

implementation 'com.github.kongpf8848:RxHttp:1.0.11'

配置(可選)

  RxHttpConfig.getInstance()
    /**
     * 失敗重試次數
     */
    .maxRetries(3)
    /**
     * 每次失敗重試間隔時間
     */
    .retryDelayMillis(200)
    /**
     * 自定義OkHttpClient.Builder(),RxHttp支援自定義OkHttpClient.Builder(),
     * 如不定義,則使用RxHttp預設的OkHttpClient.Builder()
     */
    .builder(OkHttpClient.Builder().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介面
    abstract class MVCHttpCallback<T> {
    
        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等網路請求
    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中呼叫網路介面
    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
    
    }
    
    • 定義網路倉庫,封裝網路介面
    /**
    * 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 <reified T> wrapHttpCallback(): MvvmHttpCallback<T> {
       	return object : MvvmHttpCallback<T>() {
    
       	}
       }
    
       inline fun <reified T> newCallback(liveData: MutableLiveData<TKState<T>>): HttpCallback<TKResponse<T>> {
       	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 <reified T> httpGet(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
       	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
    /**
     *將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)
       	}
       }
    
    }
    
    • 經過一系列封裝,最後在View層如Activity中ViewModel呼叫Repository中的介面
     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,如果本文對你有幫助,可以考慮給我點贊哦

Demo

https://github.com/kongpf8848/RxHttp

相關文章