博客 / 詳情

返回

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

博客主頁

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

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

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

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

所以説,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三個模塊

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

  • 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

缺點

1、 MVP接口過多
2、 每一個功能,相對於MVC要多寫好幾個文件
3、 如果某一個界面中需要請求多個服務器接口,這個界面文件中會實現很多的回調接口,導致代碼繁雜
4、 如果更改了數據源和請求中參數,會導致更多的代碼修改
5、 額外的代碼複雜度及學習成本

MVP相對於MVC優點

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

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

3、 方便進行單元測試

  • 一般單元測試都是用來測試某些新加的業務邏輯有沒有問題,如果採用傳統的代碼風格(習慣性上叫做MV模式,少了P),我們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時如果發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧重新寫吧……
  • MVP中,由於業務邏輯都在Presenter裏,我們完全可以寫一個PresenterTest的實現類繼承Presenter的接口,現在只要在Activity裏把Presenter的創建換成PresenterTest,就能進行單元測試了,測試完再換回來即可。萬一發現還得進行測試,那就再換成PresenterTest吧。

4、 避免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

5、 模塊職責劃分明顯,層次清晰,接口功能清晰
6、 Model層和View層分離,解耦;修改View而不影響Model
7、 功能複用度高,方便;一個Presenter可以複用於多個View,而不用更改Presenter的邏輯
8、 如果後台接口還未寫好,但已知返回數據類型的情況下,完全可以寫出此接口完整的功能

Android中MVP角色

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

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

2、Activity interface:需要View實現的接口,View通過View interface與Presenter進行交互,降低耦合,方便進行單元測試

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

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

4、Presenter:作為View與Model交互的中間紐帶,處理與用户交互的負責邏輯

3. MVP案例實現

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

1、ArticleModel就是Model層接口

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

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

}

2、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);
            }
        }
    }
}

3、 ArticleViewInterface就是主界面的邏輯接口,代表View接口角色,用於Presenter回調View的操作

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

4、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);
            }
        });
    }
}

5、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的生命週期來解決這個問題。

1、 首先建立一個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();
    }

}

2、 創建一個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();
}

如果我的文章對您有幫助,不妨點個贊鼓勵一下(^_^)

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.