MVP應用架構模式

最近經過《Android源碼設計模式解析與實戰》對MVP應用架構進行了瞭解,摘其重點記錄於此。數據庫

MVP簡介

MVP模式全稱Model View Presenter。能: 1.有效地下降View複雜性,避免業務邏輯被塞進View中; 2.解除View與Model的耦合,保證了系統的整潔性、靈活性。設計模式

理想化的MVP模式能夠實現同一份邏輯代碼搭配不一樣的顯示頁面,由於它們之間並不依賴於具體(class),而是依賴於抽象(interface)。 MVP並非一個標準化的模式,咱們可根據本身的需求和本身認爲對的方式去修正MVP的實現方式,它能夠隨着Presenter的複雜程度變化。 只要咱們是經過Presenter將View和Model解耦合、下降類型複雜度、各個模塊能夠獨立測試、獨立變化,這就是正確的方向。緩存

MVP模式的三個角色

  1. Presenter層:主要做爲View和Model的橋樑,它從Model層檢索數據後,返回給View層,使得View和Model之間沒有耦合,也將業務邏輯從View角色上抽離出來。
  2. View層:用戶界面,一般是指Activity、Fragment或者某個View控件,它含有一個Presenter成員變量。負責頁面渲染與事件分發(即調用Presenter方法處理事件)。
  3. Model層:Model角色主要是提供數據的存取功能。Presenter須要經過Model層存儲、獲取數據,Model就像一個數據倉庫。更直白地說,Model是封裝了數據庫DAO或者網絡獲取數據的角色,或者兩種數據獲取方式的集合。

MVP模式的應用

下面經過一個簡單的客戶端示例來直觀地體會MVP在開發中的運用。咱們的UI原型大體以下圖所示:主界面就是一個ListView和進度條,在加載數據時顯示進度條,加載完成以後隱藏。bash

UI原型

進入應用後首先會從服務器下載最新的20篇文章,而後將每一篇文章的簡介顯示到列表上。所以,咱們的業務邏輯大概有下列2個:服務器

  1. 向服務器請求數據,而且在數據存儲到數據庫中;
  2. 從數據庫中加載文章列表。

關鍵類以下:網絡

  1. 本地倉庫——LinkedList:負責提供(輸出)本地數據,同時負責將遠程數據緩存至本地,被倉庫層持有;
  2. 遠程倉庫接口——RemoteRepository:如Retrofit對應的interface文件,被倉庫層持有;
  3. 遠程倉庫實現——RemoteRepositoryImpl :負責Http請求的發起及響應的接收,被倉庫層實例化;
  4. 倉庫層接口——ArticleModel:定義了倉庫層須要實現的功能,亦即Presenter可調用的方法,被Presenter持有;
  5. 倉庫層實現——ArticleModelImpl :即Model層,內部持有本地倉庫和遠程倉庫的引用,負責爲Presenter提供數據,被Presenter實例化;
  6. Presenter層——ArticlePresenter :被View層實現持有,同時持有倉庫層接口(ArticleModel)及View層接口(ArticleViewInterface),經過調用倉庫層及View層的方法實現業務邏輯;
  7. View層接口——ArticleViewInterface:定義視圖層須要實現的功能,被Presenter持有;
  8. View層實現——MvpHomeActivity:實現View層須要具有的功能(方法),按需調用Presenter的業務邏輯;
/**
*數據加載監聽
*/
public interface DataListener<T> {
    //泛型是數據集合的類型
    public void onComplete(T articles);
}

/**
*遠程數據獲取接口(remote倉庫接口)
*/
public interface RemoteRepository{
    /**
    *遠程抓取數據
    */
    public void fetchArticles(DataListener<List<String>> listener);
}

/**
*遠程數據獲取實現(remote倉庫實現)
*/
public class RemoteRepositoryImpl implements RemoteRepository{
    @Override
    public void fetchArticles(DataListener<List<String>> listener) {
        List<String> articles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            articles.add("文章" + i);
        }
         listener.onComplete(articles);
    }
}

/**
*數據層抽象(Model層接口、倉庫層接口)
*/
public interface ArticleModel {
    /**
    *調用遠程接口獲取文章列表
    */
    public void loadArticlesFromRemote(DataListener<List<String>> listener);
    /**
    *緩存網絡數據至本地
    */
    public void saveArticles(List<String> articles);
    /**
    *從緩存中加載數據
    *@param listener  該參數由ModelImpl調用,由Presenter負責實現,用於解耦。
    */
    public void loadArticlesFromCache(DataListener<List<String>> listener);
}

/**
*數據層實現(Model層實現、倉庫層實現)
*/
public class ArticleModelImpl implements ArticleModel {
    /**
    *持有local倉庫的引用
    */
    List<String> mCacheArticles = new LinkedList<String>();

    /**
    *持有remote倉庫的引用
    */
    RemoteRepository remote= new RemoteRepositorympl();
	
    @Override
    public void loadArticlesFromRemote(DataListener<List<String>> listener) {
        remote.fetchArticles(listener);
     }
	
    @Override
    public void saveArticles(List<String> articles) {
        //爲了代碼簡單,咱們這裏模擬數據庫的存取操做,將數據緩存到內存中
        mCacheArticles.addAll(articles);
     }
	
    @Override
    public void loadArticlesFromCache(DataListener<List<String>> listener) {
        listener.onComplete(mCacheArticles);
    }
}

/**
*Presenter層
*/
public class ArticlePresenter {
    //Presenter層持有View層的接口
    ArticleViewInterface mArticleView;
	
    //Presenter層持有Model層的接口
    ArticleModel mArticleModel;
	
    //構造器,給持有的View層接口賦以具體的實現,進而能夠調用View層的方法
    //同時實例化成員變量
    public ArticlePresenter(ArticleViewInterface viewInterface) {
        mArticleView = viewInterface;
        mArticleModel = new ArticleModelImpl();
    }
	
    /**
    *業務邏輯1:獲取遠程數據
    */
    public void fetchArticles() {
        mArticleView.showLoading();
        mArticleModel .loadArticlesFromRemote(new DataListener<List<String>>() {
            @Override
            public void onComplete(List<String> result) {
                //數據加載完,調用View的showArticles函數將數據傳遞爲View顯示
                mArticleView.showAricles(result);
	
                mArticleView.hideLoading();
	
                //儲存到數據庫
                mArticleModel.saveArticles(result);
            }
        });
    }

    /**
    *業務邏輯2:獲取緩存數據:
    */
    public void loadArticlesFromDB(){
        mArticleModel.loadArticlesFromCache(new DataListener<List<String>>() {
            @Override
            public void onComplete(List<String> result) {
                mArticleView.showAricles(result);
            }
        });
    }
}

/**
*View層接口
*/
public interface ArticleViewInterface {
    /**
    *展現數據
    */
    public void showAricles(List<String> articles);
    /**
    *顯示進度條
    */
    public void showLoading();
    /**
    *隱藏進度條
    */
    public void hideLoading();
}


/**
*View層實現
*/
public class MvpHomeActivity extends Activity implements ArticleViewInterface {
	    
	    private ListView lv;
	    private ProgressBar pb;
           
	    private List<String> mArticles = new LinkedList<String>();
	    private BaseAdapter mAdapter;
            //View層實現持有Presenter的引用
	    private ArticlePresenter mPresenter;

	    @Override
	    protected void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.activity_mvp_home);
	        //初始化各類控件
	        initViews();
	        //構建ArticlePresenter,與Activity關聯
	        mPresenter = new ArticlePresenter(this);

		    boolean netAvailable = true;
	        if (netAvailable) {
	            mPresenter.fetchArticles();
	        }else{
	            mPresenter.loadArticlesFromDB();
	        }
	    }
	
	    private void initViews() {
	        pb = (ProgressBar) findViewById(R.id.pb);
	        lv = (ListView) findViewById(R.id.lv);
	        mAdapter = new CommonAdapter<String>(this,R.layout.item_layout,mArticles) {
	            @Override
	            protected void convert(ViewHolder viewHolder, String item, int position) {
	                viewHolder.setText(R.id.text1,item);
	            }
	        };
	        lv.setAdapter(mAdapter);
	    }
	
	    @Override
	    public void showAricles(List<String> articles) {
	        mArticles.addAll(articles);
	        mAdapter.notifyDataSetChanged();
	    }
	
	    @Override
	    public void showLoading() {
	        pb.setVisibility(View.VISIBLE);
	    }
	
	    @Override
	    public void hideLoading() {
	        pb.setVisibility(View.GONE);
	    }
	}
複製代碼

延伸

須要注意的是,上述代碼中對於ArticlePresenter並無進行接口抽象,而是使用了具體類,由於業務邏輯相對穩定,在此咱們直接使用具體類便可。固然,若是業務邏輯相對來講易於變化,使用Presenter接口來應對更是最好不過了。架構

同時,因爲Presenter常常性地須要執行一些耗時操做,如網絡請求。而Presenter持有了MvpHomeActivity的強引用,若是在請求結束以前Activity被銷燬了,那麼因爲網絡請求尚未返回,致使Presenter一直持有MvpHomeActivity對象,使得MvpHomeActivity對象沒法被回收,此時就發生了內存泄漏。ide

爲了解決上述問題,能夠經過弱引用和Activity,Fragment的生命週期來解決這個問題。首先創建一個Presenter抽象,它是一個泛型類,泛型類型爲View角色要實現的接口類型,代碼以下:函數

public abstract class BasePresenter<T> {
    protected Reference<T> mViewRef;//View接口類型的弱引用
	
    public void attachView(T view){
        mViewRef = new WeakReference<T>(view);//創建關聯
    }
	
    //獲取View
    protected T getView(){
        return mViewRef.get();
    }
	
    //判斷是否與View創建了關聯
    public boolean isViewAttached(){
        return mViewRef!=null&&mViewRef.get()!=null;
    }
	
    //解除關聯
    public void detachView(){
        if(mViewRef!=null){
            mViewRef.clear();
            mViewRef = null;
        }
    }
}
複製代碼

而後,建立一個MVPBaseActivity基類,經過這個基類的生命週期來控制它和Presenter的關係,代碼以下:測試

public abstract class MVPBaseActivity<V, P extends BasePresenter<V>> extends Activity {
    /**
    *Presenter對象
    */
    protected P mPresenter;
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //建立Presenter
        mPresenter = createPresenter();
        mPresenter.attachView((V) this);
    }
	
    @Override
    protected void onDestroy() {
        super.onDestroy();
         /**
        *弱引用防止內存溢出
        */
        mPresenter.detachView();
    }
	
    /**
    *子類負責提供具體的Presenter對象
    */
    protected abstract P createPresenter();
}
複製代碼

MVPBaseActivity含有兩個泛型參數,第一個是View層接口,第二個是Presenter的具體類型。經過泛型參數,使得一些通用的邏輯能夠被抽象到MVPBaseActivity類中。

它山之石

  1. 理解Java中的弱引用
相關文章
相關標籤/搜索