1.定义统一的返回结果类 ResultData
data class ResultData<T>(var code: Int, var message: String, var data: T?)
code:接口返回数据状态码(状态码与后台人员协商),0代表成功,其他值代表不同的错误类型
message:接口返回的信息,描述具体的接口返回状态--成功、失败信息
data:接口返回数据,不同接口返回不同的数据结构,使用泛型,可能为null
data class ResultData<T>(var code: Int, var message: String, var data: T?)
code:接口返回数据状态码(状态码与后台人员协商),0代表成功,其他值代表不同的错误类型
message:接口返回的信息,描述具体的接口返回状态--成功、失败信息
data:接口返回数据,不同接口返回不同的数据结构,使用泛型,可能为null
2.定义接口类 ApiService
interface ApiService {
@POST("m/login")
@FormUrlEncoded
fun userLogin(@FieldMap options: Map<String, String>): Observable<ResultData<UserInfoVo>>
}
所有的网络请求接口都可以定义在该类中
3.接口类实现
object Api {
/**
* 请求超时时间
*/
private val DEFAULT_TIMEOUT = 5L
private var SERVICE: ApiService? = null
@JvmStatic
val default: ApiService
get() {
if (SERVICE == null) {
val loggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
//打印retrofit日志
Log.d("RetrofitLog", "retrofitBack = " + message)
})
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
//手动创建一个OkHttpClient并设置超时时间
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(RequestInterceptor())
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build()
SERVICE = Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(UriConsts.BASE_URL)
.build()
.create(ApiService::class.java)
}
return SERVICE!!
}
}
4.Observable变换,统一处理返回结果
使用lifecycleProvider,来取消网络请求,主要使用场景是在Activity、Fragment里进行网络请求时,应该在destroy的时候取消请求,防止短暂的内存泄露,所以Activity应该继承自RxAppCompatActivity,Fragment继承自RxFragment,具体关于lifecycleProvider的可以参考https://github.com/trello/RxLifecycle。
needCertification是针对需要认证的网络接口(例如:带token的接口请求),可以在接收到token失效的code信息做刷新token的操作,这里就没具体的实现,各位看官可以自己去实现自己的逻辑代码。
class HttpSimpleTransformer<T, E>(private var lifecycleProvider: LifecycleProvider<E>? = null,
private var needCertification: Boolean = false,
private var httpSimpleSubscriber: HttpSimpleSubscriber<T>? = null)
: ObservableTransformer<ResultData<T>, T> {
companion object {
private val TAG = "HttpSimpleTransformer"
}
override fun apply(upstream: Observable<ResultData<T>>): ObservableSource<T> {
if (lifecycleProvider is RxAppCompatActivity) {
val rxAppCompatActivity = lifecycleProvider as RxAppCompatActivity
upstream.compose(rxAppCompatActivity.bindUntilEvent(ActivityEvent.DESTROY))
} else if (lifecycleProvider is RxFragment) {
val rxFragment = lifecycleProvider as RxFragment
upstream.compose(rxFragment.bindUntilEvent(FragmentEvent.DESTROY))
}
val observable = upstream
.flatMap { tResultData -> flatResponse(tResultData) }
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
//不能破坏链式结构,否则不起作用
if (httpSimpleSubscriber is ProgressSubscriber) {
Log.d(TAG, "doOnSubscribe")
val progressSubscriber = httpSimpleSubscriber as ProgressSubscriber
progressSubscriber.showProgressDialog()
}
}
if (null != httpSimpleSubscriber) {
observable.subscribe(httpSimpleSubscriber!!)
}
return observable
}
private fun flatResponse(response: ResultData<T>): Observable<T> {
return Observable.create(ObservableOnSubscribe<T> { subscriber ->
try {
if (response.code == HttpStatus.SUCCESS) {//请求成功
if (null != response.data) {
subscriber.onNext(response.data!!)
} else {
subscriber.onError(NullDataException("返回数据为空"))
}
} else {//请求失败
if (needCertification) {
//需要认证,需要认证的接口根据需要选择是否要做统一的额外逻辑处理
subscriber.onError(ApiException(response.code))
} else {
//这里抛出自定义的一个异常.可以处理服务器返回的错误.
subscriber.onError(ApiException(response.code))
}
return@ObservableOnSubscribe
}
//请求完成
subscriber.onComplete()
} catch (e: Exception) {
Log.e(TAG, e.message)
subscriber.onError(e)
}
})
}
}
这里有两点需要注意的地方:
1、doOnSubscribe不能与前面的Observable链式调用结构分开,否则它里面的回调方法不会得到调用,这样带加载框的接口就不会显示加载框了
2、返回结果中data为null的情况不能直接调用subscriber.onNext(response.resultData),在Rxjava2.x里已经严格限定了不能传null(可以看源码),否则会保错,导致奔溃。这里采用自定义了一个空数据异常NullDataException,调用subscriber.onError(NullDataException("返回数据为空")),在自定义subscriber中再进行一轮处理,详解见subscriber里onError的处理
5.统一subscriber
abstract class HttpSimpleSubscriber<T> : Observer<T> {
companion object {
private val TAG = "HttpSimpleSubscriber"
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: T) {
_onNext(t)
}
override fun onError(t: Throwable) {
var errorCode = HttpStatus.ERROR
var msg = "请求失败,请稍后再试..."
if (t is NullDataException) {
_onNext(null)
return
} else if (t is ApiException) {
errorCode = t.errorCode
msg = t.message ?: "请求失败,请稍后再试..."
} else if (t is UnknownHostException) {
errorCode = HttpStatus.SOCKET_TIMEOUT
msg = "请检查网络"
} else if (t is SocketTimeoutException) {
errorCode = HttpStatus.SOCKET_TIMEOUT
msg = "网络连接超时,请稍后再试..."
} else if (t is ConnectException) {
errorCode = HttpStatus.SOCKET_TIMEOUT
msg = "网络连接失败,请稍后再试..."
}
Log.e(TAG, "errorCode:${errorCode} " + t)
_onError(errorCode, msg)
}
override fun onComplete() {
_onComplete()
}
abstract fun _onNext(retData: T?)
open fun _onComplete() {
}
open fun _onError(errorCode: Int, msg: String) {
Toast.makeText(MyApplication.context, msg, Toast.LENGTH_SHORT).show()
}
}
这里主要是对onError的处理,针对不同的错误类型,显示不同的错误信息给用户看。主要是由于服务器传递过来的错误信息直接给用户看的话,用户未必能够理解需要根据错误码对错误信息进行一个转换,在显示给用户。
这里有个特殊的异常NullDataException,其实是接口请求成功了,只是data为null,所以在这里做了区分处理,调用的是_onNext(null)方法而不是_onError(errorCode, msg)方法。
指定带加载框的网络请求这里封装了ProgressSubscriber,继承自HttpSimpleSubscriber,里面主要增加了显示、隐藏加载框的逻辑,详细看源代码。
6.Observable扩展方法
fun <T : Any, E> Observable<ResultData<T>>.compose1(lifecycleProvider: LifecycleProvider<E>? = null,
needCertification: Boolean = false,
httpSimpleSubscriber: HttpSimpleSubscriber<T>? = null): Observable<T> =
compose(HttpSimpleTransformer(lifecycleProvider, needCertification, httpSimpleSubscriber))
通过该扩展方法可以讲Observable<ResultData<T>>转化为Observable<T>
fun <E> userLogin(userName: String, password: String,
lifecycleProvider: LifecycleProvider<E>,
httpSimpleSubscriber: HttpSimpleSubscriber<UserInfoVo>? = null): Observable<UserInfoVo> {
var requestParam = HashMap<String, String>()
requestParam.put("username", userName)
requestParam.put("password", MD5Util.MD5(password))
return mApiService.userLogin(requestParam)
.compose1(lifecycleProvider, httpSimpleSubscriber = httpSimpleSubscriber)
}