项目地址:kongpf8848/RxHttp
简介: 基于 RxJava2+Retrofit+OkHttp4.x 封装的网络请求类库,亮点多多,完美兼容 MVVM(ViewModel,LiveData),天生支持网络请求和生命周期绑定,天生支持多 BaseUrl,支持文件上传下载进度监听,支持断点下载,支持 Glide 和网络请求公用一个 OkHttpClient
更多:作者 提 Bug
标签:
基于 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
allprojects {
repositories {
mavenCentral()
}
}
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
})
}
}
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?,如 hashMapOf("name" to "jack")
*/
.params(map)
/**
*每个网络请求对应的 tag 值,可为 null,用于后续手动根据 tag 取消指定网络请求
*/
.tag("xxx")
/**
* HttpCallback:网络回调,参数 xxx 为返回数据对应的数据模型,
* 类似 RxJava 中的 Observer,onComplete 只有在 onNext 回调之后执行,如发生错误则只会回调 onError 而不会执行 onComplete
*/
.enqueue(object : HttpCallback() {
/**
* 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(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 项目
```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 httpGet(
context: Context,
url: String,
params: Map?,
tag: Any? = null,
callback: MVCHttpCallback
) {
RxHttp.getInstance().get(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(simpleHttpCallback(callback))
}
/**
* POST 请求
* context:上下文
* url:请求 url
* params:参数列表,可为 null
* tag:标识一个网络请求
* callback:网络请求回调
*/
inline fun httpPost(
context: Context,
url: String,
params: Map?,
tag: Any? = null,
callback: MVCHttpCallback
) {
RxHttp.getInstance().post(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(simpleHttpCallback(callback))
}
......
inline fun simpleHttpCallback(callback: MVCHttpCallback): HttpCallback> {
return object : HttpCallback>(callback.getType()) {
override fun onStart() {
super.onStart()
callback.onStart()
}
override fun onNext(response: TKResponse?) {
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>() {
override fun onStart() {
LogUtils.d(TAG, "onButtonGet onStart() called")
}
override fun onSuccess(result: List?) {
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 : 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
} else {
BaseViewModel::class.java as Class
}
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
}
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() {
}
}
inline fun newCallback(liveData: MutableLiveData>): HttpCallback> {
val type = wrapHttpCallback().getType()
return object : HttpCallback>(type) {
override fun onStart() {
liveData.value = TKState.start()
}
override fun onNext(response: TKResponse?) {
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?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
RxHttp.getInstance()
.get(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(newCallback(liveData))
return liveData
}
inline fun httpPost(
context: Context,
url: String,
params: Map?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
RxHttp.getInstance().post(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(newCallback(liveData))
return liveData
}
inline fun httpPostForm(
context: Context,
url: String,
params: Map?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
RxHttp.getInstance().postForm(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(newCallback(liveData))
return liveData
}
inline fun httpPut(
context: Context,
url: String,
params: Map?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
RxHttp.getInstance().put(context)
.url(url)
.params(params)
.tag(tag)
.enqueue(newCallback(liveData))
return liveData
}
inline fun httpDelete(
context: Context,
url: String,
params: Map?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
RxHttp.getInstance().delete(context)
.params(params)
.url(url)
.tag(tag)
.enqueue(newCallback(liveData))
return liveData
}
/**
*上传
*支持上传多个文件,map 中对应的 value 类型为 File 类型或 Uri 类型
*支持监听上传进度
val map =Map()
map.put("model", "xiaomi")
map.put("os", "android")
map.put("avatar",File("xxx"))
map.put("video",uri)
*/
inline fun httpUpload(
context: Context,
url: String,
params: Map?,
tag: Any? = null
): MutableLiveData> {
val liveData = MutableLiveData>()
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 {
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 {
[email protected] = first
[email protected] = second
}
}
constructor(state: Int, progress: Long, total: Long) {
this.state = state
this.progress = progress
this.total = total
}
fun handle(handleCallback: HandleCallback.() -> Unit) {
val callback = HandleCallback()
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 {
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 start(): TKState {
return TKState(START)
}
fun response(response: TKResponse?): TKState {
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 error(t: Throwable?): TKState {
return TKState(FAIL, t)
}
fun progress(progress: Long, total: Long): TKState {
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**
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.