編輯:關於Android編程
在日常開發APP 的過程中,隨著業務的擴展,規模的變化。我們的代碼規模也會逐漸變得龐大,每一個類裡的代碼也會逐漸增多。尤其是Activity和Fragment ,由於Context 的存在,基本上所有對視圖的操作我們只能在Activity和Fragment中完成;即便是對某些邏輯進行封裝,Activity和Fragment 依舊會顯得過於臃腫。因此,我們需要換一種思路去寫代碼,這個時候MVP模式就應用而生了!那麼MVP 怎麼用呢,下面就來說一說。
假設你現在如要實現下圖中的功能:
這個需求很簡單,就是點擊按鈕,下載一張圖片,顯示下載進度;下載完成後,在ImageView中顯示這張圖片。
下面我們就分別用傳統的方式(也就是所謂的MVC)和MVP 模式分別取實現這個功能。然後分析一下MVP 到底好在哪裡。
public class MVCActivity extends AppCompatActivity { private Context mContext; private ImageView mImageView; private MyHandler mMyHandler; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvc); mContext = this; init(); } private void init() { //view init mImageView = (ImageView) findViewById(R.id.image); mMyHandler = new MyHandler(); progressDialog = new ProgressDialog(mContext); progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { progressDialog.dismiss(); } }); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setTitle("下載文件"); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); //click-event findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog.show(); HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler)); } }); findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog.show(); HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler)); } }); } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 300: int percent = msg.arg1; if (percent < 100) { progressDialog.setProgress(percent); } else { progressDialog.dismiss(); Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView); } break; case 404: progressDialog.dismiss(); Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show(); break; default: break; } } } }
用mvc的方式,一個Activity就能搞定。代碼邏輯很簡單,點擊按鈕後顯示之前初始化好ProgressDialog,然後開始下載任務(這裡HttpUtil 內部簡單封裝了OKHttp 的異步GET請求,實現下載文件保存到本地的功能,實現細節在此不做深入探討,有興趣的同學可以查看源碼),然後將請求結果通過Handler返回,在handleMessage中根據返回數據的信息做出不同的UI 處理;下載成功時在ImageView中顯示圖片,下載失敗時Toast提示。
可以發現,在這種情況之前,Activity的任務十分繁重,既要負責下載任務的具體實施,還要根據下載進行再次的邏輯判斷,才能去更新UI。這裡只是一個簡單的任務,你可能覺得無所謂,但是實際開發中,一個Activity中有許多的交互事件,這個時候Activity的代碼就顯得特別的龐大;一旦需求變更或出現bug,那簡直就是噩夢一場。
因此,我們希望Activity可以變成下面這樣
他負責發起處理和用戶交互的內容,但又不負責具體的實現; 需要顯示什麼,不顯示什麼,什麼東西顯示多少,有個東西可以直接告訴他, Activity不再做復雜的邏輯處理;具體到上面的demo裡就是,Activity負責發起下載任務,但是不負責具體實現;什麼時候顯示ProgressDialog,顯示多少?什麼時候提示錯誤信息,這一切都希望有個東西能直接告訴Activity,而不再是在Activity裡再做判斷。怎樣才能做到呢?那就得靠MVP 了。
MVP 模式所做的事情很簡單,就是將業務邏輯和視圖邏輯抽象到接口中。
怎麼理解呢,我們就根據此次要實現的下載功能,用代碼說話。
Model 接口定義所有需要實現的業務邏輯,在我們的下載任務中,業務邏輯只有一個,就是下載;因此Model 接口可以這麼定義 :
public interface IDownloadModel { /** * 下載操作 * @param url */ void download(String url); }
View 接口定義所有需要實現的視圖邏輯,在我們的下載任務中,視圖邏輯包括
- 顯示ProgressDialog;
- 顯示Dialog具體進度;
- 顯示具體的View(設置圖片);
- 顯示錯誤信息(Toast提示)
因此View接口可以這麼定義:
public interface IDownloadView { /** * 顯示進度條 * @param show */ void showProgressBar(boolean show); /** * 設置進度條進度 * @param progress */ void setProcessProgress(int progress); /** * 根據數據設置view * @param result */ void setView(String result); /** * 設置請求失敗時的view */ void showFailToast(); }
Presenter 接口作為連接Model和View的中間橋梁,需要將二者連接起來,因此他需要完成以下工作:
- 執行下載任務
- 下載成功返回下載結果
- 下載過程返回下載進度
- 下載失敗回調
因此,Presenter 就可以這麼定義:
public interface IDowndownPresenter { /** * 下載 * @param url */ void download(String url); /** * 下載成功 * @param result */ void downloadSuccess(String result); /** * 當前下載進度 * @param progress */ void downloadProgress(int progress); /** * 下載失敗 */ void downloadFail(); }
上面實現了,各個接口的定義,下面來看看他們具體的實現:
public class DownloadModel implements IDownloadModel { private IDowndownPresenter mIDowndownPresenter; private MyHandler mMyHandler = new MyHandler(); public DownloadModel(IDowndownPresenter IDowndownPresenter) { mIDowndownPresenter = IDowndownPresenter; } @Override public void download(String url) { HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler)); } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 300: int percent = msg.arg1; if (percent < 100) { mIDowndownPresenter.downloadProgress(percent); } else { mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH); } break; case 404: mIDowndownPresenter.downloadFail(); break; default: break; } } } }
在MVP模式中,Model的工作就是完成具體的業務操作,網絡請求,持久化數據增刪改查等任務。同時Model中又不會包含任何View。
這裡Model的具體實現很簡單,將Http任務的結果返回到Handler當中,而在Handler中的實現又是由Presenter完成。
那麼Presenter接口又是怎樣實現的呢?趕緊來看看
public class DownloadPresenter implements IDowndownPresenter { private IDownloadView mIDownloadView; private IDownloadModel mIDownloadModel; public DownloadPresenter(IDownloadView IDownloadView) { mIDownloadView = IDownloadView; mIDownloadModel = new DownloadModel(this); } @Override public void download(String url) { mIDownloadView.showProgressBar(true); mIDownloadModel.download(url); } @Override public void downloadSuccess(String result) { mIDownloadView.showProgressBar(false); mIDownloadView.setView(result); } @Override public void downloadProgress(int progress) { mIDownloadView.setProcessProgress(progress); } @Override public void downloadFail() { mIDownloadView.showProgressBar(false); mIDownloadView.showFailToast(); } }
可以看到,我們在DownloadPresenter的構造方法中,同時實例化了Model和View,這樣Presenter中就同時包含了兩者;
這樣;**在Presenter具體實現中,業務相關的操作由Model去完成(例如download),視圖相關的操作由View去完成
(如setView等)**。Presenter 作為橋梁的作用就這樣體現出來了,巧妙的將View和Model的具體實現連接了起來。
最後再看一下View接口的具體實現,也就是Activity的實現:
public class MVPActivity extends AppCompatActivity implements IDownloadView { private Context mContext; private ImageView mImageView; private ProgressDialog progressDialog; private DownloadPresenter mDownloadPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = this; setContentView(R.layout.activity_mvp); init(); } private void init() { mDownloadPresenter = new DownloadPresenter(this); //view init mImageView = (ImageView) findViewById(R.id.image); findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadPresenter.download(Constants.DOWNLOAD_URL); } }); findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL); } }); progressDialog = new ProgressDialog(mContext); progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { progressDialog.dismiss(); } }); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setTitle("下載文件"); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); } @Override public void showProgressBar(boolean show) { if (show) { progressDialog.show(); } else { progressDialog.dismiss(); } } @Override public void setProcessProgress(int progress) { progressDialog.setProgress(progress); } @Override public void setView(String result) { Glide.with(mContext).load(result).into(mImageView); } @Override public void showFailToast() { Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show(); } }
在點下按鈕執行開始下載任務的時候,View(Activity)中沒有具體的實現,只是調用了Presenter中的download方法,而Presenter中的download又會去調用Model的download方法,Model又會在根據具體邏輯(在這裡就是Http請求)的狀態去調用Presenter中的方法,例如我們在handleMessage方法中,調用mIDowndownPresenter.downloadProgress(percent)時,就會去調用Presenter的具體實現
@Override public void downloadProgress(int progress) { mIDownloadView.setProcessProgress(progress); }
而他的內部實現又是操作具體的View,也就是我們在Activity中初始化Presenter中傳遞的this,也就是當前Activity(View),這樣最終回到了Activity中的
@Override public void setProcessProgress(int progress) { progressDialog.setProgress(progress); }
我們為progressDialog 設置進度。
至此,我們就通過MVP 的模式實現了我們之前所設想的Activity
Button的click方法負責發起下載任務,但又不負責具體實現,而是由Presenter轉接給Model去實現 Activity 什麼時候顯示ProgressDialog,什麼時候顯示Toast直接由Presenter告訴他,他只做一個View想做的事情 Activity裡沒有任何邏輯處理,所有的邏輯判斷都在Model中完成了。這就是MVP !!!
通過上面的兩種實現方案,相信每個人都已經理解了MVC和MVP的區別;下面就其各自的優缺點再做一下
總結;當然,這裡的優缺點只是相對而言。
上面兩張圖分別是MVC和MVP架構圖。相信許多和我一樣嘗試去學習和了解MVP架構的同學對這兩圖(或類似的圖)並不陌生。
結構更加清晰*
我們回過頭再去看MVCActivity 的實現,暫且將我們對Http請求的封裝歸結為Model(M),那麼剩下的就只有Activity了,而這個Activity即實現視圖邏輯,又需要實現部分業務邏輯,也就是說他既是Controller(C)又是View(V)。V和C的劃分完全不清晰;因此,傳統的代碼結構只能勉強稱為MV 或者是MC,如果算上xml 的布局文件,才能牽強的稱為MVC 結構。
而MVP 就不同了,Model,View,Presenter各司其職,互相搭配,實現了解耦,完全解放了Activity(或者是Fragment)。這就是MVP 的優勢,代碼結構更加清晰。可以這樣說,同一個模塊的實現,甚至允許幾個人分工完成;假設有一個非常復雜的Activity,如果使用MVP 的模式開發;那麼這個時候,定義好MVP的接口之後,就可以有人專門去做Model,另一個人專門去做View;再由一個人寫Presenter的代碼,當然這需要極強的代碼規范和協作能力;但這在傳統的MVC模式中根本是無法想象的,所有的東西都在一個類裡,兩個人一起改,有了沖突怎麼玩/(ㄒoㄒ)/~~。
需求變更,不再是噩夢
假設現在有新的需求,產品經理認為下載失敗後只有一個Toast提示太單調了(而且用戶有可能錯過了這Toast的顯示,而誤以為APP失去了響應),因此,現在希望在下載失敗後彈出一個Dialog,可以重試下載任務。是想,如果代碼使用傳統的MVC 結構,恰巧這個代碼不是你寫的,或者說就是你寫的,但是你已經忘記了具體的邏輯;那麼為了實現這個需求你又得去重新捋一遍邏輯,到某個類的xxx行進行修改;但是如果使用MVP就不同了View接口已經定義好了showFailToast就是用來顯示錯誤提示的;因此即便代碼不是你寫的,你都可以很快的找到,應該去哪裡改;而省去很多時間。
更容易寫單元測試
這個就不展開說了,總之寫過單元測試的人應該都有這樣的體會。
MVP這麼好,也不是沒有缺點。
如圖中所示,使用MVP 架構之後,多出了許多類;這是必然的;每一個View(Activity或Fragment)都至少需要各自的Model、Presenter和View接口,在加上他們各自的實現,也就是說每一個頁面都會有6個java文件(算上Fragment或Activity,因為他就是View的實現),這樣一個稍有點規模的APP,類就會變得異常的多,而每一個類的加載又會消耗資源;因此,相較於MVC,這算是MVP最大的缺點了吧。
當然,對於這個問題我們可以通過泛型參數、抽象父類的方式,將一些公用的Model及Presenter抽象出來。這應該就是使用MVP架構的精髓了。
個人感覺,使用MVP 架構是利大於弊的;隨著項目規模的增加,代碼邏輯的清晰才是最重要的事情。況且Google官方也出推出了一系列關於MVP的使用demo。
因此,這也是官方提倡大家使用的。凡事,有利必有弊;類數目的增長是無法避免的事情,因此如何使用泛型和抽象優化MVP 的結構就變成了我們用好
MVP的關鍵了。
當然,我們不能為了MVP而去MVP,如果項目結構不是很龐大,業務不是很復雜;那麼傳統的MVC 架構足以,而且也方便!
一:簡介 systrace 是 Android4.1 引入的一套用於做性能分析的工具。 基於 Linux 內核的 ftrace 機制(用於跟蹤 Linux 內核的函數調用
1080P全高清屏幕雖然可為手機帶來更細膩的視界,但同時也會增加系統負載,拖慢游戲速度(和同配置720P手機相比)。那麼,如何才能提高1080P手機的游戲速
先給大家展示下效果圖:不知道大家對效果圖感覺怎麼樣,個人覺還不錯,感興趣的朋友可以參考下實現代碼哦。public class ToggleButton extends V
一 背景概述:ScrollView裡嵌套ListView,一直是Android開發者(反正至少是我們組)最討厭的設計之一,完美打破ListView(RecyclerVie