前言
在學(xué)習(xí)了 MVC 架構(gòu)之后,發(fā)現(xiàn) Activity 和 Fragment 和 XML 界面的開(kāi)發(fā)就是典型的 MVC 架構(gòu)模式,在 Activity 中不僅要處理 UI 操作奠伪,還要處理請(qǐng)求數(shù)據(jù)的操作。
然而在我接觸到的開(kāi)發(fā)項(xiàng)目中合溺,看到一個(gè) Activity 中的代碼行數(shù)接近上千行是常態(tài)。在修改的過(guò)程中經(jīng)常要在類里面不斷的翻上翻下的,修改起來(lái)也非常費(fèi)勁芦缰。
這樣就導(dǎo)致了 MVC 架構(gòu)模式耦合度太高、職責(zé)不明確枫慷,不易于維護(hù)的原因让蕾,這次就來(lái)學(xué)習(xí) MVC 的演變出來(lái)的 MVP 架構(gòu)模式。
MVC 的工作原理: MVC 即 Model View Controller流礁,簡(jiǎn)單來(lái)說(shuō)就是通過(guò) Controller 的控制去操作 Model 層的數(shù)據(jù)涕俗,并且返回給 View 層展示。當(dāng)用戶觸發(fā)事件的時(shí)候神帅,View 層會(huì)發(fā)送指令到 Controller 層再姑,接著 Controller 去通知 Model 層更新數(shù)據(jù),Model 層更新完數(shù)據(jù)以后直接顯示在 View 層上找御,這就是 MVC 的工作原理元镀。
MVP是什么
MVP 的全稱是 Model-View-Presenter,MVP 是 MVC 的一種演進(jìn)版本霎桅,將 MVC 中的 Controller 改為了 Presenter栖疑,View 通過(guò)接口與 Presenter 進(jìn)行交互,有效降低 View(Activity / Fragment) 的復(fù)雜性滔驶,避免業(yè)務(wù)邏輯被塞進(jìn) View 中遇革,使得 View 變得臃腫。
另外揭糕,MVP 模式會(huì)解除 View 與 Model 的耦合萝快,同時(shí)又帶來(lái)了良好的可擴(kuò)展性、可測(cè)試性著角,保證了系統(tǒng)的整潔性揪漩、靈活性。雖然在簡(jiǎn)單的應(yīng)用中可能會(huì)因?yàn)楦鞣N接口變得復(fù)雜吏口,但在稍有規(guī)模的應(yīng)用中奄容,依然能保持結(jié)構(gòu)的整潔和靈活。
MVP的結(jié)構(gòu)
Model
主要是提供數(shù)據(jù)的存取功能产徊,Presenter 需要通過(guò) Model 層存儲(chǔ)和獲取數(shù)據(jù)昂勒,Model 就像一個(gè)數(shù)據(jù)倉(cāng)庫(kù)。Model 是管理數(shù)據(jù)庫(kù)和網(wǎng)絡(luò)獲取數(shù)據(jù)的角色舟铜。View
一般是指 Activity 和 Fragmen t等叁怪,它含有一個(gè) Presenter 成員變量。通常 View 需要實(shí)現(xiàn)一個(gè)邏輯接口深滚,將 View 上的操作轉(zhuǎn)交給 Presenter 進(jìn)行實(shí)現(xiàn)奕谭,最后 Presenter 調(diào)用 View 的邏輯接口將結(jié)果返回給 View 元素涣觉。Presenter
作為 View 與 Model 交互的中間紐帶,處理與用戶交互的負(fù)責(zé)邏輯血柳。它從 Model 層檢索數(shù)據(jù)后官册,返回給 View 層,使得 View 和 Model 之間沒(méi)有耦合难捌,也將業(yè)務(wù)邏輯從View角色上抽離出來(lái)膝宁。
在通常開(kāi)發(fā)中,
View
是指 Activity / Fragment根吁,不過(guò)员淫,Presenter 和 Activity 通過(guò)定義一個(gè) view 接口進(jìn)行關(guān)聯(lián),而 Presenter 和 Model 是通過(guò)Callback
接口進(jìn)行關(guān)聯(lián)击敌。
- View接口:顯示提示框介返、數(shù)據(jù)更新;
- Callback接口:請(qǐng)求數(shù)據(jù)時(shí)反饋狀態(tài)(成功沃斤、失敗和異常等等)圣蝎;
MVP的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
分離了視圖邏輯和業(yè)務(wù)邏輯,降低了耦合衡瓶。
Activity 只處理生命周期的任務(wù)徘公,代碼變得更加簡(jiǎn)潔。
視圖邏輯和業(yè)務(wù)邏輯分別抽象到了 View 和 Presenter 的接口中去哮针,提高代碼的可閱讀性关面。
Presenter 被抽象成接口,可以有多種具體的實(shí)現(xiàn)十厢,所以方便進(jìn)行單元測(cè)試等太。
把業(yè)務(wù)邏輯抽到 Presenter 中去,避免后臺(tái)線程引用著 Activity 導(dǎo)致 Activity 的資源無(wú)法被系統(tǒng)回收從而引起內(nèi)存泄露寿烟。
Presenter 代碼可復(fù)用澈驼,一個(gè) Presenter 可以用于多個(gè) View辛燥,而不需要更改 Presenter 的邏輯筛武。
缺點(diǎn)
Presenter 中除了應(yīng)用邏輯以外,還有大量的 View->Model挎塌,Model->View 的手動(dòng)同步邏輯徘六,造成 Presenter 比較笨重,維護(hù)起來(lái)會(huì)比較困難榴都。
由于對(duì)視圖的渲染放在了 Presenter 中待锈,所以視圖和 Presenter 的交互會(huì)過(guò)于頻繁。
如果 Presenter 過(guò)多地渲染了視圖嘴高,往往會(huì)使得它與特定的視圖的聯(lián)系過(guò)于緊密竿音。一旦視圖需要變更和屎,那么 Presenter 也需要變更了。
額外的代碼復(fù)雜度及學(xué)習(xí)成本春瞬。
MVP 和 MVC 的區(qū)別
MVP 是基于 MVC 模式上演變過(guò)來(lái)柴信,與 MVC 有一定的相似性,Controller 和 Presenter 負(fù)責(zé)邏輯的處理宽气,Model 提供數(shù)據(jù)随常,View 負(fù)責(zé)顯示。
在 MVC 中萄涯,當(dāng) Model 被 Controller 更新后绪氛,Model 會(huì)直接通知 View 并更新顯示。而在 MVP 中涝影,Model 與 View 不存在直接關(guān)系枣察,這兩者之間間隔著的是 Presenter 層,其負(fù)責(zé)調(diào)控 View 與 Model 之間的間接交互袄琳,這也是 MVP 和 MVC 兩者之間最大的區(qū)別询件。
此外 Presenter 與 View、Model 的交互使用接口定義交互操作可以降低耦合唆樊、簡(jiǎn)化代碼宛琅。
MVP 與 Activity、Fragment 的生命周期
- 問(wèn)題原因:由于 Model 在進(jìn)行異步操作逗旁,例如請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)嘿辟,Presenter 持有 Activity 的強(qiáng)引用,如果在請(qǐng)求結(jié)束之前使得 Activity 被銷毀了片效,那么由于網(wǎng)絡(luò)請(qǐng)求還沒(méi)有返回红伦,導(dǎo)致 Presenter 持有 Activity 對(duì)象,使得 Activity 無(wú)法被回收淀衣,此時(shí)就會(huì)發(fā)生
內(nèi)存泄露
昙读。(也許應(yīng)用中出現(xiàn)一次兩次內(nèi)存泄漏不會(huì)造成多大的影響,但應(yīng)用在長(zhǎng)時(shí)間使用后膨桥,若這些占據(jù)系統(tǒng)的大量?jī)?nèi)存的 Activity 得不到 GC 回收的話蛮浑,最終會(huì)導(dǎo)致OOM
的出現(xiàn),就會(huì)直接Crash
應(yīng)用只嚣。)- 解決辦法:通過(guò)
弱引用
和 Activity沮稚、Fragment 的生命周期來(lái)綁定/解綁 View
解決這個(gè)問(wèn)題,建立BasePresenter
册舞,是一個(gè)泛型類 蕴掏,泛型類型為 View 角色要實(shí)現(xiàn)的接口類型 。- 好處:Presenter 不需要在構(gòu)造函數(shù)中傳入 View 對(duì)象,而是在 View 中自由地通過(guò)Presenter 的 attachView 方法和 detachView 方法綁定和解綁 View 對(duì)象盛杰,除了
attachView
和detachView
挽荡,我們還可以另外聲明 onResume 和 onStop 方法。
Model層的單獨(dú)優(yōu)化
前面講了 View 和 Presenter 兩個(gè)層次即供,而 Model 層比較特殊徐伐,相對(duì)比較獨(dú)立的存在,幫上層拿數(shù)據(jù)募狂,這是因?yàn)?MVP 模式的理念就是讓業(yè)務(wù)邏輯相互獨(dú)立办素。但如果每個(gè)網(wǎng)絡(luò)請(qǐng)求也獨(dú)立成單個(gè) Model 的話,代碼操作起來(lái)也會(huì)非常麻煩祸穷,比如:
- 無(wú)法對(duì)所有 Model 統(tǒng)一管理性穿;
- 每個(gè) Model 對(duì)外提供的獲取數(shù)據(jù)方法不一樣,上層請(qǐng)求數(shù)據(jù)沒(méi)有規(guī)范雷滚;
- 代碼冗余需曾,重復(fù)性代碼多;
- 對(duì)已存在的 Model 進(jìn)行修改繁瑣祈远;
那么就需要對(duì) Model 進(jìn)行封裝優(yōu)化呆万,使得 Model 層變成一個(gè)龐大且獨(dú)立單一的模塊,請(qǐng)求方式規(guī)范化车份,管理直觀化:
- 數(shù)據(jù)請(qǐng)求能夠單獨(dú)編寫和測(cè)試谋减,無(wú)需配合上層界面測(cè)試;
- 統(tǒng)一以
DataModel
類作為數(shù)據(jù)請(qǐng)求層的入口扫沼,通過(guò)反射機(jī)制獲取對(duì)應(yīng)的 Model出爹; - 無(wú)縫切換不同的數(shù)據(jù)源(網(wǎng)絡(luò)請(qǐng)求庫(kù)、緩存缎除、數(shù)據(jù)庫(kù))严就;
MVP結(jié)構(gòu)圖
示例代碼
-
IBaseView:View接口中定義Activity的UI邏輯
public interface IBaseView { void showLoading(); void hideLoading(); void showToast(String msg); void showErr(); Context getContext(); }
-
BaseActivity:主要是負(fù)責(zé)實(shí)現(xiàn) BaseView 中通用的UI邏輯方法,如此這些通用的方法就不用每個(gè)Activity都要去實(shí)現(xiàn)一遍了器罐。
public abstract class BaseActivity extends AppCompatActivity implements IBaseView { // 加載條 private ProgressDialog mProgressDialog; // 獲取Presenter實(shí)例梢为,子類實(shí)現(xiàn) public abstract BasePresenter getPresenter(); // 初始化Presenter的實(shí)例,子類實(shí)現(xiàn) public abstract void initPresenter(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化Presenter initPresenter(); if (getPresenter()!=null){ getPresenter().attachView(this); } // 初始化進(jìn)度條 mProgressDialog = new ProgressDialog(this); mProgressDialog.setCancelable(false); } @Override public void showLoading() { if (!mProgressDialog.isShowing()) { mProgressDialog.show(); } } @Override public void hideLoading() { if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } @Override public void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } @Override public void showErr() { showToast("err...."); } @Override public Context getContext() { return BaseActivity.this; } @Override protected void onDestroy() { super.onDestroy(); if (getPresenter() != null){ getPresenter().detachView(); } } }
-
MainActivity:繼承了BaseActivity抽象類轰坊,實(shí)現(xiàn)了getPresenter和initPresenter完成P層綁定铸董。實(shí)現(xiàn)IMvpView接口中的showData達(dá)到UI更新操作。
public class MainActivity extends BaseActivity implements IMvpView { TextView mTextView; MvpPresenter mvpPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main3); mTextView = findViewById(R.id.text); } @Override public BasePresenter getPresenter() { return mvpPresenter; } @Override public void initPresenter() { mvpPresenter = new MvpPresenter(); //初始化Presenter // mvpPresenter.attachView(this);// attachView放在抽象父類中 } // 按鈕1 public void getData(View view) { mvpPresenter.getData("normal"); } // 按鈕2 public void getDataForFailure(View view) { mvpPresenter.getData("failure"); } // 按鈕3 public void getDataForError(View view) { mvpPresenter.getData("error"); } @Override public void showData(String data) { mTextView.setText(data); } }
-
IMvpView:IMvpView是Activity與Presenter層的中間層衰倦,它的作用是根據(jù)具體業(yè)務(wù)的需要袒炉,為Presenter提供調(diào)用Activity中具體UI邏輯操作的方法旁理。
/** * View接口是Activity與Presenter層的中間層樊零,它的作用是根據(jù)具體業(yè)務(wù)的需要, * 為Presenter提供調(diào)用Activity中具體UI邏輯操作的方法。 */ public interface IMvpView extends IBaseView { /** * 當(dāng)數(shù)據(jù)請(qǐng)求成功后驻襟,調(diào)用此接口顯示數(shù)據(jù) * @param data 數(shù)據(jù)源 */ void showData(String data); }
-
BasePresenter:處理View的生命周期夺艰;
public class BasePresenter<V extends IBaseView> { // 綁定的view private V mView; // 綁定view,一般在初始化中調(diào)用該方法 public void attachView(V mvpView) { this.mView = mvpView; } // 斷開(kāi)view沉衣,一般在onDestroy中調(diào)用 public void detachView() { this.mView = null; } // 是否與View建立連接郁副,每次請(qǐng)求業(yè)務(wù)之前都要判斷 public boolean isViewAttached() { return mView != null; } // 獲取當(dāng)前連接的view public V getmView() { return mView; } }
-
MvpPresenter:該類是具體的邏輯業(yè)務(wù)處理類,負(fù)責(zé)請(qǐng)求數(shù)據(jù)豌习,并對(duì)數(shù)據(jù)請(qǐng)求的反饋進(jìn)行處理存谎。
public class MvpPresenter extends BasePresenter<IMvpView> { /** * 獲取網(wǎng)絡(luò)數(shù)據(jù) * @param userId */ public void getData(String userId) { if (!isViewAttached()) { return; } //顯示進(jìn)度條 getmView().showLoading(); DataModel.request1(UserDataModel.class) .params(userId).execute(new MvpCallback() { @Override public void onSuccess(Object data) { //調(diào)用view接口顯示數(shù)據(jù) if (isViewAttached()) { getmView().showData((String) data); } } @Override public void onFailure(String msg) { if (isViewAttached()) { getmView().showToast(msg); } } @Override public void onError() { if (isViewAttached()) { getmView().showErr(); } } @Override public void onComplete() { if (isViewAttached()) { getmView().hideLoading(); } } }); } }
-
MvpCallback
/** * Callback 接口是Model層給Presenter層反饋請(qǐng)求信息的傳遞載體, * 所以需要在Callback中定義數(shù)據(jù)請(qǐng)求的各種反饋狀態(tài) * 除了請(qǐng)求成功的回調(diào)方法肥隆,其他的像請(qǐng)求失敗既荚,請(qǐng)求出錯(cuò)這些方法我們做的事幾乎是一樣的。 * 后期可以構(gòu)建一個(gè)通用的BaseCallBack去處理請(qǐng)求的異常情況 */ public interface MvpCallback<T> { /** * 數(shù)據(jù)請(qǐng)求成功 * @param data 請(qǐng)求到的數(shù)據(jù) */ void onSuccess(T data); /** * 網(wǎng)絡(luò)返回?cái)?shù)據(jù)失敗栋艳,請(qǐng)求成功 * @param msg 無(wú)法正常返回?cái)?shù)據(jù)的原因 */ void onFailure(String msg); /** * 請(qǐng)求數(shù)據(jù)失敗恰聘、無(wú)法聯(lián)網(wǎng)、缺少權(quán)限吸占、內(nèi)存泄露等等原因 */ void onError(); /** * 無(wú)論執(zhí)行上面那個(gè)方法晴叨,最后都會(huì)執(zhí)行此方法,可以在這里設(shè)置隱藏加載框 */ void onComplete(); }
-
DataModel:通過(guò)反射機(jī)制獲取對(duì)應(yīng)的model
public class DataModel { public static <T extends BaseModel> T request1(Class<T> cls) { // 聲明一個(gè)空的BaseModel T model = null; try { //利用反射機(jī)制獲得對(duì)應(yīng)Model對(duì)象的引用 model = (T) cls.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return model; } }
- BaseModel:定義了對(duì)外的請(qǐng)求數(shù)據(jù)規(guī)則矾屯,包括設(shè)置參數(shù)的方法和設(shè)置Callback的方法兼蕊,還可以定義一些通用的數(shù)據(jù)請(qǐng)求方法,比如說(shuō)網(wǎng)絡(luò)請(qǐng)求的Get和Post方法件蚕。
public abstract class BaseModel<T> { //數(shù)據(jù)請(qǐng)求參數(shù) protected String[] mParams; /** * 設(shè)置數(shù)據(jù)請(qǐng)求參數(shù) * @param args 參數(shù)數(shù)組 */ public BaseModel params(String... args) { mParams = args; return this; } // 添加Callback并執(zhí)行數(shù)據(jù)請(qǐng)求 // 具體的數(shù)據(jù)請(qǐng)求由子類實(shí)現(xiàn) public abstract void execute(MvpCallback<T> callback); // 執(zhí)行Get網(wǎng)絡(luò)請(qǐng)求遍略,此類看需求由自己選擇寫與不寫 protected void requestGetAPI(String url, MvpCallback<T> callback) { //這里寫具體的網(wǎng)絡(luò)請(qǐng)求 } // 執(zhí)行Post網(wǎng)絡(luò)請(qǐng)求,此類看需求由自己選擇寫與不寫 protected void requestPostAPI(String url, Map params, MvpCallback<T> callback) { //這里寫具體的網(wǎng)絡(luò)請(qǐng)求 } }
-
getNetData:實(shí)現(xiàn)具體的Model請(qǐng)求時(shí)必須要重寫B(tài)aseModel的抽象方法execute
public class UserDataModel extends BaseModel<String> { @Override public void execute(final MvpCallback<String> callback) { new Handler().postDelayed(new Runnable() { @Override public void run() { // mParams 是從父類得到的請(qǐng)求參數(shù) switch (mParams[0]){ case "normal": callback.onSuccess("根據(jù)參數(shù)"+mParams[0]+"的請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)成功"); break; case "failure": callback.onFailure("請(qǐng)求失斨枳:參數(shù)有誤"); break; case "error": callback.onError(); break; } callback.onComplete(); } },2000); } }