Android中的MVP模式使用
在講MVP 之前,我們先來簡單說下什么是MVC, 即Model(模型)、View(視圖)、Control(控制器),相信大家對于MVC模式早已耳熟能詳。原理性的東西這里不再多說。MVC在AndroidApp里面就有很好的體現。因為對于Android本身來說,界面部分的開發一般會用XML文件進行界面的描述開發。也就是MVC中的View層。而對于Model部分則大多是對應本地數據文件的讀取或從網絡獲取數據。最后的Control控制器則有Activity來擔當。MVC就說到這里,相信大多數Android猿們也是基于這種模式進行開發的。接下來,我們就來談一談今天的主角MVP模式在Android中又是如何實現的。
何為MVP呢?即Model,VIew,Presenter(交互中間人相當于Control) 。但這里的Presenter隔離了Model和View之間的通信,使得Model和View之間能夠完全解耦。在傳統的MVC中,Activity擔當了Control的角色,同時還要負責Dialog,Toast,PopupWindow的彈出,這就往往讓Activity的責任變得繁重。一個Activity可能動不動就是幾千行代碼。而在MVP中View的角色不僅僅只是XML,而變成了Activity,Activity只負責View的工作。結合上面說的MVP能使Model和View之間能完全解耦,也就是說,在Activity中不會有任何關于Model層的操作,這就符合面向對象設計原則中的單一職責,讓各個模塊只負責自己的部分。更易于維護,代碼也會更加簡介。下面我會以一個小的項目來演示MVP在Android中如何使用。我在今日頭天的web頁拿到了一個新聞的URL,獲取Json數據,解析顯示
Bean
package himan.mvp.bean;
import java.io.Serializable;
public class NewsInfo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String datetime;
private int id;
private String title;
private String imageUrl;
private String abstractInfo;
public String getDatetime() {
return datetime;
}
public void setDatetime(String datetime) {
this.datetime = datetime;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getAbstractInfo() {
return abstractInfo;
}
public void setAbstractInfo(String abstractInfo) {
this.abstractInfo = abstractInfo;
}
public NewsInfo(String datetime, int id, String title, String imageUrl,
String abstractInfo) {
super();
this.datetime = datetime;
this.id = id;
this.title = title;
this.imageUrl = imageUrl;
this.abstractInfo = abstractInfo;
}
public NewsInfo() {
}
}Model層的接口:還記得前兩篇帖子講的面向對象的六大設計模式精髓:面向接口編程,依賴于抽象
package himan.mvp.model;
import himan.mvp.bean.NewsInfo;
import java.util.List;
public interface INewsModel {
/**
* 加載數據
*
* @param dataListener
*/
void loadNews(IOnDataListener<List<NewsInfo>> dataListener);
/**
* 緩存數據
*
* @param listNews
*/
void saveNews(List<NewsInfo> listNews);
}Model實現類
package himan.mvp.modelimpl;
import java.util.List;
import himan.mvp.bean.NewsInfo;
import himan.mvp.model.INewsModel;
import himan.mvp.model.IOnDataListener;
import himan.mvp.net.NewsNetHelper;
import himan.mvp.net.volley.UIDataListener;
public class NewsModelImpl implements INewsModel {
@Override
public void loadNews(final IOnDataListener<List<NewsInfo>> dataListener) { // 回調接口
NewsNetHelper netHelper = new NewsNetHelper();
netHelper.setDataListener(new UIDataListener<List<NewsInfo>>() {
@Override
public void onDataChanged(List<NewsInfo> data) {
if (data == null) {
dataListener.error();
} else {
dataListener.completeLoad(data);
}
}
});
netHelper
.doHttpGet("http://toutiao.com/api/article/recent/?source=2&count=20&category=__all__&max_behot_time=1449467901.41&utm_source=toutiao&offset=0&_=1449468004256");
}
@Override
public void saveNews(List<NewsInfo> listNews) {
// 緩存數據到本地
}
}View層接口:依賴于抽象編程
package himan.mvp.view;
import himan.mvp.bean.NewsInfo;
import java.util.List;
public interface INewsView {
/**
* 顯示新聞列表
*
* @param listNews
*/
public void showNewsList(List<NewsInfo> listNews);
/**
* 顯示正在加載中進度條
*/
public void showLoading();
/**
* 關閉正在加載中進度條
*/
public void hideLoading();
/**
* 顯示數據加載失敗
*/
public void showError();
}MVP關鍵之處:中間人Presenter
package himan.mvp.presenter;
import java.util.List;
import himan.mvp.bean.NewsInfo;
import himan.mvp.model.INewsModel;
import himan.mvp.model.IOnDataListener;
import himan.mvp.modelimpl.NewsModelImpl;
import himan.mvp.view.INewsView;
public class NewsPresenter {
// 同時持有Model層和View層的引用
private INewsView mNewsView;
private INewsModel mNewsModel = new NewsModelImpl();
public NewsPresenter(INewsView newsView) {
this.mNewsView = newsView;
}
public void showNews() {
mNewsView.showLoading();
mNewsModel.loadNews(new IOnDataListener<List<NewsInfo>>() {
@Override
public void error() {
// 加載失敗
mNewsView.hideLoading();
mNewsView.showError();
}
@Override
public void completeLoad(List<NewsInfo> t) {
mNewsView.hideLoading();
mNewsView.showNewsList(t);
}
});
}
}Activity實現了View接口
package himan.mvp;
import himan.mvp.adapter.NewsAdapter;
import himan.mvp.bean.NewsInfo;
import himan.mvp.net.volley.VolleyQueueController;
import himan.mvp.presenter.NewsPresenter;
import himan.mvp.utils.LoadingWindow;
import himan.mvp.view.INewsView;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
/**
* 測試 加載的數據 是從今日頭條官網的直接push下來的
*
* @author Mr.Himan
*
*/
public class MainActivity extends Activity implements INewsView {
private ListView mlVNews;
private NewsAdapter mNewsAdapter;
private List<NewsInfo> mListNews;
private NewsPresenter mNewsPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化Volley框架
VolleyQueueController.init(this);
// ImageLoader 默認配置參數
ImageLoaderConfiguration configuration = ImageLoaderConfiguration
.createDefault(this);
// 初始化ImageLoader
ImageLoader.getInstance().init(configuration);
initView();
mNewsPresenter = new NewsPresenter(this);
mNewsPresenter.showNews();
}
private void initView() {
mlVNews = (ListView) findViewById(R.id.news_lv_news_list);
mListNews = new ArrayList<NewsInfo>();
mNewsAdapter = new NewsAdapter(this, mListNews, R.layout.lv_item_news);
mlVNews.setAdapter(mNewsAdapter);
}
@Override
public void showNewsList(List<NewsInfo> listNews) {
mListNews.addAll(listNews);
mNewsAdapter.notifyDataSetChanged();
}
@Override
public void showLoading() {
Toast.makeText(this, "正在加載數據...", Toast.LENGTH_LONG).show();
}
@Override
public void hideLoading() {
Toast.makeText(this, "數據加載成功", Toast.LENGTH_SHORT).show();
}
@Override
public void showError() {
}
}這就是MVP實現一個新聞展示的全部代碼(貌似頭條的接口關掉了 - -),Activity實現了View接口,而Presenter中保存有View和Model層的引用,使得Presenter能通過Model拿到數據,通過View的引用控制視圖的顯示。流程大概是這樣,Presenter先通過Model的引用拿到數據,然后通過View的引用進行數據的顯示,視圖更新。Activity中的代碼也非常簡介。通過上面代碼我們能夠看出MVP的優點還是很多的,首先使得Model和View層完全解耦,我們知道項目中改動最多的一般都是View層,這就讓程序易于維護,而且能夠高度復用Presenter,而且也易于后期的拓展。缺點也顯而易見,就是爆炸式增長的類和接口。復用的時候也可能造成接口的冗余。最大的問題是Presenter持有著View的強應用,在請求網絡數據等耗時操作的時候,Activity可能被銷毀,可能會導致View無法回收,而造成內存問題。關于這個問題,我會在下一篇MVP的帖子中講解如何處理,歡迎關注