淺談MVP架構的實現方式(架構思想)
  • MVP模式
    – 談起MVC模式可能大家都耳熟能詳,最開始多用於web應用的開發,後在移動開發的過程中也引入了MVC,但是很多公司的專案在使用MVC的時候並沒有很好的將三層解耦,很多的資料請求操作仍是在Activity裡面執行,造成很多程式碼的可維護性仍然不高。
    – MVP模式的使用大大降低了mode和view之間的耦合度,方便應用的擴充套件。MVP結構中View和Model都通過Presenter來進行解耦。
    MVC模式結構
    MVP模式結構

專案主要實現的功能是展示一個列表資料,用不同的方式展示,一種是ListView的方式,一種是GridView的方式,專案結構通過對擴充套件開放對修改關閉,在不修改原始碼的情況下修改展示方式。
這個是ListView的展示方式

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(new AvatarListAdapter(this, list));
}

上面的是最基本的實現方式,接下來使用MVP來實現,在MVP模式中有三個角色,Model、View、Presenter,先從簡單的Model開始,Model中所要做的工作就是獲取資料,在實際開發中,最好有一個BaseModel的介面定義一些抽象方法,Demo中為了演示是從本地獲取的資料,在實際的開發中則為網路資料載入。

BaseModel的程式碼:
public interface BaseModel
{
//載入資料的方法,因為沒有返回值所以在資料獲取到之後需要回撥
void loadData(ResultCallBack callBack);
/**
* 載入完資料之後回撥的函式
*/
interface ResultCallBack
{
void onResult(List result);
}
}
具體實現獲取資訊的model:
public class AvatarListModel implements BaseModel
{
@Override
public void loadData(ResultCallBack callBack)
{
List<AvatarBean> list = new ArrayList<AvatarBean>();
list.add(new AvatarBean(R.mipmap.image_1,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_2,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_3,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_4,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_5,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_6,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_7,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_8,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_9,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_10,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_11,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_12,"XiaoMing"));
//載入完成資料後回撥處理
callBack.onResult(list);
}
}

上述講述了model的定義,model層完成之後來進行View層的編碼,由於在MVP模式中view只需要來進行顯示或者事件分發操作,所以可以先抽象出一個View介面用來定義一些需要實現的方法(該介面需要在Avtivity中去實現。此處為什麼需要定義View介面,因為在Presenter中需要傳遞一個View來供Presenter獲取資料之後回撥具體的操作,後面會講到)

View介面的定義:
public interface AvatarListView
{
/**
* 顯示載入彈窗
*/
void showProgress();
/**
* 獲取資料之後顯示資料
*/
void showData(List<AvatarBean> result);
}

因為專案實現的是一個資料列表,所以該頁面需要的操作就是載入資料的提示和顯示資料。
下一步是定義Avtivity來實現這個View

Activity的程式碼:
public class AvatarListActivity extends BaseActivity implements AvatarListView
{
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
AvatarListPresenter presenter = new AvatarListPresenter(this);
presenter.fetch();
}
/**
* 該方法是提示載入資料,實際開發中可能為一個載入動畫
*/
@Override
public void showProgress()
{
Toast.makeText(this, "一大波美女即將到來", Toast.LENGTH_SHORT).show();
}
/**
* 該方法是在獲取資料之後顯示到介面卡
* @param result 獲取到的資料
*/
@Override
public void showData(List<AvatarBean> result)
{
listView.setAdapter(new AvatarListAdapter(this,result));
}
}

實現了View和Model下面就要實現Presenter,作為MVP中最重要的角色,Presenter作為中間人的最主要的任務就是請求資料,通知View顯示資料,下面來看看Presenter的程式碼:

presenter的程式碼:
public class AvatarListPresenter
{
//Model層中的model,用來獲取資料
private BaseModel model;
//View層的View,即實現了該介面的activity
private AvatarListView view;
/**
* Presenter從網路上夾在資料並且交由View處理
*/
public void fetch()
{
view.showProgress();
model = new AvatarListModel();
model.loadData(new AvatarListModel.ResultCallBack()
{
@Override
public void onResult(List result)
{
view.showData(result);
}
});
}
}

這裡大家可以明白為什麼在寫View的時候需要一個介面了吧,在這裡面我們可以使用里氏代換,然後呼叫父類的方法,子類的實現。到目前為止已經用MVP實現了這個功能。大家整理一下思路然後繼續下面的東西
現在需要換一種方式來實現這個功能,上面的是使用一個ListView,接下來將使用GridView,可能大家會說直接寫一個佈局吧Activity中的ListView一更換就行了,確實是可以實現,但是如果也無需求又需要修改回ListView呢,難道我們就一直改這個,並且程式設計的思想要求我們擴充套件,所以現在用MVP實現GridView,由於資料獲取方式和Avtivity要做的工作不變,所以Model和View介面不需要更改,只需要重新定義一個Activity就行。

GridView方式實現程式碼:
public class AvatarGridActivity extends BaseActivity<AvatarListView,AvatarListPresenter> implements AvatarListView
{
GridView gridView;
private List list;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_avatar_grid);
gridView = (GridView)findViewById(R.id.gridView);
gridView.setAdapter(new AvatarGridAdapter(this,list));
AvatarListPresenter presenter = new AvatarListPresenter(this);
presenter.fetch();
}
@Override
public void showProgress()
{
Toast.makeText(this, "資料載入中。。。", Toast.LENGTH_SHORT).show();
}
@Override
public void showData(List<AvatarBean> result)
{
gridView.setAdapter(new AvatarGridAdapter(this,result));
}
}

修改的東西就是更換了一個GridView,但是對於我們開發者來說這樣更利於程式碼維護,有利於擴充套件。

ok,接下來我們考慮一個問題,就是如果我們的資料獲取方式變了呢,如果開始用的是retrofit,但是技術經理非得要求用OkHttp呢,根據上面的思路,我們需要重新定義一個Model

public class AvatarNetWorkModel implements BaseModel
{
@Override
public void loadData(ResultCallBack callBack)
{
//執行緒休眠5秒鐘
/*try
{
Thread.sleep(5000);
} catch (InterruptedException e)
{
e.printStackTrace();
}*/
//模擬網路請求資料
//模擬網路延時
SystemClock.sleep(5000);
List<AvatarBean> list = new ArrayList<AvatarBean>();
list.add(new AvatarBean(R.mipmap.image_1,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_2,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_3,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_4,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_5,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_6,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_7,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_8,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_9,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_10,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_11,"XiaoMing"));
list.add(new AvatarBean(R.mipmap.image_12,"XiaoMing"));
//載入完成資料後回撥處理
callBack.onResult(list);
}
}

這樣我們在presenter中只需要修改一下呼叫的方法即可達到不同方式的獲取資料。

接下來大家需要考慮一個問題,既然說架構,需要考慮的東西當然很多,舉一個例子:加入我們在Avtivity中進行網路請求,但是在沒有請求完成之前該頁面被銷燬了,使用者點選了返回,那麼由於Presenter持有了該VIew物件,所以造成了記憶體不能回收activity佔用的記憶體,造成了記憶體洩漏問題,如何來解決這樣的問題,這裡我使用WeakReference。先梳理一下思路:

Presenter持有View的引用,所以如果想解決記憶體洩漏,該持有方式應該為弱引用,既然每個Presenter都需要時弱引用,所以這裡我用一個抽象類來實現

public abstract class BasePresenter<T>
{
protected WeakReference<T> mViewRef;
/**
* Presenter關聯View,使用弱引用保證在View被銷燬之後及時回收記憶體,避免記憶體洩漏
* @param view
*/
public void attachView(T view)
{
mViewRef = new WeakReference<T>(view);
}
/**
* 解除關聯
*/
public void detachView()
{
if (mViewRef != null)
{
mViewRef.clear();
mViewRef = null;
}
}
protected T getView()
{
return mViewRef.get();
}
}

用泛型是因為我們不能確定使用者是那一個View。
有了Presenter我們需要考慮activity了,在activity銷燬的時候我們需要釋放弱引用,在建立activity的時候我們需要繫結View到Presenter,所以BaseActivity的程式碼為:

public abstract class BaseActivity<V ,T extends BasePresenter<V>> extends Activity
{
protected T presenter;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
presenter = createPresenter();
//將View關聯到Presenter
presenter.attachView((V)this);
}
@Override
protected void onDestroy()
{
super.onDestroy();
//解除關聯
presenter.detachView();
}
/**
* 獲取每個頁面中的需要的Presenter,用來獲取資料並回撥頁面處理
* @return
*/
protected abstract T createPresenter();
}

ok 到此已經都實現完了,有了BaseActivity和BasePresenter之後可能上面的activity和presenter都有需要修改的地方,這裡程式碼就不再修改了,該原始碼已經上傳到了Github。如果有什麼問題大家可以向我提出疑問。本人QQ:2592522521

專案原始碼地址:https://github.com/zhangXiaolizi/ZglMVP