MVP這種架構在Android界已經基本成為標配,MVP本身也有很多寫法和變種,當然,沒有最好的架構,只有最合適的架構,具體架構要怎么寫,還是要看實際項目的需要。
我們在這里簡單梳理一下MVP的一些演化版本,希望為具體的項目實現提供一點參考。
MVP本身的概念,就是把Model、View和Presenter相互解耦,大概可以這樣理解:
各自分工如下:
- View負責外部界面交互,不直接處理業務邏輯
- Presenter負責內部業務邏輯,不直接處理業務數據
- Model負責核心業務數據,與數據庫和網絡進行數據交互
原始MVP
如果僅從分工的角度實現MVP,只需要發生兩次引用:
- 向View里引用Presenter,(因為View都是Fragment或Activity,有特定的構造函數,所以一般采用set方式引用),以處理具體的內部業務邏輯,代碼形如:
public class TasksFragment extends Fragment{
...
private Presenter mPresenter;
public void setPresenter(Presenter presenter) {
mPresenter = presenter;
}
...
}
- 向Presenter里引用Model和View,其中View需要通過接口封裝一下再引用(一般在構造函數中引用),引用Model為業務邏輯提供核心的業務數據,引用View操作與界面相關的業務邏輯,代碼形如:
public class Presenter{
...
private Repository mRepository;//model的實現這里不再展開
private TasksFragment mView;
public Presenter(Repository tasksRepository, TasksFragment tasksView) {//presenter里引用model
mRepository = tasksRepository;
mTView = tasksView;
mTasksView.setPresenter(this);//view里引用presenter
}
...
}
這就是一個最原始的MVP的實現,這個版本有一個嚴重的問題,就是可維護性太差!
這版MVP雖然實現了各司其職,但其實質只不過是把代碼拆到了不同的文件里,在實現中,M、V和P都引用了實體類的實例,形成了非常緊密的耦合,它其實只是實現了這樣的效果:
很顯然,難以復用,難以擴展,未來的維護簡直是個災難。
為了解耦合,很自然地,要使用接口去解耦合。
演化1-Google Architecture
Google在github上開源的architecture是個教科書般的MVP框架,它是這樣做的:
- V和P的接口化和注入
View和Presenter不再引用實體類,而是引用抽象接口,View里引用的Presenter的接口,Presenter里引用的也是View的接口,這樣的View和Presenter的代碼形如:
public class TasksFragment extends Fragment implements IView{//實現接口以便注入到Presenter
...
private IPresenter mPresenter;
public void setPresenter(IPresenter presenter) {//view已有特定的構造函數,以set方式注入為宜
mPresenter = presenter;
}
...
}
和
public class Presenter implements IPresenter{//實現接口以便注入到view
...
private Repository mRepository;//model的實現這里不再展開
private IView mView;
public Presenter(Repository tasksRepository, IView tasksView) {//presenter里注入model和view
mRepository = tasksRepository;
mTView = tasksView;
mTasksView.setPresenter(this);//view里注入presenter
}
...
}
如果愿意的話,model也可以采用接口注入的形式(google architecture并沒有做model的接口注入,是為了確保引用的實例是一個全局唯一的數據層單例,這樣容易避免污染數據),這樣就能實現一個完好解耦的MVP:
- 集中管理V和P的接口
其實就是把V和P的接口放在同一個接口文件下了,代碼形如:
public interface ITasksContract {
interface IView{...}
interface IPresenter{...}
}
這樣做有兩個好處:
1.從一組業務來講,業務邏輯和界面邏輯在同一個文件中定義,極具連貫性,極大地方便了閱讀、理解和維護(這也會引導你先從接口開始寫代碼)
2.從多組業務來講(App一般有多組業務),便于管理好多個V和P的接口,這些接口天然按照業務分別寫在不同文件里,擴展和引用更加清晰,不易出錯
google architecture還做了一項改進,為V和P的接口定義了更基礎的接口,在基礎接口中統一定義了View注入Presenter的行為和Presenter開啟業務工作的行為,代碼形如:
public interface BaseView<T> {//用泛型定義Presenter
void setPresenter(T presenter);//用set注入Presenter
}
和
public interface BasePresenter {
void start();//開啟業務工作
}
你自己實現的V和P的接口,只要繼承基礎接口,就能保證MVP基礎行為的一致性,這樣你的V和P就可以更加專注于業務
(除了教科書般的MVP,google architecture還提供了教科書般的數據Model層實現,不過這里就不做展開了)
演化2-泄露的問題
上面的這種做法,有一個潛在的問題,就是內存泄露
我們知道,Presenter為了實現業務邏輯,一手持有數據Model,一手持有View,這里面有一個隱含的bug:
數據Model在處理數據時,無論是處理本地數據還是網絡數據,都是耗時操作,是不能在主線程運行的;而View,是必須在主線程運行的。這就容易產生一個問題,當View關閉退出時,Presenter可能還在異步線程里工作,而且Presenter還持有著View的實例——標準的內存泄露場景
要避免持有型的內存泄露,一個很有效的辦法就是把強引用的持有變成弱引用,就是說,在Presenter里,要用WeakReference的方式去持有View,實現代碼形如:
protected WeakReference<T> mViewRef; // view 的弱引用
public void attachView(T view){//持有View
mViewRef = new WeakReference<T>(view);
}
public void detachView(){
if (mViewRef != null){
mViewRef.clear();
mViewRef = null;
}
}
public T getView() {//獲取view的實例
return mViewRef.get();
}
這段代碼其實是通用代碼,根據聚焦業務的原則,應該抽象為基礎行為,而接口是不能實現任何方法的,所以,這段代碼只能通過抽象類實現通用化,整個類的代碼形如:
public abstract class MVPBasePresenter<T> {
protected WeakReference<T> mViewRef; // view 的弱引用
public void attachView(T view){//持有View
mViewRef = new WeakReference<T>(view);
}
public void detachView(){
if (mViewRef != null){
mViewRef.clear();
mViewRef = null;
}
}
public T getView() {//獲取view的實例
return mViewRef.get();
}
}
其中,attachView和detachView要在View的相應的生命周期中調用,這樣的話,我們又需要為View實現相關的抽象類,Fragment和Activity都需要
//需要兩個泛型類型,一個用來繼承Presenter的抽象類,而這個Presenter抽象類又需要一個View的泛型
public abstract class MVPBaseFragment<V,T extends MVPBasePresenter<V>> extends Fragment {
protected T mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
mPresenter.attachView((V)this);
}
@Override
public void onDestroy() {
super.onDestroy();
if(mPresenter!=null)
mPresenter.detachView();
}
protected abstract T createPresenter();
}
這樣一個業務Fragment在實例化時,代碼形如:
//ICategoryContract.View是IView接口
//我們還定義了ICategoryContract.Presenter作為IPresenter接口
//CategoryPresenter繼承了MVPBasePresenter抽象了和IPresenter接口
public class MainFragment
extends MVPBaseFragment<ICategoryContract.View,CategoryPresenter>
implements ICategoryContract.View {...}
我們實際上把Presenter抽象類和IPresenter業務接口做了分離,把View抽象類和IView業務接口做了分離,基礎行為和業務邏輯互不干擾。
Activity的代碼內容類似,這里不再重復。
到了實際項目中,V和P分別繼承對應的抽象類,因為抽象類里已經實現了弱引用和相關的管理,所以我們可以專注于業務邏輯的實現。
不過,這樣做帶來兩個問題:
- 如果在View的構造函數中自動處理Presenter的實例化,實際上會束縛了我們自己的寫作方式,比如我們的Presenter需要注入Model,就不能用構造方式注入;更嚴重的是,如果我們在Presenter初始化時需要設置某些UI控件,因為抽象類的oncreate需要先于業務類的oncreate去執行(業務類里需要先執行super.oncreate),會遇到UI控件不能及時初始化的問題。
- Android的View其實是在不斷擴張的,以Activity為例,常見的就包括Activity、AppCompatActivity、FragmentActivity、RxAppCompatActivity等,如果使用這種抽象類的模式,每遇到一種Activity,就得去做一個對應的抽象類,可擴展性很差。
參照Google的做法,我們應該再多做一點接口的文章
演化3-View的剝離
我們回頭再看一遍Presenter抽象類
//Presenter抽象類
public abstract class MVPBasePresenter<T>{...}
其實在Presenter抽象類里,用來處理View的泛型是與業務無關的,我們此前是做了一個View的抽象類來配合Presenter做弱引用處理,其實細想起來,這個View的角色沒必要使用抽象類,我們用一個IView基礎接口就可以滿足需要:
//基礎接口,不需要定義任何方法
public interface IMVPBaseView {}
我們的業務接口里,IView業務接口繼承這個基礎接口:
public interface CategoryContract {
...
interface View implements IMVPBaseView{
...
}
}
我們的Fragement可以恢復Google教科書那樣的簡潔:
public class TasksFragment extends Fragment implements IView{//實現的接口中包含基礎行為和業務邏輯
...
}
最終,我們的MVP結構是這樣的:
Model:接口注入(更靈活)或引用一個全局單例(更干凈)
View:IMVPBaseView(基礎行為)-> IContract.View(業務邏輯)-> XXFragment(V的具體實現)
Presenter:(MVPBasePresenter(基礎行為) + IContract.Presenter)-> XXPresenter(P的具體實現)
當然,在這種方式下,Presenter的創建、初始化、銷毀等行為,也還給了最終的業務Fragment(或Activity)。
演化4-Dagger
MVP里面其實有大量的依賴關系和注入行為,代碼會顯得比較復雜,而Dagger是一個專門處理依賴注入的框架,可以用配置的方式實現復雜的依賴關系,所以我們完全可以用Dagger來實現MVP
在Dagger(Dagger2)里,核心要素就是Module、Inject和Component,它們分別起這樣的作用:
- Module:提供依賴,其實就是把我們此前用set或構造參數注入的依賴實例,改用module配置出來,由Dagger負責傳給要注入的類,比如把IView和數據Model注入到Presenter里,代碼形如:
//為presenter提供IView參數實例
@Module
public class TasksPresenterModule {
private final TasksContract.View mView;
public TasksPresenterModule(TasksContract.View view) {
mView = view;
}
@Provides//提供參數的函數方法
TasksContract.View provideTasksContractView() {
return mView;
}
}
和
//為presenter提供數據Model參數實例(google官方示例里又嵌套了幾層component)
@Singleton//要求dagger實現單例
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}
- Inject:指定依賴,就是說明某個屬性對象是需要用Module注入進來的,比如在Presenter里說明某個modle對象和某個view對象是需要dagger注入進來的,代碼形如:
//presenter類的參數改用Dagger注入
class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
/**
* Dagger strictly enforces that arguments not marked with {@code @Nullable} are not injected
* with {@code @Nullable} values.
*/
@Inject //參數是需要注入的
TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
mTasksRepository = tasksRepository;
mTasksView = tasksView;
}
...
}
同樣,在Activity里也要用dagger注入persenter,代碼形如:
public class TasksActivity extends AppCompatActivity {
@Inject TasksPresenter mTasksPresenter;//內部對象是需要注入的
...
}
- Component:組裝器,做兩件事:1-把做好的Module對象作為參數提供給要注入的類,比如把Modle對象和IView對象實例化,作為Presenter的參數,完成Presenter的實例化;2-把完成注入和實例化的類,注入到當前類里,比如把完成實例化的Presenter注入到Activity里,代碼形如:
public class TasksActivity extends AppCompatActivity {
@Inject TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Create the presenter
DaggerTasksComponent.builder()
//component會做出presenter需要的兩個參數
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment))
.build()//構造出Presenter的實例
.inject(this);//把Presenter注入到當前Activity中
...
}
...
}
Dagger只是用注解來配置依賴關系,編譯時還是用工廠類和傳參等形式實現的依賴注入,例如,針對上述代碼,Dagger的apt插件會在編譯時把它轉成形如這樣的代碼:
...
//生成的Component類里,Module工廠類實現Module的實例化
this.provideTasksViewProvider = MainModule_ProvideTasksViewFactory.create(builder.mainModule);
...
//生成的Component類里,Presenter工廠類實現Presenter的實例化
mainPresenterProvider = MainPresenter_Factory.create(provideTasksRepositoryProvider,provideTasksViewProvider);
...
//生成的Activity的Injector類里,用構造參數實現依賴注入
this.mainPresenterProvider = mainPresenterProvider;
這樣用Dagger實現的MVP,最開始會有點別扭,因為類之間的注入關系好像不像直接代碼實現那樣熟悉,但習慣之后,你會發現這么幾個好處:
1.基于JSR330的穩定和標準的依賴注入方法
2.依賴關系是配置化的,代碼可讀性更強,也容易聚焦業務
3.可以通過注解實現全局單例
演化5-Kotlin的引入
作為基礎通用框架,我們必須有一個Kotlin的版本,當然,不同的演化版本,會有不同的寫法,如果參照演化3的版本,對應的Kotlin版本形如:
//基礎IMVPView接口
interface IMvpView {
}
//基礎MvpPresenter抽象類
abstract class MvpPresenter<T:IMvpView> {
protected var mViewRef:WeakReIference<T>?=null
fun attachView(view:T){
mViewRef= WeakReference(view)
}
fun detachView(){
if(mViewRef!= null){
mViewRef!!.clear()
mViewRef=null
}
}
val view:T? get() = mViewRef!!.get()
}
//業務邏輯接口
interface ICatContract {
interface View<Presenter>{
fun refreshUI()
}
interface Presenter{
fun doInitPage()
}
}
//業務Presenter
class CatPresenter : MvpPresenter<CatActivity>(),ICatContract.Presenter {
override fun doInitPage() {
}
}
//業務Activity
class CatActivity : AppCompatActivity(),MvpView,ICatContract.View<CatPresenter> {
val TAG: String = "CatActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
override fun refreshUI() {
}
}
總結
MVP作為一個基礎型的結構,核心作用在于輔助我們實行良好的可讀性和可維護性,我們可以為一個Presenter提供多種View的實現(例如,一個業務可以同時有全屏Activity和對話框Activity兩種形式,分別提供給不同的業務環節,背后卻使用同一個Presenter),也可以為一個Presenter提供不同的數據Model(例如,在兩個根據后臺數據動態繪制界面的Activity實例中,業務邏輯一致,可以使用同一種Presenter,但數據內容不同,就可以使用兩個分別注入了不同Model的Presenter實例)
MVP里有Passive View(Presenter通過View的接口操作View,并作為主要的業務驅動方)和Supervisor Controller(Presenter負責大量復雜的View邏輯)兩種衍生,
MVP還是一個開放性的結構,你可以根據自己的需要,去規避某些缺陷,或取得某些優勢,如何去演化一個適合自己需求的MVP框架,一方面滿足需求,一方面保持靈活,完全看自己的發揮了
關于MVVM
MVP的結構比較通透明了,不過其中的View總是要寫一些業務邏輯相關的代碼,比如操縱Presenter,處理生命周期,實例化Model對象等,如果需要更進一步,把View的角色限定為純粹的UI,不做任何業務邏輯,不涉及任何數據,就需要用到MVVM模式了。
在MVVM模式里,不再有Presenter,用ViewModel來處理業務邏輯,ViewModel不處理UI,而View只負責UI,與ViewModel建立數據綁定關系,通過databinding自動實現UI和Model之間的數據操作。
技術細節推薦閱讀如何構建Android MVVM應用程序
引用
Github Google android-architecture
Android App的設計架構:MVC,MVP,MVVM與架構經驗談