那么,我们就从 MVP 开始。
首先,项目中使用 Retrofit + RxJava2 进行网络请求,那么,我们在使用的时候就要考虑到 RxJava 的生命周期问题,如果不对它进行处理,那么在应用中进入一个 Activity、请求网络、在请求未完成返回这个流程中就会抛出异常,导致应用崩溃,于是,就有了这样的 BaseMVPPresenter。
/**
* Presenter基类
*
* @param V MVP View类型 继承[BaseMVPView]
* @param M MVP Module 继承[BaseMVPModule]
*/
open class BaseMVPPresenter {
/** MVP View 对象 */
protected var mView: V? = null
/** MVP Module 对象 */
@Inject
protected lateinit var mModule: M
/** RxJava2 生命周期管理 */
private val disposables: CompositeDisposable = CompositeDisposable()
/**
* 界面绑定,关联 MVP View
*
* @param view MVP View
*/
fun attach(view: V) {
mView = view
}
/**
* 解除绑定,去除 MVP View 引用
*/
fun detach() {
mView = null
}
/**
* 检查请求返回数据,并在登录状态异常时弹出提示
*
* @param data 返回数据
* @param T 返回数据类型
*
* @return 是否成功
*/
protected fun checkResponse(data: T): Boolean {
return data.code == Constants.ResponseCode.SUCCESS
}
/**
* 将网络请求添加到 RxJava2 生命周期
*/
protected fun addDisposable(dis: Disposable) {
disposables.add(dis)
}
/**
* 消费所有事件
*/
fun dispose() {
if (!disposables.isDisposed && disposables.size() > 0) {
disposables.dispose()
}
}
}
为了能够方便的进行复用,所以 BaseMVPPresenter 中使用泛型来确定 View 以及 Module 的类型。
定义的 attach() 方法在 presenter 使用前调用,绑定 View,并在 Activity 结束时使用 detach() 方法接触绑定,移除引用,调用 dispose() 方法消费所有事件。
在 Module 中进行网络请求以及其他耗时操作,所以有了 BaseMVPModule
/**
* MVP Module基类
*/
open class BaseMVPModule @Inject constructor() {
@Inject
lateinit var netClient: NetApi
}
因为在所有 Module 中都会用到网络请求,所以将网络请求 API 在 BaseMVPModule 中声明。
其中 NetApi 的依赖注入可以新建一个 NetModule
/**
* 网络模块依赖注入
*/
@Module
class NetModule {
@Provides
@Singleton
fun netClient(): NetApi {
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(ParametersInterceptor())
.addInterceptor(LogInterceptor())
.build()
val retrofit = Retrofit.Builder()
.baseUrl(UrlDefinition.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
return retrofit.create(NetApi::class.java)
}
}
这里的 @Singleton 注解表明使用单例模式,即 NetApi 对象是单例的。
然后还需要将 NetModule 添加到 ApplicationSub 中
/**
* Application Dagger2 组件
*/
@Singleton
@Component(modules = arrayOf(
ActivityModule::class,
SupportFragmentModule::class,
NetModule::class,
AndroidSupportInjectionModule::class))
interface ApplicationSub : AndroidInjector {
@Component.Builder
abstract class Builder : AndroidInjector.Builder()
}
注意:Module 中使用了 @Singleton 注解,ApplicationSub 中也要添加 @Singleton 注解。
然后,就是 BaseMVPView
/**
* MVP View基类
*/
interface BaseMVPView {
/**
* 网络请求结束
*/
fun onNetFinished()
/**
* 网络故障
*/
fun onNetError()
/**
* 无数据
*/
fun onNoData()
/**
* 加载中
*/
fun onLoading()
}
在 BaseMVPView 中,定义了一系列通用方法,即每个界面都会用到的方法。
上面说完了 MVP 模式相关的模块基类封装,现在说说 Activity 的基类封装。
我们为什么要封装基类?当然是为了开发方便,防止重复的模版代码,那么首先就要把每个界面都需要的抽取出来,标题栏那是肯定的,还有加载数据的不同状态,即上面 BaseMVPView 中所定义的几个状态。把这些抽取成 layout_base.xml
标题栏
网络异常
无数据
加载中
新建 BaseActivity 继承 DaggerAppCOmpatActivity (支持 Dagger2),使用泛型确定 DataBinding、MVPPresenter 类型。
/**
* Activity 基类
*/
abstract class BaseActivity, DB : ViewDataBinding>
: DaggerAppCompatActivity(),
BaseMVPView,
RootHandler.OnTitleClickListener {
/** 当前界面 Context 对象*/
protected lateinit var mContext: AppCompatActivity
/** 当前界面 Presenter 对象 */
@Inject
protected lateinit var presenter: P
/** 根布局 DataBinding 对象 */
protected lateinit var rootBinding: LayoutBaseBinding
/** 当前界面布局 DataBinding 对象 */
protected lateinit var mBinding: DB
/**
* 重写 onCreate() 方法,添加了 Dagger2 注入、Activity 管理以及根布局等初始化操作
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 保存当前 Context 对象
mContext = this
// 添加到 AppManager 应用管理
AppManager.addActivity(this)
// 加载根布局,初始化 DataBinding
rootBinding = DataBindingUtil.inflate(
LayoutInflater.from(mContext),
R.layout.layout_base, null, false
)
// 绑定事件处理
rootBinding.handler = RootHandler(this)
}
/**
* 重写 onDestroy() 方法,移除 Activity 管理以及 MVP 生命周期管理
*/
override fun onDestroy() {
// 从应用管理移除当前 Activity 对象
AppManager.removeActivity(this)
// 界面销毁时,消费所有事件,清空引用
presenter.dispose()
presenter.detach()
super.onDestroy()
}
/**
* 重写 setContentView(layoutResID) 方法,使其支持 DataBinding 以及标题栏、状态栏初始化操作
*/
override fun setContentView(layoutResID: Int) {
// 初始化标题栏
initTitleBar()
// 加载布局,初始化 DataBinding
mBinding = DataBindingUtil.inflate(
LayoutInflater.from(mContext),
layoutResID, null, false
)
// 将当前布局添加到根布局
rootBinding.flContent.removeAllViews()
rootBinding.flContent.addView(mBinding.root)
// 设置布局
super.setContentView(rootBinding.root)
// 初始化状态栏
initStatusBar()
}
/**
* 初始化标题栏,抽象方法,子类实现标题栏自定义
*/
protected abstract fun initTitleBar()
/**
* 初始化状态栏,默认主题色、不透明,修改需重写
*/
protected fun initStatusBar() {
setStatusBar()
}
/**
* 设置状态栏,默认主题色、不透明
*
* @param colorResId 状态栏颜色,默认主题色
* @param alpha 状态栏透明度,默认不透明,取值范围 0~255
*/
protected fun setStatusBar(@ColorRes colorResId: Int = R.color.colorTheme, alpha: Int = 0) {
if (alpha !in 0..255) {
RuntimeException("The value of the alpha must between 0 and 255")
} else {
StatusBarUtil.setResColor(this, colorResId, alpha)
}
}
/**
* 显示标题栏
*/
protected fun showTitle() {
rootBinding.handler?.showTitle = true
}
/**
* 设置标题文本
*
* @param strResID 标题文本资源id
*/
protected fun setTitleStr(@StringRes strResID: Int) {
rootBinding.handler?.showTvTitle = true
rootBinding.handler?.tvTitle = getString(strResID)
}
/**
* 设置标题文本
*
* @param str 标题文本
*/
protected fun setTitleStr(str: String) {
rootBinding.handler?.showTvTitle = true
rootBinding.handler?.tvTitle = str
}
/**
* 设置标题栏左侧图标,默认返回按钮
*
* @param resID 标题栏左侧图标资源id,默认返回按钮
*/
protected fun setIvLeft(@DrawableRes resID: Int = R.mipmap.arrow_left_white) {
rootBinding.handler?.showIvLeft = true
rootBinding.handler?.ivLeftResID = resID
}
/**
* 设置右侧图标
*
* @param resID 图片资源id
*/
protected fun setIvRight(@DrawableRes resID: Int) {
rootBinding.handler?.showIvRight = true
rootBinding.handler?.ivRightResID = resID
}
/**
* 设置右侧文本
*
* @param strResID 文本资源id
*/
protected fun setTvRight(@StringRes strResID: Int) {
rootBinding.handler?.showTvRight = true
rootBinding.handler?.tvRight = getString(strResID)
}
/**
* 重写BaseMvpView中方法,网络异常时调用
*/
override fun onNetError() {
val handler = rootBinding.handler
handler?.let {
if (handler.showNoData) {
handler.showNoData = false
}
if (handler.showLoading) {
val drawable = rootBinding.ivLoading.drawable
(drawable as? AnimationDrawable)?.stop()
handler.showLoading = false
}
if (!handler.showNetError) {
handler.showNetError = true
}
onListComplete()
}
}
/**
* 重写BaseMvpView中方法,无数据时调用
*/
override fun onNoData() {
val handler = rootBinding.handler
handler?.let {
if (handler.showNetError) {
handler.showNetError = false
}
if (handler.showLoading) {
val drawable = rootBinding.ivLoading.drawable
(drawable as? AnimationDrawable)?.stop()
handler.showLoading = false
}
if (!handler.showNoData) {
handler.showNoData = true
}
onListComplete()
}
}
/**
* 重写BaseMvpView中方法,加载数据时调用
*/
override fun onLoading() {
val handler = rootBinding.handler
handler?.let {
if (handler.showNetError) {
handler.showNetError = false
}
if (handler.showNoData) {
handler.showNoData = false
}
if (!handler.showLoading) {
val drawable = rootBinding.ivLoading.drawable
(drawable as? AnimationDrawable)?.start()
handler.showLoading = true
}
}
}
/**
* 重写BaseMvpView中方法,网络请求结束后调用,隐藏其他界面
*/
override fun onNetFinished() {
val handler = rootBinding.handler
handler?.let {
if (handler.showNetError) {
handler.showNetError = false
}
if (handler.showNoData) {
handler.showNoData = false
}
if (handler.showLoading) {
val drawable = rootBinding.ivLoading.drawable
(drawable as? AnimationDrawable)?.stop()
handler.showLoading = false
}
onListComplete()
}
}
/**
* 使用SwipeToLoadView时重写,完成刷新步骤
*/
protected fun onListComplete() {}
/**
* 标题栏左侧点击事件,默认结束当前界面
*/
override fun onLeftClick() {
finish()
}
/**
* 标题栏右侧点击事件
*/
override fun onRightClick() {}
/**
* 无数据界面点击事件,默认显示加载中
*/
override fun onNoDataClick() {
onLoading()
}
/**
* 网络异常界面点击事件,默认显示加载中
*/
override fun onNetErrorClick() {
onLoading()
}
}
就这样完成了 BaseActivity 的封装,使用起来也很简单:
/**
* 主界面
*/
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mFrags = ArrayList()
mFrags.add(MoviesListFragment())
mBinding.vp.adapter = FragVpAdapter.Builder()
.manager(supportFragmentManager)
.frags(mFrags)
.build()
}
override fun initTitleBar() {
showTitle()
setTitleStr("高评分电影")
}
}
详细代码大家可以看我的 Github项目:KotlinTest
在上面的代码中有几个简单的 Kotlin 语法要和大家说明:
// Java 中,如果类声明有泛型,而在使用时不需要泛型,那么直接不声明即可
abstract class BaseActivity {}
// 但是在 Kotlin 中必须使用 * 代替
abstract class BaseActivity
, DB : ViewDataBinding> {}
/*
* 在 DataBinding 中,Handler 的类型是可为空的,即 var handler: Handler?
* 在 Kotlin 中,调用可空类型的方法或属性必须使用安全调用 ?.
*/
handler?.showTvTitle = true // 等价于 if(null != handler) handler.showTvTitle = true
/*
* Kotlin 还提供了 let 函数,{}使用了 lambda 表达式, Kotlin 是默认支持的
*/
handler?.let {
dosomething...
}
// 等价于
if(null != handler) {
dosomething
}
/*
* Kotlin 中的类型转换使用 as 关键字,如果类型错误抛出异常
* 同时 Kotlin 也提供了安全转换 as? ,转换成功返回该类型对象,转换失败返回 null
*/
(drawable as? AnimationDrawable)?.start()
// 等价于 Kotlin
if(drawable is AnimationDrawable) { // 判断是否是该类型
drawable.start() // 使用 is 关键字判断类型,如果为 true 则自动转换为该类型使用
}
// 等价于 Java
if(drawable instanceof AnimationDrawable) {
((AnimationDrawable) drawable).start();
}
/*
* Kotlin 中有智能类型转换,即在变量声明时就赋值,可以省略声明时的类型,Kotlin 会根据赋的值确定变量的类型
*/
var str = "" // 自动确定为 String 类型
val handler = mBinding.handler // 自动确定为 Handler 类型