Github地址:https://github.com/cn-ljb/mvp-kotlin -> Star 一下o(∩_∩)o
該篇文章主要講解,如何使用Kotlin語言搭建一個高效、解耦、快捷的Android MVP架構。
什麼是MVP架構?
簡單介紹下:
通常一般的Android項目結構,我們會在Activity\Fragment中編寫大量代碼,例如:網絡請求、數據填充、頁面切換等等,這種項目結構宏觀的稱之爲MVC。
MVC:我們可以把數據源(網絡請求、IO…)看作Model層,xml等佈局文件看作View層,Activity\Fragment看作Controller層。但在android中xml能力太薄弱了,以至於Activity不得不做很多本不屬於它的工作。
MVP:在MVP架構中Model層與MVC一樣作爲數據源,不過將Activity\Fragment都看作爲View層的一部分負責數據的展示和填充,將Model層與View層的關聯操作交給了Presenter層。
一個基本的MVP架構圖如下:
與之前的Android MVC相比,不僅Activity的分工不明確問題得到了解決,還帶來另一個好處:Model層與View層不再直接可見,耦合問題得到解決。
該項目MVP架構
在此基礎上,該項目中的MVP架構對每個模塊進行細化,大致架構圖如下:
1、View層根據自己的需要繼承對應的BaseMvpActivity\BaseMvpFragment\BaseMvpFragmentActivity,並實現createPresenter()函數,它們提供基礎的View層模版;
/** * 1、繼承BaseMvpActivity * 2、通過泛型告訴View層,當前Presenter的契約接口LoginContract.IPresenter * 3、實現自己的契約接口LoginContract.IView */ class LoginActivity : BaseMvpActivity<LoginContract.IPresenter>(), LoginContract.IView { override fun createPresenter() = LoginPresenter(this) ... }
2、Presenter層提供了基礎的IBasePresent接口模板,考慮到整個項目使用rxjava2作爲異步庫,爲了方便管理rx生命週期,額外提供了一個BaseRxLifePresenter抽象類;
/** * 1、繼承BaseRxLifePresenter * 2、通過泛型告訴Presenter層,當前View的契約接口LoginContract.IView * 3、實現自身的契約接口LoginContract.IPresenter */ class LoginPresenter(mvpView: LoginContract.IView) : BaseRxLifePresenter<LoginContract.IView>(mvpView), LoginContract.IPresenter { //rxjava生命週期管理舉例 override fun delayGoHomeTask() { Observable.timer(1500, TimeUnit.MILLISECONDS) .subscribe { getMvpView().goHome() } .bindRxLifeEx(RxLife.ON_DESTROY) } //登錄功能 override fun login(userName: String) { RxUtils.dispose(mLoginDisposable) mLoginDisposable = UserProtocol.getUserInfoByName(userName) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeEx({ if (it.message.isNullOrBlank()) { getMvpView().loginSuccess() } else { getMvpView().loginError(it.message) } }, { getMvpView().loginError(null) }).bindRxLifeEx(RxLife.ON_DESTROY) } }
3、View層與Presenter層的交互通過接口進行解耦。例如上方的LoginActivity與LoginPresenter的交互都在LoginContract中進行限制;
/** * 登錄頁View層\Presenter層通訊契約接口 */ interface LoginContract { interface IView : IBaseViewContract { fun loginSuccess() fun loginError(errorMsg: String?) fun goHome() } interface IPresenter : IBasePresenterContract { fun login(userName: String) fun delayGoHomeTask() } }
4、Modle層封裝Protocol,用於包裝數據源並提供轉換爲Observable的函數,方便與Rxjava2結合使用(項目中DaoProtocol通過繼承BaseDAOProtocol實現,HttpProtocol通過Retrofit實現,這塊可以根據自己實際情況封裝),並實現自身向Presenter公開的約束接口;
class UserDaoProtocol : BaseDaoProtocol(), IUserDaoProtocol { override fun saveUser(context: Context, user: User) = createObservable { saveUserImpl(context, user) } private fun saveUserImpl(context: Context, user: User): Boolean = if (findUserByUserIdImpl(context, user.id) == null) { insertUserImpl(context, user) } else { updateUserImpl(context, user) } ... }
————— 分割線 ——————
//Model層對外提供的約束接口 interface IUserDaoProtocol : DaoInterface { /** * 保存用戶信息 * */ fun saveUser(context: Context, user: User): Observable<Boolean> ... }
5、每個Protocol對象建議通過Factory產出(DaoProtocol需在DaoFactoryConfig中進行配置,HttpProtocol得益於Retrofit自身的實現,不需要我們手動關聯工廠代碼),從而與Presenter進行解耦;
object DaoFactoryConfig { //TODO 在此處配置DAO接口 @Suppress("UNCHECKED_CAST") fun <T> configProtocol(clazz: Class<T>): T = when (clazz) { IUserDaoProtocol::class.java -> UserDaoProtocol() else -> throw IllegalStateException("NotFound Dao Interface Object : ${clazz.name}") } as T }
例如:通過DaoFactory獲取UserDAOProtocol的父級IUserDaoProtocol接口引用,而不是它的自身引用,避免直接操作接口約束之外的公共域:
DaoFactory.getProtocol(IUserDaoProtocol::class.java).saveUser(context, user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()6、一個View對應一個Presenter,View與Presenter交互通過Contract接口進行約束,一個Presenter可對應多個Modle對象,這些Modle對象通過Factory產出(Protocol,每個Protocol都應該是可複用,且內存安全的).