MVP模式已經(jīng)是老生常談了汗销,很多程序員用過這個架構(gòu)進行開發(fā)跟束,但是從無到有搭建MVP架構(gòu)的過程,或許沒有體驗過萨咕,在此我們來一步步搭建MVP環(huán)境统抬。
先說說概念,MVP: Model View Presenter
Model:獲取數(shù)據(jù)業(yè)務(wù)邏輯
View: UI展示
Presenter: 實現(xiàn)者 Model與View相互獨立危队,Presenter負責(zé)將Model與View綁定,是一個中間者的角色聪建。
如圖:MVC模式中,Model與View相互持有對方的引用茫陆,視圖與數(shù)據(jù)邏輯交互金麸。
在實際開發(fā)中,業(yè)務(wù)邏輯基本都是在Activity / ViewController中實現(xiàn)的簿盅,導(dǎo)致Controller的臃腫和難以維護挥下。
而在MVP模式中:Model與View完全被Presenter隔離,而Presenter作為一個中間者桨醋,負責(zé)將Model與View綁定棚瘟。
假設(shè)我們的有一組數(shù)據(jù)模型叫StudentBean
public class StudentBean {
private String name;
private String sex;
private String ege;
private String phone;
//各種set get屬性的方法...
首先我們創(chuàng)建出Model,獲取StudentBean的數(shù)據(jù)喜最;同時我們也希望在數(shù)據(jù)獲取成功時有所反饋偎蘸,于是我們在數(shù)據(jù)獲取結(jié)束時,來個回調(diào)瞬内,實現(xiàn)如下:
/**
* Created by Shmily on 2018/1/18.
* Model接口
*/
public interface IStudentModel {
/**
* 加載數(shù)據(jù)
* @param listener
*/
void fetchData(OnLoadListener listener);
/**
* 加載完成的回調(diào)
*/
interface OnLoadListener {
void onLoadSuccess(List<StudentBean> list);
void onLoadFailed();
}
}
由代碼我們可以看出Model提供了獲取數(shù)據(jù)的方式迷雪,并在數(shù)據(jù)獲取結(jié)束時給予回調(diào)方法。
現(xiàn)在我們實現(xiàn)這個Model接口
public class StudentModelImpl implements IStudentModel{
/**
* 獲取數(shù)據(jù),并在結(jié)束時實現(xiàn)回調(diào)
* @param listener
*/
@Override
public void fetchData(OnLoadListener listener) {
if (listener == null) {
return;
}
ArrayList<StudentBean> list = loadDataFromLocal();
if (list == null || list.isEmpty()){
listener.onLoadFailed();
}
else {
listener.onLoadSuccess(list);
}
}
/**
* 模擬加載本地數(shù)據(jù)
* @return
*/
private ArrayList<StudentBean> loadDataFromLocal(){
ArrayList<StudentBean> list = new ArrayList<>();
StudentBean student1 = new StudentBean();
student1.setName("jason");
student1.setSex("女");
student1.setEge("10");
student1.setPhone("010-1234567");
list.add(student1);
...
return list;
}
}
在StudentModelImp中遂鹊,模擬實現(xiàn)了加載數(shù)據(jù)的方法振乏,并在加載結(jié)束時,根據(jù)結(jié)果進行對應(yīng)成功失敗的回調(diào)秉扑。
有了數(shù)據(jù)獲取慧邮,我們來實現(xiàn)數(shù)據(jù)展示View模塊
View更簡單,拿到數(shù)據(jù)就展示舟陆,沒有數(shù)據(jù)误澳,就展示其他。
public interface IStudentView {
/**
* 顯示進度條
*/
void showLoading();
/**
* 展示失敗結(jié)果
*/
void showFailed();
/**
* 顯示獲取數(shù)據(jù)的結(jié)果,即數(shù)據(jù)
* @param list
*/
void showStudentList(List<StudentBean> list);
}
在View模塊中秦躯,實現(xiàn)加載進度條的方法和展示數(shù)據(jù)忆谓。
View模塊與Model模塊都有了,就差Presenter了踱承,看代碼之前倡缠,我們來分析下Presenter:
Presenter雖然隔離了Model與View哨免,但是也將二者關(guān)聯(lián)起來,也就是說要將數(shù)據(jù)獲取與數(shù)據(jù)展示關(guān)聯(lián)起來昙沦,所以琢唾,聰明的你,應(yīng)該想到了吧?
Presenter持有Model與View的引用盾饮,通過對象組合采桃,借助Model獲取數(shù)據(jù),借助View展示數(shù)據(jù)丘损,利用回調(diào)的方式普办,將這個過程串起來。
public class StudentPresenter {
private IStudentView mStudentView;
private IStudentModel mModel = new StudentModelImpl();
public StudentPresenter(IStudentView studentView) {
this.mStudentView = studentView;
}
public void fetch(){
//借助view show loading
mStudentView.showLoading();
// 借助model 獲取數(shù)據(jù)
mModel.fetchData(new IStudentModel.OnLoadListener() {
@Override
public void onLoadSuccess(List<StudentBean> list) {
// model獲取數(shù)據(jù)成功后,由view來展示
mStudentView.showStudentList(list);
}
@Override
public void onLoadFailed() {
mStudentView.showFailed();
}
});
}
}
還差最后一步徘钥,View的實現(xiàn)衔蹲,與MVC一樣,我們用Activity作為View吏饿,實現(xiàn)IStudentView接口踪危,
并借助Presenter調(diào)用數(shù)據(jù)獲取的方法。
Presenter本身不能獲取數(shù)據(jù)猪落,但是它持有View與Model的引用贞远,通過Model獲取數(shù)據(jù),再通過回調(diào)的方式笨忌,讓View展示數(shù)據(jù)
private void fetchData(){
// 調(diào)用presenter加載數(shù)據(jù)
new StudentPresenter(this).fetch();
}
/**
* 展示進度條
*/
@Override
public void showLoading() {
Toast.makeText(this,getString(R.string.loading),Toast.LENGTH_SHORT).show();
}
/**
* 展示數(shù)據(jù)
*/
@Override
public void showStudentList(List<StudentBean> list) {
myAdapter = new MyAdapter(this,list);
listview.setAdapter(myAdapter);
}
/**
* 展示加載失敗
*/
@Override
public void showFailed() {
Toast.makeText(this,getString(R.string.loadfailed),Toast.LENGTH_SHORT).show();
}
在Activity中蓝仲,完全看不到Model,但卻借助Presenter調(diào)用Model間接獲取了數(shù)據(jù)官疲,再通過View本身展示袱结。
Model與View二者完全隔離,卻被Presenter利用對象組合回調(diào)的方式相關(guān)聯(lián)途凫。
代碼的耦合度降低垢夹,在業(yè)務(wù)需求發(fā)生變化時,eg:需要從網(wǎng)絡(luò)獲取數(shù)據(jù)维费。 基于目前的代碼果元,我們不需要修改什么,只要再實現(xiàn)一個StudentImlInternet即可犀盟《梗可見實現(xiàn)了開閉原則Open Closed Principle
忘了什么是開閉原則?貼下概念:
定義: Softeware entities like classes,modules and functions should be open for extension but closed for modifications阅畴。
一個軟件實體如類倡怎、模塊和函數(shù)應(yīng)該對擴展開放,對修改關(guān)閉。
即通過擴展來實現(xiàn)變化监署,而不是通過修改已有代碼來實現(xiàn)變化颤专。
以上就是MVP模式。有沒有一種so easy! 的感覺钠乏。那么上面的寫法會不會有問題呢?
某一個Activity(View)在內(nèi)存不足時血公,已經(jīng)銷毀, 但是Model獲取數(shù)據(jù)(子線程耗時操作)
那么Presenter還持有View的引用缓熟。但Model完成后,Presenter去通知View更新摔笤,則出現(xiàn)內(nèi)存泄漏(生命周期不#####同步够滑,上下文丟失等問題)
舉例:開啟一個DownActivity 點擊頁面下載功能 那么model就開啟子線程獲取數(shù)據(jù)
然后點擊DownActivity的某按鈕去其他頁面 AnimationActivity 播放動畫或視頻,DownActivity是如果此時內(nèi)存不足>DownActivity被銷毀吕世,那么當model完成數(shù)據(jù)獲取彰触,由于P還持有V的引用,則Presenter就會通過DownActivity去更>新UI命辖,此時會發(fā)生內(nèi)存泄漏况毅。
解決方案:View被銷毀時,解除與Presenter的關(guān)聯(lián)尔艇。
So 我們需要兩個方法:attachView(view); detachView(); (像Fragment的生命周期的用法)
在View創(chuàng)建onCreate與銷毀onDestory的時候調(diào)用尔许。
但是如果每個實現(xiàn)View的Activity都在onCreate與onDestory的時候調(diào)用綁定與解綁的方法,這未免太傻了吧终娃。于是你想到了在BaseActivity中實現(xiàn)味廊,于是我們要把代碼優(yōu)化為擴展的方式: 配合泛型提取父類
/**
* Created by Shmily on 2017/6/2.
*/
public abstract class BaseActivity<V,T extends BasePresenter<V>> extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 創(chuàng)建Presenter 將在view中創(chuàng)建presenter提取到Base類
mPresenter = createPresenter();
// p與v的關(guān)聯(lián)
mPresenter.attachView((V)this);
}
public T mPresenter;
/**
* create presenter
* @return 模版方法,具體實現(xiàn)到子類
*/
public abstract T createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
// p與v解除關(guān)聯(lián) 解決內(nèi)存泄漏的問題
mPresenter.detachView();
}
}
解釋下泛型這里:
由于業(yè)務(wù)未來的擴展棠耕,Presenter實現(xiàn)可能有多種類型余佛,可以提取到父類BaseActivity中創(chuàng)建:采用模版
方法public T mPresenter; public abstract T createPresenter();
而T要在BaseActivity中聲明,So BaseActivity<T>
Presenter的父類 BasePresenter(持有View的引用)
使用泛型V 在BasePresenter聲明 BasePresenter<V>
public abstract class BasePresenter<T>
而BaseActivity持有BasePresenter的子類的引用(要在這里創(chuàng)建) So BaseActivity中的T是繼承
BasePresenter的一個子類窍荧。那么 abstract class BaseActivity<V辉巡,T extends BasePresenter<V>>
基于上文內(nèi)存泄漏的可能性,我們對Presenter的擴展時蕊退,讓presenter對view的持有為弱引用
public abstract class BasePresenter<T> {
//View接口類型弱引用
public WeakReference<T> mViewRefer;
/**
* bind view with presenter
* @param view
*/
protected void attachView(T view) {
//建立關(guān)聯(lián)
mViewRefer = new WeakReference<T>(view);
}
//可以通過此方法,判斷是否與View建立了關(guān)聯(lián)
protected boolean isViewAttached() {
return mViewRefer != null && mViewRefer.get() != null;
}
/**
* unbind view with presenter
*/
protected void detachView(){
if (mViewRefer != null) {
mViewRefer.clear();
mViewRefer = null;
}
}
/**
* base 類提供view的引用
* @return
*/
public T getView(){
return mViewRefer.get();
}
}
對于弱引用不了解的童鞋郊楣,一定要自己學(xué)習(xí)下。
最后在Activity調(diào)用
private void fetchData(){
mPresenter.requestData();
}
/**
* 子類實現(xiàn)父類的方法
* @return
*/
@Override
public StudentPresenterIml createPresenter() {
return new StudentPresenterIml();
}
在子類中創(chuàng)建自己想要的presenter對象咕痛,通過引用父類的mPresenter直接獲取數(shù)據(jù)痢甘。
MVP是安卓研發(fā)編碼時常用的代碼框架,本篇先是以最精簡的方式茉贡,搭建MVP框架塞栅。了解了它的核心與本質(zhì)后,再用擴展的方式,再將Presenter的實現(xiàn)提到抽象層放椰,具體實現(xiàn)延遲到子類作烟,并解決可能存在的內(nèi)存泄漏的問題。
代碼中有充分的注釋砾医,在此獻上代碼
[精簡版] https://github.com/Shmily701/SimpleMVP
[擴展版] https://github.com/Shmily701/ExtendedMVP
喜歡學(xué)習(xí)拿撩,樂于分享,不麻煩的話如蚜,給個?鼓勵下吧压恒!