Rxjava2+Retrofit2之kotlin封裝
Rxjava2+Retrofit2二次封裝,使用kotlin語言,有loading,token,生命週期,防多次重複請求等處理
ofollow,noindex">https://github.com/TianGuisen/TianTools/tree/master/app/src/main/java/tg/my/core/retrofit使用方法
interface UserApi { @POST(Urls.LOGIN) @FormUrlEncoded fun login(@Field("username") username: String, @Field("password") password: String): Observable<ResWrapper<UserInfo>> } object UserService : UserApi by gsonService.create(UserApi::class.java)
//普通請求使用NormalObserver UserService.login("tian", "1").compose(ioMain(this)) .subscribe(object : NormalObserver<UserInfo>() { override fun onSuccess(info: UserInfo?, code: Int, message: String?, tag: Any?) { //... } })
//Recyclerview介面使用RVObserver ProductService.getProductList(pageNum, 6).compose(ioMain(mV)) .subscribe(object : RVObserver<MutableList<ProductInfo>>(null, lv, page) { override fun onSuccess(info: MutableList<ProductInfo>?, code: Int, message: String?, tag: Any?) { //... } })
新增gradle依賴
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.14' implementation 'com.trello.rxlifecycle2:rxlifecycle:2.2.2' implementation 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle-kotlin:2.2.2' implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.2' implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-scalars:2.4.0' implementation 'org.ligboy.retrofit2:converter-fastjson:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
一:返回實體基類
通常後臺返回json資料格式為
{ "code": 200, "message": "成功", "success": true, "data": { ...} }
建立類如下,實際情況根據後臺返回修改
class ResWrapper :Serializable { var code:Int = -1 var message:String? ="" var data:T? =null var success:Boolean =false }
二.建立Retrofit
//使用懶載入構建 val gsonService: Retrofit by lazy { val retrofitParams = RetrofitParams() //新增引數攔截器 retrofitParams.interceptors.add(ParamInterceptor()) //新增日誌攔截器 retrofitParams.interceptors.add(LoggerInterceptor()) //新增Gson轉換器 retrofitParams.converterFactory = GsonConverterFactory.create() createRetrofit(retrofitParams) } private fun createRetrofit(params: RetrofitParams): Retrofit { val builder = OkHttpClient.Builder() for (interceptor in params.interceptors) { builder.addInterceptor(interceptor) } builder.connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.MINUTES) .retryOnConnectionFailure(false) .writeTimeout(30, TimeUnit.MINUTES) val retrofit = Retrofit.Builder() .baseUrl(params.baseUrl) .addConverterFactory(params.converterFactory) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(builder.build()) .build() return retrofit } private class RetrofitParams { val interceptors = mutableListOf<Interceptor>() var baseUrl = BASE_URL lateinit var converterFactory: Converter.Factory }
三.建立NormalObserver對返回資料進行處理(重點)
如果不自定義Observer,一般是這種寫法,使用lambda建立預設的Observer,這樣是無法對返回結果進行處理的
UserService.login("tian", "1").compose(ioMain(this)) .subscribe{ }
現在建立NormalObserver繼承DisposableObserver
abstract class NormalObserver<T> : DisposableObserver<ResWrapper<T>>, IObserver<T> { var loading: RotateLoading? = null override var tag: String? = null override var canRepeat: Boolean = false override var showToast: Boolean = true /** *@loading 是否顯示loading *@showToast 是否顯示錯誤toast *@tag: 請求標記,傳入url */ constructor(loading: RotateLoading? = null, showToast:Boolean=true,tag: String? = null) { this.loading = loading this.tag = tag this.showToast=showToast } /** * 設定可以重複請求.預設禁止 */ fun setCanRepeat(): NormalObserver<T> { canRepeat = true return this } override fun onStart() { if (!isConnected()) { ToastUtil.normal("網路異常") return } addTag(this) loading?.start() } final override fun onComplete() { removeTag() loading?.stop() onFinish() } final override fun onError(e: Throwable) { Logger.t(AppConfigs.LOGGER_NET_TAG).e(e, "") removeTag() val apiError = if (e is HttpException) { //連線成功但後臺返回錯誤狀態碼 when (e.code()) { ApiErrorType.INTERNAL_SERVER_ERROR.code -> ApiErrorType.INTERNAL_SERVER_ERROR ApiErrorType.BAD_GATEWAY.code -> ApiErrorType.BAD_GATEWAY ApiErrorType.NOT_FOUND.code -> ApiErrorType.NOT_FOUND else -> ApiErrorType.UNEXPECTED_ERROR } } else { when (e) {//傳送網路問題或其它未知問題 is UnknownHostException -> ApiErrorType.NETWORK_NOT_CONNECT is ConnectException -> ApiErrorType.NETWORK_NOT_CONNECT is SocketTimeoutException -> ApiErrorType.CONNECTION_TIMEOUT else -> ApiErrorType.UNEXPECTED_ERROR } } showToast.yes { ToastUtil.normal(apiError.name) } onFailure(null, apiError.code, apiError.name, tag) loading?.stop() } final override fun onNext(t: ResWrapper<T>) { checkLogin(t) if (t.success) { onSuccess(t.data, t.code, t.message, tag) } else { onFailure(t.data, t.code, t.message, tag) } } protected abstract fun onSuccess(info: T?, code: Int, message: String?, tag: Any?) protected open fun onFailure(info: T?, code: Int, message: String?, tag: Any?) { } protected open fun onFinish() {} }
interface IObserver<T> { var tag: String? var canRepeat: Boolean var showToast: Boolean /** * 檢查登入 */ fun checkLogin(t: ResWrapper<T>) { if (t.code == ResCode.TOKEN_OVERDUE) { RxBus2.getInstance().post(ResCode.TOKEN_OVERDUE, t) } } fun addTag(observer: DisposableObserver<ResWrapper<T>>) { if (!canRepeat && tag != null) { ApiTagManager.instance.add(tag!!, observer) } } fun removeTag() { if (!canRepeat && tag != null) { ApiTagManager.instance.remove(tag!!) } } }
object ResCode { //和後臺約定的業務錯誤 /** * token過期,需要登入 */ const val TOKEN_OVERDUE = 10 } enum class ApiErrorType(val code: Int, @param: StringRes private val message: String) { //非業務錯誤 INTERNAL_SERVER_ERROR(500, "伺服器錯誤"), BAD_GATEWAY(502, "伺服器錯誤"), NOT_FOUND(404, "未找到"), CONNECTION_TIMEOUT(408, "連線超時"), NETWORK_NOT_CONNECT(499, "網路異常"), UNEXPECTED_ERROR(700,"未知錯誤"); }
/** * 網路請求tag管理 * 相同tag的請求只會存在一個 */ class ApiTagManager private constructor() { private val maps = arrayMapOf<String, DisposableObserver<*>>() companion object { val instance = ApiTagManager() } /** * 後入隊的會被關閉 */ fun add(tag: String, observer: DisposableObserver<*>) { maps.keys.forEach { if (tag == it) { observer.dispose() return } } maps.put(tag, observer) } /** * 先入隊的會被關閉 */ fun add2(tag: String, observer: DisposableObserver<*>) { maps.keys.forEach { if (tag == it) { maps.get(it)?.dispose() maps.remove(it) maps.put(tag, observer) return } } maps.put(tag, observer) } fun remove(tag: String) { if (!maps.isEmpty()) { maps.remove(tag) } } fun removeAll() { if (!maps.isEmpty()) { maps.clear() } } fun cancel(tag: String) { if (maps.isEmpty()) { return } val m1 = maps.get(tag) if (m1 == null) { return } if (!m1.isDisposed()) { m1.dispose() maps.remove(tag) } } fun cancelAll() { if (maps.isEmpty()) { return } val tags = maps.keys for (tag in tags) { cancel(tag) } } }
四.執行緒轉換和生命週期處理
當activity或者fragment被銷燬時,請求應該終止,否則會記憶體洩漏降低效能.使用 RxLifecycle 進行生命週期管理
implementation 'com.trello.rxlifecycle2:rxlifecycle:2.2.2' implementation 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle-kotlin:2.2.2' implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.2'
因為我使用的是fragment,使用actvity的話需要自己修改
關於RxLifecycle的詳細使用方法請看 RxLifecycle
/** * 訂閱在io,回撥在main * 傳入fragment會繫結生命週期在destroy的時候取消訂閱 */ fun <T> ioMain(fra: BaseFra<*>?=null): ObservableTransformer<T, T> { return object : ObservableTransformer<T, T> { @SuppressLint("CheckResult") override fun apply(upstream: Observable<T>): ObservableSource<T> { if (fra != null) { upstream.compose(fra.bindUntilEvent(FragmentEvent.DESTROY)) } return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) } } }
五.載入Loading
這裡分為兩種loading
1是常用的loading,可以是一個dialog,也可以是一個自定義view.我使用的https://github.com/TianGuisen/TianTools/blob/master/app/src/main/java/tg/my/core/view/RotateLoading.java
2.還有一種loading的場景是這樣:一個recyclerview頁面,剛剛進去的時候是載入狀態,載入正常顯示出recyclerview,資料是null則顯示一個"暫無資料"圖片頁面,網路錯誤則顯示另一個頁面,可能點選後還要去重新載入,這時候就需要自定義一個控制元件去包裹recyclerview了.可以參考https://github.com/TianGuisen/TianTools/blob/master/app/src/main/java/tg/my/core/view/LoadView.kt