MVP簡介
相信大家對MVC都是比較熟悉了:M-Model-模型、V-View-視圖、C-Controller-控制器,MVP作為MVC的演化版本,那么類似的MVP所對應的意義:M-Model-模型、V-View-視圖、P-Presenter-表示器。 從MVC和MVP兩者結合來看,Controlller/Presenter在MVC/MVP中都起着邏輯控制處理的角色,起着控制各業務流程的作用。而 MVP與MVC最不同的一點是M與V是不直接關聯的也是就Model與View不存在直接關系,這兩者之間間隔着的是Presenter層,其負責調控 View與Model之間的間接交互,MVP的結構圖如下所示,對於這個圖理解即可而不必限於其中的條條框框,畢竟在不同的場景下多少會有些出入的。在 Android中很重要的一點就是對UI的操作基本上需要異步進行也就是在MainThread中才能操作UI,所以對View與Model的切斷分離是 合理的。此外Presenter與View、Model的交互使用接口定義交互操作可以進一步達到松耦合也可以通過接口更加方便地進行單元測試。

關於android的MVP模式其實一直沒有一個統一的實現方式,不同的人由於個人理解的不同,進而產生了很多不同的實現方式,其實很難去說哪一種更好,哪一種不好,針對不同的場合,不同的實現方式都有各自的優缺點。
而我采用的MVP是Google提出的一種MVP實現方式,個人認為這種方式實現簡單,更加適合android項目。傳統的MVP中Model起着處理具體邏輯的功能,Presenter起着隔離和解耦的作用,而在Google的MVP中弱化了Model,Model的邏輯由Presenter來實現,spManager、dbManager可以看做是Model,由Fragment實現View實現視圖的變化,Activity作為一個全局的控制者,負責創建view以及presenter實例,並將二者聯系起來。
實現步驟
1.BasePresenter
public interface BasePresenter { void start(); }
2.BaseView
public interface BaseView<P extends BasePresenter> { void setPresenter(P presenter); }
兩個接口分別作為Presenter和View的基類,僅定義了最基本的方法,具體頁面的view和presenter則分別定義繼承的接口,添加屬於自己頁面的方法。
3.Contract 契約類
這是Google MVP與其他實現方式的不同之一,契約類用於定義同一個界面的view和presenter的接口,通過規范的方法命名或注釋,可以清晰的看到整個頁面的邏輯。
public interface SampleContract { interface Presenter extends BasePresenter { //獲取數據 void getData(App app, int userId); //檢查數據是否有效 void checkData(); //刪除消息 void deleteMsg(App app, int msgId); ... } interface View extends BaseView<Presenter> { //顯示加載中 void showLoading(); //刷新界面 void refreshUI(MessageListEntity.CategoryData data); //顯示錯誤界面 void showError(); ... } }
4.具體的Impl類
Fragment實現View接口,這里使用Google推薦的創建Fragment實例的方法newInstance(),將fragment必備的參數傳入。
public class SampleFragment extends BaseFragment implements SampleContract.View{ private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; private String mParam1; private String mParam2; private SampleContract.Presenter mPresenter; /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment SampleFragment. */ // TODO: Rename and change types and number of parameters public static SampleFragment newInstance(String param1, String param2) { SampleFragment fragment = new SampleFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView textView = new TextView(getActivity()); textView.setText(R.string.hello_blank_fragment); return textView; } @Override public void setPresenter(SampleContract.Presenter presenter) { mPresenter = presenter; } @Override public void refreshUI(MessageListEntity.CategoryData data) { //change UI } @Override public void showError() { //change UI } }
Presenter實現類,提供一個參數為對應View的構造器,持有View的引用,並調用View的setPresenter()方法,讓View也持有Presenter的引用,方便View調用Presenter的方法。
public class SamplePresenterImpl implements SampleContract.Presenter { private SampleContract.View mView; public SamplePresenterImpl(SampleContract.View mView) { this.mView = mView; mView.setPresenter(this); } ... }
5.最后就是Activity
創建view以及presenter實例,並將二者聯系起來。
public class SampleActivity extends BaseActivity { public static final String FRAGMENT_TAG = "fragment_tag"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout_detail); init(); } private void init() { //初始化view FragmentManager fragmentManager = getSupportFragmentManager(); SampleFragment fragment = (SampleFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); if (fragment == null) { fragment = SampleFragment.newInstance(param1, param2); fragmentManager.beginTransaction().add(R.id.fl_container, fragment, FRAGMENT_TAG).commit(); } //初始化presenter new SamplePresenterImpl(fragment); } }
下圖是Google官方Demo:todo-mvp模式的架構圖
從結構方面觀察一下MVP模式

為什么使用MVP?
文章一開始並沒有說MVP的好處,而是先介紹了如何實現MVP,在這里我會通過分析來體現MVP的好處。
1.解耦
毋庸置疑這是最大的好處,通過上述例子可以看到一個頁面被分成了兩個部分,一個是視圖的邏輯,另一個是業務邏輯。兩者持有的引用都是對方的接口,因此可以隨意地替換實現(頁面大修改),並且單獨修改view或者presenter的邏輯並不會影響另一方(小修改)。
2.代碼清晰
以前的MVC模式,當一個頁面的邏輯足夠復雜,就會出現一個activity上千行的情況,在這樣的一個類中想定位一個bug是十分困難的,有時候自己的敲的代碼看着都像別人的代碼,得重頭捋一捋才能定位,相當耗時。而用MVP模式,一個頁面的邏輯都可以從Contract類中直觀的看到有哪些,找到對應邏輯的方法再進入實現類中找到問題所在,效率上不可同日而語。
3.靈活
好多MVP的實現都是用Activity來實現View,這里使用Fragment便是出於靈活的考慮,Fragment可以復用,可以替換。面對多變的需求可以更從容的應對,例如:一個頁面原本是一個列表,后來要求這個頁面顯示2個Tab,原來的列表變為其中一個Tab。類似的情況可能很常見,如果是用Activity實現的,可能要修改activity類中的很多代碼,而原本就使用Fragment和MVP的話,僅需添加一個Fragment,修改Activity假如切換Tab的邏輯,而不用修改第一個Fragment的任何邏輯,這更符合OOP的編程思想。
4.簡化
開頭說過,傳統MVP中Model起着處理具體邏輯的功能,Presenter起着隔離和解耦的作用。這種方式實現的MVP中Presenter看上去更像一個代理類,僅僅是不讓View直接訪問Model。雖然這樣做解耦的程度更高,但實際項目中,一個頁面邏輯的修改是非常少的,僅僅在產品修改需求是才會發生,大部分情況下僅僅是修復bug之類的小修改,並需要這樣徹底的解耦。從另一個方面來說,一個頁面的視圖邏輯和業務邏輯本就是一體的。因此,Google的MVP弱化的Model的存在,讓Presenter代替傳統意義上的Model,減少了因使用MVP而劇增的類。這種方式相比不適用MVP僅從1個activity,增加到了1個activity,1個fragment(view),1個presenter,1個contract(如果有傳統意義上的Model,還會有1個Model的接口和1個實現類)。
注意點!
1.一段邏輯到底是放在View還是放在Presenter?其實從定義我們也可以知道View只處理界面的邏輯,任何邏輯判斷都應該在presenter中實現。但在實際過程中很容易出現View處理邏輯的情況,例如:網絡請求返回一些數據,需要對數據進行刪選或處理,處理的過程很容易發生在View層,這是需要極力避免的情況,所有不涉及界面的判斷都要放到presenter層。
2.類名、方法名的規范。頁面的契約類都以contract結尾,presenter的實現類都以PresenterImpl結尾。View和Presenter接口中的方法需要見名知意,意義模糊的可以適當添加注釋。規范的命名有助於后續的修改和他人的維護。
3.子線程回調。Presenter中處理子線程任務完成后,一般會回到主線程調用View的方法刷新UI,但如果此時activity已經銷毀,並且沒有取消子線程的任務(例如網絡請求),此時去調用View的方法很容易發生空指針,應該在調用之前判斷一下,因此建議view增加一個isActive()方法,用於判斷當前view是否可見:
public interface BaseView<P extends BasePresenter> { void setPresenter(P presenter); boolean isActive(); }
在fragment中只需這樣實現即可:
@Override public boolean isActive() { return isAdded(); }
除此之外,如果子線程的任務會持續很久,或者由於網絡等額外因素導致子線程耗時過久,但此時activity其實已經destroy了,presenter的子線程還在運行則不會被GC,並且presenter持有了fragment(view),導致了內存泄露。
解決這個問題可以通過弱應用的方式解決,下是實現方式:
public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter { protected WeakReference<V> mView; public BasePresenterImpl(V view) { mView = new WeakReference<V>(view); view.setPresenter(this); } protected boolean isViewActive() { return mView != null && mView.get().isActive(); } public void detachView() { if (mView != null) { mView.clear(); mView = null; } } }
view也可以抽象abstract類。
作者:胡奚冰
鏈接:https://www.jianshu.com/p/bbb3b77d47eb
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。