前言
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