MVP簡介 相信大家對MVC都是比較熟悉了:M-Model-模型、V-View-視圖、C-Controller-控制器,MVP作為MVC的演化版本,那麼類似的MVP所對應的意義:M-Model-模型、V-View-視圖、P-Presenter-表示器。 從MVC和MVP兩者結合來看,Controll ...
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的交互使用介面定義交互操作可以進一步達到松耦合也可以通過介面更加方便地進行單元測試。
MVP結構圖關於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模式
20160510110516376.png
為什麼使用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
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。