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的帖子中講解如何處理,歡迎關注