MVC架構設計與三層模型 & MVP思想精髓與解耦

博客主頁android

1. MVC架構設計與經典的三層模型

MVC:Model-View-Controller,經典模式,很容易理。數據庫

  • Model:業務層和模型層,實體模型和業務相關的代碼
  • View:視圖層,android中對應於layout佈局文件
  • Controller:控制層,android中的UI操做,對應於Activity

可是在Android實際開發中,這個View層對應於佈局文件,其實能作的事情特別少,實際上關於該佈局文件中的數據綁定操做,事件處理的代碼都在Activity中;咱們每每也會把具體的業務相關代碼放到了Activity中;再加上Activity自己又承擔着控制層的責任,這樣致使Contrlller層愈來愈臃腫。segmentfault

因此說,MVC真實存在的是MC(V),Controller與Model根本就分不開,View和Model嚴重耦合。服務器

Model-View-Controller

從圖中能夠看出,Controller是做爲媒介,處於Model和View之間。Model和View之間有緊密的聯繫,耦合性偏強。網絡

MVC主要缺點有兩個:架構

  • Model層與View層之間耦合度強,致使難以維護
  • Controller層會變得複雜,代碼臃腫

優勢:異步

  • Controller層和View層都在Activity中操做,數據操做方便
  • 模塊職責劃分明確,主要劃分層M-V-C三個模塊

舉一個簡單的例子:獲取網絡圖片並展現在界面上ide

  • Model層:獲取網絡圖片
public interface ImageModel {
    // 從網絡加載圖片
    void loadImage(String imagePath, OnImageListener listener);

    interface OnImageListener {
        void shopImage(Bitmap bitmap);
    }
}

public class ImageModelImpl implements ImageModel {

    @Override
    public void loadImage(String imagePath, OnImageListener listener) {
        if (listener != null && !TextUtils.isEmpty(imagePath)) {
            // 模擬網絡獲取圖片
            if (!TextUtils.isEmpty(imagePath)) {
                listener.shopImage(BitmapFactory.decodeFile(imagePath));
            } else {
                listener.shopImage(null);
            }

        }
    }
}

  • Controller層
public class MainActivity extends AppCompatActivity implements ImageModel.OnImageListener {

    private static final String TAG = "===>";
    // View層
    private ImageView mShowImageView;
    // Model層
    private ImageModel mImageModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mShowImageView = findViewById(R.id.showImageView);

        findViewById(R.id.loadImageBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 加載圖片
                mImageModel.loadImage("/mnt/sdcard/Pictures/2.png", MainActivity.this);
            }
        });

        mImageModel = new ImageModelImpl();
    }

    @Override
    public void shopImage(Bitmap bitmap) {
        if (bitmap != null) {
            // 展現圖片
            mShowImageView.setImageBitmap(bitmap);
        }
    }
}

2. MVP思想精髓與巧妙解耦View和Model

MVP:Model-View-Presenter,MVC的一個演變模式,將Controller換成了Presenter,主要爲了解決上述第一個缺點,將View和Model解耦,不過第二個缺點依然沒有解決。函數

  • View:對應於Activity,負責View的繪製以及用戶交互
  • Model:業務邏輯和實體模型
  • Persenter:負責完成View於Model間的交互

Model-View-Presenter

缺點

一、 MVP接口過多
二、 每個功能,相對於MVC要多寫好幾個文件
三、 若是某一個界面中須要請求多個服務器接口,這個界面文件中會實現不少的回調接口,致使代碼繁雜
四、 若是更改了數據源和請求中參數,會致使更多的代碼修改
五、 額外的代碼複雜度及學習成本佈局

MVP相對於MVC優勢

一、 減小了Activity的職責,簡化了Activity中的代碼,將複雜的邏輯代碼提取到了Presenter中進行處理。與之對應的好處就是:耦合度更低

二、 Activity代碼變得更加簡潔:使用MVP以後,Activity就能瘦身許多了,基本上只有findView、setListener以及init的代碼。其餘的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,並且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,之後要調整業務。刪減功能也就變得簡單許多。

三、 方便進行單元測試

  • 通常單元測試都是用來測試某些新加的業務邏輯有沒有問題,若是採用傳統的代碼風格(習慣性上叫作MV模式,少了P),咱們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時若是發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧從新寫吧……
  • MVP中,因爲業務邏輯都在Presenter裏,咱們徹底能夠寫一個PresenterTest的實現類繼承Presenter的接口,如今只要在Activity裏把Presenter的建立換成PresenterTest,就能進行單元測試了,測試完再換回來便可。萬一發現還得進行測試,那就再換成PresenterTest吧。

四、 避免Activity的內存泄露

  • 發生OOM異常的緣由:
    現內存泄露形成APP的內存不夠用,而形成內存泄露的兩大緣由之一就是Activity泄露(Activity Leak)(另外一個緣由是Bitmap泄露(Bitmap Leak);

    Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶老是喜歡隨便寫一大堆對象,而後幻想着虛擬機能幫他們處理好內存的回收工做。但是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用着的對象由於還可能會被調用,因此不能回收

    Activity是有生命週期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於後臺的Activity的資源以免OOM。

  • MVC產生內存泄露異常分析
    採用傳統的MVC模式,一大堆異步任務和對UI的操做都放在Activity裏面,好比你可能從網絡下載一張圖片,在下載成功的回調裏把圖片加載到 Activity 的 ImageView 裏面,因此異步任務保留着對Activity的引用。這樣一來,即便Activity已經被切換到後臺(onDestroy已經執行),這些異步任務仍然保留着對Activity實例的引用,因此係統就沒法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象每每是在堆(Java Heap)裏佔最多內存的,因此係統會優先回收Activity對象,若是有Activity Leak,APP很容易由於內存不夠而OOM。
  • MVC模式如何避免內存泄露
    只要在當前的Activity的onDestroy裏,分離異步任務對Activity的引用,就能避免 Activity Leak

五、 模塊職責劃分明顯,層次清晰,接口功能清晰
六、 Model層和View層分離,解耦;修改View而不影響Model
七、 功能複用度高,方便;一個Presenter能夠複用於多個View,而不用更改Presenter的邏輯
八、 若是後臺接口還未寫好,但已知返回數據類型的狀況下,徹底能夠寫出此接口完整的功能

Android中MVP角色

一、View:負責繪製UI元素,與用戶進行交互(在Android中體現爲Activity)

  • 提供UI交互
  • 在presenter的控制下修改UI
  • 將業務事件交由presenter處理
  • View層不存儲數據,不與Model層交互
  • 在Android中View層通常是Activity、Fragment、View(控件)、ViewGroup(佈局)等

二、Activity interface:須要View實現的接口,View經過View interface與Presenter進行交互,下降耦合,方便進行單元測試

三、Model:負責存儲、檢索、操縱數據(有時也實現一個Model interface用來下降耦合)

  • 從網絡、數據庫、文件、第三方等數據源讀取數據
  • 對外部的數據類型進行解析轉換爲APP內部數據交由上層處理
  • 對數據的臨時存儲、管理、協調上層數據請求

四、Presenter:做爲View與Model交互的中間紐帶,處理與用戶交互的負責邏輯

3. MVP案例實現

從服務器端下拉最新的20篇文章,而後將每一篇文章的簡介顯示到列表上,當用戶點擊某項數據進入到另外一個頁面,該頁面加載這篇文章的詳細內容。

一、ArticleModel就是Model層接口

public interface ArticleModel {
    // 加載文章數據
    void loadArticles(OnArticleListener listener);

    interface OnArticleListener {
        void onLoadComplete(List<Article> data);
    }

}

二、ArticleModelImpl實現了ArticleModel接口,用於加載網絡數據,爲了代碼簡單,這裏睡眠2秒模擬從網絡獲取數據

public class ArticleModelImpl implements ArticleModel {
    @Override
    public void loadArticles(OnArticleListener listener) {
        new LoadArticleTask(listener).execute();
    }

    private static class LoadArticleTask extends AsyncTask<Void, Void, List<Article>> {

        private final OnArticleListener listener;

        LoadArticleTask(OnArticleListener listener) {
            this.listener = listener;
        }

        @Override
        protected List<Article> doInBackground(Void... params) {
            // 模擬網絡請求
            SystemClock.sleep(2000);

            final List<Article> data = new ArrayList<>();
            for (int i = 0; i < 40; i++) {
                data.add(new Article("title-" + i, "message:" + i));
            }
            return data;
        }

        @Override
        protected void onPostExecute(List<Article> data) {
            if (listener != null) {
                listener.onLoadComplete(data);
            }
        }
    }
}

三、 ArticleViewInterface就是主界面的邏輯接口,表明View接口角色,用於Presenter回調View的操做

public interface ArticleViewInterface {
    void showProgressBar(); // 顯示進度條
    void hideProgressBar(); // 隱藏進度條
    void showArticles(List<Article> data); // 展現數據
}

四、Presenter層,做爲View和Model的中間人。

public interface ArticlePresenter {
    void loadArticles();
}

public class ArticlePresenterImpl implements ArticlePresenter {
    // ArticleView的接口,表明了View角色
    private ArticleViewInterface mView;
    // 文章數據的Model,也就是Model角色 
    private ArticleModel mArticleModel;
    public ArticlePresenterImpl(ArticleViewInterface view) {
        this.mView = view;
        mArticleModel = new ArticleModelImpl();
    }

    // 獲取文章,也就是咱們的業務邏輯
    @Override
    public void loadArticles() {
        mView.showProgressBar();
        mArticleModel.loadArticles(new ArticleModel.OnArticleListener() {
            @Override
            public void onLoadComplete(List<Article> data) {
                // 數據加載完後,通知View層更新UI
                mView.hideProgressBar();
                mView.showArticles(data);
            }
        });
    }
}

五、ArticleActivity須要實現ArticleViewInterface接口,而且須要創建與Presenter的聯繫,ArticleActivity的業務邏輯都交給Presenter進行處理,處理結果經過ArticleViewInterface接口回調給ArticleActivity類

public class ArticleActivity extends AppCompatActivity implements ArticleViewInterface {

    private ProgressBar mProgressBar;
    private ArrayAdapter<Article> mAdapter;
    private ArticlePresenter mArticlePresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        // 構建ArticlePresenter,與ArticleActivity創建關聯
        mArticlePresenter = new ArticlePresenterImpl(this);
    }

    private void initView() {
        mProgressBar = findViewById(R.id.load_progress_bar);

        ListView listView = findViewById(R.id.list_view);

        mAdapter = new ArrayAdapter<>(this, R.layout.item_list, R.id.item_title);

        listView.setAdapter(mAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 請求文章數據
        mArticlePresenter.loadArticles();
    }

    @Override
    public void showArticles(List<Article> data) {
        mAdapter.setNotifyOnChange(true); 
        mAdapter.addAll(data); // 更新UI
    }

   // ....
}

4. MVP與Activity、Fragment的生命週期

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

如何解決這樣的問題呢?
咱們能夠經過弱引用和Activity、Fragment的生命週期來解決這個問題。

一、 首先創建一個Presenter抽象,BasePresenter,它是一個泛型類,泛型類型爲View角色要實現的接口類型

public abstract class BasePresenter<V> {
    private Reference<V> mViewRef; // View接口類型的弱引用

    public void attachView(V view) {
        mViewRef = new WeakReference<>(view); // 創建關聯
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    protected V getView() {
        return mViewRef.get();
    }

}

二、 建立一個MVPBaseActivity基類,經過這個基類的生命週期函數來控制它與Presenter的關係。MVPBaseActivity有兩個泛型參數,第一個是View的接口類型,第二個是Presneter的具體類型

public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity{

    protected T mPresenter; // Presenter對象
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = createPresenter(); // 建立Presenter

        mPresenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    protected abstract T createPresenter();
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索