前言
RxHttp是基于RxJava2+Retrofit 2.9.0+OkHttp 4.9.0實(shí)現(xiàn)的輕量級(jí),完美兼容MVVM架構(gòu)的網(wǎng)絡(luò)請(qǐng)求封裝類庫(kù)嘶居,小巧精致罪帖,簡(jiǎn)單易用促煮,輕輕松松搞定網(wǎng)絡(luò)請(qǐng)求。
GitHub
https://github.com/kongpf8848/RxHttp
亮點(diǎn)
代碼量極少整袁,類庫(kù)大小不足100kb菠齿,但足以勝任大部分APP的網(wǎng)絡(luò)請(qǐng)求任務(wù),濃縮的都是精華啊^
完美兼容MVVM坐昙,MVC架構(gòu)绳匀,兼容Kotlin和Java,Kotlin+MVVM+RxHttp組合使用更酸爽炸客,MVVM官方推薦疾棵,抱緊Google大腿就對(duì)了
完美解決泛型類型擦除的棘手問(wèn)題,還原泛型的真實(shí)類型
天生支持網(wǎng)絡(luò)請(qǐng)求和Activity痹仙,F(xiàn)ragment生命周期綁定是尔,界面銷毀時(shí)自動(dòng)取消網(wǎng)絡(luò)請(qǐng)求回調(diào)
天生支持多BaseUrl,支持動(dòng)態(tài)傳入U(xiǎn)rl
支持自定義OkHttpClient.Builder开仰,可高度自定義網(wǎng)絡(luò)請(qǐng)求參數(shù)
支持Glide等和網(wǎng)絡(luò)請(qǐng)求公用一個(gè)OkHttpClient拟枚,充分利用OkHttpClient的線程池和連接池,大部分情況下一個(gè)App一個(gè)OkHttpClient就夠了
支持GET众弓,POST恩溅,PUT,DELETE等請(qǐng)求方式田轧,支持文件上傳及進(jìn)度監(jiān)聽(tīng)暴匠,支持同時(shí)上傳多個(gè)文件鞍恢,支持Uri上傳
支持文件下載及進(jìn)度監(jiān)聽(tīng)傻粘,支持大文件下載,支持?jǐn)帱c(diǎn)下載
使用要求
項(xiàng)目基于AndroidX帮掉,Java8+弦悉,minSdkVersion>=21
使用
implementation 'com.github.kongpf8848:RxHttp:1.0.11'
配置(可選)
RxHttpConfig.getInstance()
/**
* 失敗重試次數(shù)
*/
.maxRetries(3)
/**
* 每次失敗重試間隔時(shí)間
*/
.retryDelayMillis(200)
/**
* 自定義OkHttpClient.Builder(),RxHttp支持自定義OkHttpClient.Builder()蟆炊,
* 如不定義稽莉,則使用RxHttp默認(rèn)的OkHttpClient.Builder()
*/
.builder(OkHttpClient.Builder().apply {
connectTimeout(60, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(60, TimeUnit.SECONDS)
/**
* DEBUG模式下,添加日志攔截器涩搓,建議使用RxHttp中的FixHttpLoggingInterceptor污秆,使用OkHttp的HttpLoggingInterceptor在上傳下載的時(shí)候會(huì)有IOException問(wèn)題
*/
if (BuildConfig.DEBUG) {
addInterceptor(FixHttpLoggingInterceptor().apply {
level = FixHttpLoggingInterceptor.Level.BODY
})
}
})
基礎(chǔ)使用
- GET/POST/PUT/DELETE/上傳請(qǐng)求
RxHttp.getInstance()
/**
* get:請(qǐng)求類型,可為get,post,put,delete,upload昧甘,分別對(duì)應(yīng)GET/POST/PUT/DELETE/上傳請(qǐng)求
* context:上下文良拼,可為Context,Activity或Fragment類型充边,當(dāng)context為Activity或Fragment時(shí)網(wǎng)絡(luò)請(qǐng)求和生命周期綁定
*/
.get(context)
/**
* 請(qǐng)求url庸推,如https://www.baidu.com
*/
.url("xxx")
/**
*請(qǐng)求參數(shù)鍵值對(duì),類型為Map<String, Any?>?,如hashMapOf("name" to "jack")
*/
.params(map)
/**
*每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)的tag值贬媒,可為null聋亡,用于后續(xù)手動(dòng)根據(jù)tag取消指定網(wǎng)絡(luò)請(qǐng)求
*/
.tag("xxx")
/**
* HttpCallback:網(wǎng)絡(luò)回調(diào),參數(shù)xxx為返回?cái)?shù)據(jù)對(duì)應(yīng)的數(shù)據(jù)模型际乘,
* 類似RxJava中的Observer坡倔,onComplete只有在onNext回調(diào)之后執(zhí)行,如發(fā)生錯(cuò)誤則只會(huì)回調(diào)onError而不會(huì)執(zhí)行onComplete
*/
.enqueue(object : HttpCallback<xxx>() {
/**
* http請(qǐng)求開(kāi)始時(shí)回調(diào)
*/
override fun onStart() {
}
/**
* http請(qǐng)求成功時(shí)回調(diào)
*/
override fun onNext(response: xxx?) {
}
/**
* http請(qǐng)求失敗時(shí)回調(diào)
*/
override fun onError(e: Throwable?) {
}
/**
* http請(qǐng)求成功完成時(shí)回調(diào)
*/
override fun onComplete() {
}
/**
* 上傳進(jìn)度回調(diào),請(qǐng)求類型為upload時(shí)才會(huì)回調(diào)
*/
override fun onProgress(readBytes: Long, totalBytes: Long) {
}
})
- 下載請(qǐng)求
RxHttp.getInstance()
/**
* download:請(qǐng)求類型蚓庭,下載請(qǐng)求
* context:上下文致讥,如不需要和生命周期綁定,應(yīng)該傳遞applicationContext
*/
.download(context)
/**
* 保存路徑
*/
.dir(dir)
/**
*保存文件名稱
*/
.filename(filename)
/**
* 是否為斷點(diǎn)下載,默認(rèn)為false
*/
.breakpoint(true)
/**
* 下載地址器赞,如http://study.163.com/pub/ucmooc/ucmooc-android-official.apk
*/
.url(url)
/**
* 請(qǐng)求Tag
*/
.tag(null)
/**
* 下載回調(diào)
*/
.enqueue(object: DownloadCallback() {
/**
* 下載開(kāi)始時(shí)回調(diào)
*/
override fun onStart() {
}
/**
* 下載完成時(shí)回調(diào)
*/
override fun onNext(response: DownloadInfo?) {
}
/**
* 下載失敗時(shí)回調(diào)
*/
override fun onError(e: Throwable?) {
}
/**
* 下載完成之后回調(diào)
*/
override fun onComplete() {
}
/**
* 下載進(jìn)度回調(diào)
*/
override fun onProgress(readBytes: Long, totalBytes: Long) {
}
})
- 取消請(qǐng)求
/**
* tag:Any?,請(qǐng)求Tag,對(duì)應(yīng)網(wǎng)絡(luò)請(qǐng)求里的Tag值
* 如不為null,則取消指定網(wǎng)絡(luò)請(qǐng)求,
* 如為null垢袱,則取消所有網(wǎng)絡(luò)請(qǐng)求
*/
RxHttp.getInstance().cancelRequest(tag)
項(xiàng)目實(shí)戰(zhàn)
此處假設(shè)服務(wù)端返回的數(shù)據(jù)格式為{"code":xxx,"data":T,"msg":""},其中code為響應(yīng)碼港柜,整型请契,等于200時(shí)為成功,其余為失敗夏醉,data對(duì)應(yīng)的數(shù)據(jù)類型為泛型(boolean爽锥,int,double畔柔,String氯夷,對(duì)象{ },數(shù)組[ ]等類型)
{
"code": 200,
"data":T,
"msg": ""
}
對(duì)應(yīng)的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項(xiàng)目
- 定義MVCHttpCallback,用于將網(wǎng)絡(luò)請(qǐng)求結(jié)果回調(diào)給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 } /** * 請(qǐng)求開(kāi)始時(shí)回調(diào)靶擦,可以在此加載loading對(duì)話框等,默認(rèn)為空實(shí)現(xiàn) */ open fun onStart() {} /** * 抽象方法腮考,請(qǐng)求成功回調(diào),返回內(nèi)容為泛型玄捕,對(duì)應(yīng)TKResponse的data */ abstract fun onSuccess(result: T?) /** * 抽象方法踩蔚,請(qǐng)求失敗回調(diào),返回內(nèi)容為code(錯(cuò)誤碼)枚粘,msg(錯(cuò)誤信息) */ abstract fun onFailure(code: Int, msg: String?) /** * 上傳進(jìn)度回調(diào)馅闽,默認(rèn)為空實(shí)現(xiàn) */ open fun onProgress(readBytes: Long, totalBytes: Long) {} /** * 請(qǐng)求完成時(shí)回調(diào),請(qǐng)求成功之后才會(huì)回調(diào)此方法馍迄,默認(rèn)為空實(shí)現(xiàn) */ open fun onComplete() {} }
- 定義網(wǎng)絡(luò)接口,封裝GET/POST等網(wǎng)絡(luò)請(qǐng)求
object MVCApi { /** * GET請(qǐng)求 * context:上下文 * url:請(qǐng)求url * params:參數(shù)列表福也,可為null * tag:標(biāo)識(shí)一個(gè)網(wǎng)絡(luò)請(qǐng)求 * callback:網(wǎng)絡(luò)請(qǐng)求回調(diào) */ 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請(qǐng)求 * context:上下文 * url:請(qǐng)求url * params:參數(shù)列表,可為null * tag:標(biāo)識(shí)一個(gè)網(wǎng)絡(luò)請(qǐng)求 * callback:網(wǎng)絡(luò)請(qǐng)求回調(diào) */ 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中調(diào)用網(wǎng)絡(luò)接口
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中有詳細(xì)的示例演示MVC項(xiàng)目如何使用RxHttp
-
MVVM項(xiàng)目
- 定義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?) {} /** * 創(chuàng)建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) { /** * 網(wǎng)絡(luò)倉(cāng)庫(kù) */ protected val networkbaseRepository: NetworkRepository = NetworkRepository.instance /** * 上下文 */ protected var context: Context = application.applicationContext }
- 定義網(wǎng)絡(luò)倉(cāng)庫(kù)暴凑,封裝網(wǎng)絡(luò)接口
/** * MVVM架構(gòu)網(wǎng)絡(luò)倉(cāng)庫(kù) * 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對(duì)應(yīng)的LiveData數(shù)據(jù)被覆蓋, * 在TKState類handle方法里會(huì)特別處理回調(diào)的量承,放心好了 */ } 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 } /** *上傳 *支持上傳多個(gè)文件,map中對(duì)應(yīng)的value類型為File類型或Uri類型 *支持監(jiān)聽(tīng)上傳進(jìn)度 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:上下文,如不需要和生命周期綁定,應(yīng)該傳遞applicationContext * url:下載地址 * dir:本地目錄路徑 * filename:保存文件名稱 * callback:下載進(jìn)度回調(diào) * md5:下載文件的MD5值 * breakpoint:是否支持?jǐn)帱c(diǎn)下載,默認(rèn)為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類搬设,用于將網(wǎng)絡(luò)回調(diào)轉(zhuǎn)化為L(zhǎng)iveData
/** *將HttpCallback回調(diào)轉(zhuǎn)化為對(duì)應(yīng)的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) } } }
- 經(jīng)過(guò)一系列封裝穴店,最后在View層如Activity中ViewModel調(diào)用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中有詳細(xì)的示例演示MVVM項(xiàng)目如何使用RxHttp