上文有说到,ReactiveHttp 提供了在网络请求过程中自动完成 showLoading、dismissLoading、showToast 等行为的能力。首先,BaseRemoteDataSource 在网络请求过程中会通过 IUIActionEvent 接口来通知 BaseReactiveViewModel 需要触发的行为,从而连锁触发 ShowLoadingLiveData、DismissLoadingLiveData、ShowToastLiveData 值的变化,BaseReactiveActivity 就通过监听 LiveData 值的变化来完成 UI 层操作
四、惯常做法
以下步骤应该是大部分应用目前进行网络请求时的惯常做法了
服务端返回给移动端的数据使用具有特定格式的 Json 来进行通信,用整数 status 来标明本次请求是否成功,在失败时则直接 showToast(msg) ,data 则需要用泛型来声明了,最终就对应移动端的一个泛型类,类似于 HttpWrapBean
{ “status”:200, “msg”:“success”, “data”:"" }
data class HttpWrapBean(val status: Int, val msg: String, val data: T)
然后在 interface 中声明 Api 接口,这也是使用 Retrofit 的惯常用法。根据项目中的实际情况,开发者可能是使用 Call 或者 Observable 作为每个接口返回值的最外层的数据包装类,然后再使用 HttpWrapBean 来作为具体数据类的包装类
interface ApiService {
@POST(“api1”) fun api1(): Observable<HttpWrapBean>
@GET(“api2”) fun api2(): Call<HttpWrapBean>
}
然后项目中使用的是 RxJava,那么就需要像以下这样来完成网络请求
val retrofit = Retrofit.Builder() .baseUrl(“https://xxx.com”) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() val service = retrofit.create(ApiService::class.java) val call: Observable<HttpWrapBean> = service.api1() call.subscribe(object : Consumer<HttpWrapBean> { override fun accept(userBean: HttpWrapBean?) {
}
}, object : Consumer { override fun accept(t: Throwable?) {
} })
五、简单入门
ReactiveHttp 在使用上会比上面给出的例子简单很多,下面就来看下通过 ReactiveHttp 如何完成网络请求
ReactiveHttp 需要知道网络请求的结果,但不知道外部会使用什么字段名来标识 HttpWrapBean 中的三个值,所以需要外部实现 IHttpWrapBean 接口来进行标明。例如,你可以这样来实现:
data class HttpWrapBean(val status: Int, val msg: String, val data: T) : IHttpWrapBean {
override val httpCode: Int get() = status
override val httpMsg: String get() = msg
override val httpData: T get() = data
//网络请求是否成功 override val httpIsSuccess: Boolean get() = status == 200
}
用suspend 来修饰接口方法,且不需要其它的外层包装类。suspend 是 kotlin 协程引入的,当用该关键字修饰接口方法时,Retrofit 内部就会使用协程的方式来完成该网络请求
interface ApiService {
@GET(“config/district”) suspend fun getProvince(): HttpWrapBean<List>
}
ReactiveHttp 提供了 RemoteExtendDataSource 交由外部来继承实现。RemoteExtendDataSource 包含了所有的网络请求方法,外部仅需要根据实际情况来实现三个必要的字段和方法即可
- releaseUrl。即应用的 BaseUrl
- createRetrofit。用于创建 Retrofit,开发者可以在这里自定义 OkHttpClient
- showToast。当网络请求失败时,通过该方法来向用户提示失败原因
例如,你可以像以下这样来实现你自己项目的专属 DataSource
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
,当中就包含了开发者整个项目的全局网络请求配置
class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource(iActionEvent, ApiService::class.java) {
companion object {
private val httpClient: OkHttpClient by lazy { createHttpClient() }
private fun createHttpClient(): OkHttpClient { val builder = OkHttpClient.Builder() .readTimeout(1000L, TimeUnit.MILLISECONDS) .writeTimeout(1000L, TimeUnit.MILLISECONDS) .connectTimeout(1000L, TimeUnit.MILLISECONDS) .retryOnConnectionFailure(true) .addInterceptor(FilterInterceptor()) .addInterceptor(MonitorInterceptor(MainApplication.context)) return builder.build() }
}
/**
- 由子类实现此字段以便获取 release 环境下的接口 BaseUrl
*/ override val releaseUrl: String get() = “https://restapi.amap.com/v3/”
/**
- 允许子类自己来实现创建 Retrofit 的逻辑
- 外部无需缓存 Retrofit 实例,ReactiveHttp 内部已做好缓存处理
- 但外部需要自己判断是否需要对 OKHttpClient 进行缓存
- @param baseUrl
*/ override fun createRetrofit(baseUrl: String): Retrofit { return Retrofit.Builder() .client(httpClient) .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .build() }
override fun showToast(msg: String) { Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show() }
}
之后,我们就可以依靠 SelfRemoteDataSource 在任意地方发起网络请求了,按需声明 Callback 方法。此外,由于使用到了扩展函数,所以 SelfRemoteDataSource 中可以直接调用 ApiService 中的接口方法,无需特意引用和导包
private val remoteDataSource = SelfRemoteDataSource(null)
val provinceLiveData = MutableLiveData<List>()
fun reqProvince() { //enqueueLoading 方法会在发起网络请求的时候同时弹出 loading 框 //enqueue 方法则不会弹出 loading 框 remoteDataSource.enqueueLoading({ getProvince() }) { /**
- 在显示 Loading 之后且开始网络请求之前执行
*/ onStart {
} /**
- 当网络请求成功时回调
/ onSuccess { provinceLiveData.value = it } /* - 当取消网络请求时就会回调此方法
*/ onCancelled {
} /**
- 当网络请求失败时会调用此方法,在 onFinally 被调用之前执行
/ onFailed { val httpException = it val realException = httpException.realException val errorCode = httpException.errorCode } /* - 用于控制是否当网络请求失败时 Toast 失败原因
- 默认为 true,即会 Toast 提示
/ onFailToast { true } /* - 在网络请求结束之后(不管请求成功与否)且隐藏 Loading 之前执行
*/ onFinally {
} } }
六、进阶使用
上述在使用 SelfRemoteDataSource 发起网络请求时虽然调用的是 enqueueLoading 方法,但实际上并不会弹出 loading 框,因为完成 ShowLoading、DismissLoading、ShowToast 等 UI 行为是需要 RemoteDataSource、ViewModel 和 Activity 这三者一起进行配合的,即 SelfRemoteDataSource 需要和其它两者关联上,将需要触发的 UI 行为反馈给 Activity
这可以通过直接继承于 BaseReactiveActivity 和 BaseReactiveViewModel 来实现,也可以通过实现相应接口来完成关联。当然,如果你不需要 ReactiveHttp 的各个自动化行为的话,也可以不做以下任何改动
总的来说,ReactiveHttp 具有极低的接入成本
1、BaseReactiveActivity
BaseReactiveActivity 是 ReactiveHttp 提供的一个默认 BaseActivity,其实现了 IUIActionEventObserver 接口,用于提供一些默认参数和默认行为,例如 CoroutineScope 和 showLoading。但在大多数情况下,我们自己的项目是不会去继承外部 Activity 的,而是会有一个自己实现的全局统一的 BaseActivity,所以如果你不想继承 BaseReactiveActivity 的话,可以自己来实现 IUIActionEventObserver 接口,就像以下这样
@SuppressLint(“Registered”) abstract class BaseActivity : AppCompatActivity(), IUIActionEventObserver {
protected inline fun getViewModel( factory: ViewModelProvider.Factory? = null, noinline initializer: (VM.(lifecycleOwner: LifecycleOwner) -> Unit)? = null ): Lazy where VM : ViewModel, VM : IViewModelActionEvent { return getViewModel(VM::class.java, factory, initializer) }
override val lifecycleSupportedScope: CoroutineScope get() = lifecycleScope
override val lContext: Context? get() = this
override val lLifecycleOwner: LifecycleOwner get() = this
private var loadDialog: ProgressDialog? = null
override fun showLoading(job: Job?) { dismissLoading() loadDialog = ProgressDialog(lContext).apply { setCancelable(true) setCanceledOnTouchOutside(false) //用于实现当弹窗销毁的时候同时取消网络请求 // setOnDismissListener { // job?.cancel() // } show() } }
override fun dismissLoading() { loadDialog?.takeIf { it.isShowing }?.dismiss() loadDialog = null }
override fun showToast(msg: String) { if (msg.isNotBlank()) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() } }
override fun finishView() { finish() }
override fun onDestroy() { super.onDestroy() dismissLoading() }
}
2、BaseReactiveViewModel
类似地,BaseReactiveViewModel 是 ReactiveHttp 提供的一个默认的 BaseViewModel,其实现了 IViewModelActionEvent 接口,用于接收 RemoteDataSource 发起的 UI 层行为。如果你不希望继承于 BaseReactiveViewModel 的话,可以自己来实现 IViewModelActionEvent 接口,就像以下这样
open class BaseViewModel : ViewModel(), IViewModelActionEvent {
override val lifecycleSupportedScope: CoroutineScope get() = viewModelScope
override val showLoadingEventLD = MutableLiveData()
override val dismissLoadingEventLD = MutableLiveData()
override val showToastEventLD = MutableLiveData()
override val finishViewEventLD = MutableLiveData()
}
3、关联上
完成以上两步后,开发者就可以像如下所示这样将 RemoteDataSource、ViewModel 和 Activity 这三者给关联起来。WeatherActivity 通过 getViewModel 方法来完成 WeatherViewModel 的初始化和内部多个 UILiveData 的绑定,并在 lambda 表达式中完成对 WeatherViewModel 内部和具体业务相关的 DataLiveData 的数据监听,至此所有自动化行为就都已经绑定上了
class WeatherViewModel : BaseReactiveViewModel() {
private val remoteDataSource by lazy { SelfRemoteDataSource(this) }
val forecastsBeanLiveData = MutableLiveData()
fun getWeather(city: String) { remoteDataSource.enqueue({ getWeather(city) }) { onSuccess { if (it.isNotEmpty()) { forecastsBeanLiveData.value = it[0] } } } }
}
class WeatherActivity : BaseReactiveActivity() {
private val weatherViewModel by getViewModel { forecastsBeanLiveData.observe(this@WeatherActivity, { showWeather(it) }) }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_weather) weatherViewModel.getWeather(“adCode”) }
private fun showWeather(forecastsBean: ForecastsBean) {
}
}
七、其它
1、BaseRemoteDataSource
RemoteExtendDataSource 提供了许多个可以进行复写的方法,既可用于配置 OkHttp 的各个网络请求参数,也用于交由外部进行流程控制。例如,你可以这样来实现自己项目的 BaseRemoteDataSource
class BaseRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource(iActionEvent, ApiService::class.java) {
companion object {
private val httpClient: OkHttpClient by lazy { createHttpClient() }
private fun createHttpClient(): OkHttpClient { val builder = OkHttpClient.Builder() .readTimeout(1000L, TimeUnit.MILLISECONDS) .writeTimeout(1000L, TimeUnit.MILLISECONDS) .connectTimeout(1000L, TimeUnit.MILLISECONDS) .retryOnConnectionFailure(true) .addInterceptor(FilterInterceptor()) .addInterceptor(MonitorInterceptor(MainApplication.context)) return builder.build() } }
/**
- 由子类实现此字段以便获取 release 环境下的接口 BaseUrl
*/ override val releaseUrl: String get() = HttpConfig.BASE_URL_MAP
/**
- 允许子类自己来实现创建 Retrofit 的逻辑
- 外部无需缓存 Retrofit 实例,ReactiveHttp 内部已做好缓存处理
- 但外部需要自己判断是否需要对 OKHttpClient 进行缓存
- @param baseUrl
*/ override fun createRetrofit(baseUrl: String): Retrofit { return Retrofit.Builder() .client(httpClient) .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .build() }
/**
- 如果外部想要对 Throwable 进行特殊处理,则可以重写此方法,用于改变 Exception 类型
- 例如,在 token 失效时接口一般是会返回特定一个 httpCode 用于表明移动端需要去更新 token 了
- 此时外部就可以实现一个 BaseException 的子类 TokenInvalidException 并在此处返回
- 从而做到接口异常原因强提醒的效果,而不用去纠结 httpCode 到底是多少
*/ override fun generateBaseException(throwable: Throwable): BaseHttpException { return if (throwable is BaseHttpException) { throwable } else { LocalBadException(throwable) } }
/**
- 用于由外部中转控制当抛出异常时是否走 onFail 回调,当返回 true 时则回调,否则不回调
- @param httpException
*/ override fun exceptionHandle(httpException: BaseHttpException): Boolean { return true }
/**
- 用于将网络请求过程中的异常反馈给外部,以便记录
- @param throwable
*/ override fun exceptionRecord(throwable: Throwable) { Log.e(“SelfRemoteDataSource”, throwable.message ?: “”) }
/**
- 用于对 BaseException 进行格式化,以便在请求失败时 Toast 提示错误信息
- @param httpException
*/ override fun exceptionFormat(httpException: BaseHttpException): String { return when (httpException.realException) { null -> { httpException.errorMessage } is ConnectException, is SocketTimeoutException, is UnknownHostException -> { “连接超时,请检查您的网络设置” } else -> { “请求过程抛出异常:” + httpException.errorMessage } } }
override fun showToast(msg: String) { Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show() }
}
此外,开发者可以直接在自己的 BaseViewModel 中声明一个 BaseRemoteDataSource 变量实例,所有子 ViewModel 都全局统一使用同一份 DataSource 配置。如果有某些特定接口需要使用不同的 BaseUrl 的话,也可以再多声明一个 BaseRemoteDataSource
open class BaseViewModel : BaseReactiveViewModel() {
/**
- 正常来说单个项目中应该只有一个 RemoteDataSource 实现类,即全局使用同一份配置
- 但父类也应该允许子类使用一个独有的 RemoteDataSource,即允许子类复写此字段
*/ protected open val remoteDataSource by lazy { BaseRemoteDataSource(this) }
}
2、BaseHttpException
|