博客主頁
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嚴重耦合。
從圖中可以看出,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間的交互
缺點
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();
}
如果我的文章對您有幫助,不妨點個贊鼓勵一下(^_^)