Google 開(kāi)發(fā)Android MVP架構(gòu)Demo深入解析
1.什么是MVP?
Google在2016年推出了官方的Android MVP架構(gòu)Demo,本文主要分析一下官方的MVP Demo,并且借由自己的一些經(jīng)驗(yàn),提出一些學(xué)習(xí)過(guò)程中,遇到的問(wèn)題和自己的改進(jìn)、封裝措施。
MVP架構(gòu)已經(jīng)推出很多年了,現(xiàn)在已經(jīng)非常普及了,我在這里就不過(guò)多介紹,簡(jiǎn)單的說(shuō),它分為以下三個(gè)層次:
- Model:數(shù)據(jù)模型層,主要用來(lái)數(shù)據(jù)處理,獲取數(shù)據(jù);
- View:顯示界面元素,和用戶進(jìn)行界面交互;
- Presenter: 是Model和View溝通的橋梁,不關(guān)心具體的View顯示和Model的數(shù)據(jù)處理。View層中所有的邏輯操作都通過(guò)Presenter去通知Model層去完成,Model中獲取的數(shù)據(jù)通過(guò)Presenter層去通知View層顯示。 MVP架構(gòu)最大的好處,就是把傳統(tǒng)MVC架構(gòu)中View層和Control層的復(fù)雜關(guān)系完全解耦,View層只關(guān)心界面顯示相關(guān)的工作即可,Model層僅獲取數(shù)據(jù),處理邏輯運(yùn)算即可,各司其職,而不用關(guān)心其他工作。而且大家發(fā)現(xiàn)沒(méi)有,這樣設(shè)計(jì)的話,很好寫(xiě)單元測(cè)試代碼,針對(duì)于View、Presenter、Model層我們可以分別選用適合的單元測(cè)試框架,不用再像MVC/MVVM一樣,由于代碼混在或者分離在多處,到處無(wú)法“單一職責(zé)”的去完成每個(gè)類(lèi)的單元測(cè)試代碼編寫(xiě)。
在剛剛接觸android的時(shí)候,或者說(shuō),現(xiàn)在依然有很大一部分的APP開(kāi)發(fā)者,在開(kāi)發(fā)過(guò)程中,總是習(xí)慣在一個(gè)Activity、Fragment中幾乎完成了所有的功能。例如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)加載、業(yè)務(wù)邏輯處理、界面加載、界面動(dòng)畫(huà)。 后來(lái)漸漸的我們接觸到了MVC、MVP各種官方的框架,懂得了模塊的分離、解耦(MVC),懂得了通過(guò)依賴(lài)于抽象去分離各個(gè)模塊徹底解耦(MVP)。 但是官方的MVP框架的確已經(jīng)幫我們做了很多,但是依然不夠,接下來(lái),我們基于官方給的MVP框架,結(jié)合六大基本原則,去封裝更加適宜于項(xiàng)目的MVP框架。 整體設(shè)計(jì)模式Demo代碼
2.Google官方的MVP
官方Demo怎么去做的?
我們?yōu)榱朔奖闳シ治觯疫@里簡(jiǎn)化代碼,我們逐步分析,BaseView 和 BasePresenter,BaseView谷歌是這么寫(xiě)的(其實(shí)就是view的接口,展示view),以下樣例為了理解,我簡(jiǎn)化處理了部分代碼 BaseView.java
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface BaseView<T> { //View中,設(shè)置presenter對(duì)象,使View可以持有Presenter對(duì)象引用 void setPresenter(T presenter); }
BasePresenter
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface BasePresenter { }
TaskDetailContract
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface TaskDetailContract { interface View extends BaseView<Presenter> { //界面UI刷新方法 void updateTextView(String s); } interface Presenter extends BasePresenter { void loadDataFromModel(); } }
TaskGoogleActivity
package com.itbird.design.principle.mvp.google; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.itbird.design.R; public class TaskGoogleActivity extends AppCompatActivity implements TaskDetailContract.View { private static final String TAG = TaskGoogleActivity.class.getSimpleName(); private TaskDetailContract.Presenter mPresenter; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.textview); Log.e(TAG, TAG + " onCreate"); new TaskGooglePresenter(this); } @Override public void setPresenter(TaskDetailContract.Presenter presenter) { mPresenter = presenter; } @Override public void updateTextView(String s) { mTextView.setText(s); } }
TaskGooglePresenter
package com.itbird.design.principle.mvp.google; public class TaskGooglePresenter implements TaskDetailContract.Presenter { private static final String TAG = TaskGooglePresenter.class.getSimpleName(); TaskDetailContract.View mView; public TaskGooglePresenter(TaskDetailContract.View view) { mView = view; mView.setPresenter(this); } @Override public void loadDataFromModel() { //TODO :loaddata,此處可以用model、或者進(jìn)行業(yè)務(wù)操作 //調(diào)用界面方法,進(jìn)行數(shù)據(jù)刷新 mView.updateTextView("loaddata success!!!"); } }
小結(jié) 我們單從設(shè)計(jì)來(lái)說(shuō), Google MVP Demo的確向我們展示了MVP的優(yōu)點(diǎn):
- 1)業(yè)務(wù)、數(shù)據(jù)、視圖分離、解耦,從六大基本原則上來(lái)說(shuō),開(kāi)閉、單一職責(zé)、里氏代換、依賴(lài)倒置、接口隔離、最少知道,基本都做到了。
- 2)由于完全分離,所以我們很方便針對(duì)于每層去選取對(duì)應(yīng)的單元測(cè)試模式,例如針對(duì)于數(shù)據(jù)層可以使用(Junit+Mockito)、視圖層可以使用(AndroidJunitRunner+Espresso)、業(yè)務(wù)層可以使用(Junit+Mockito)
- 3)通過(guò)Contract 契約類(lèi),完全將視圖、業(yè)務(wù)相關(guān)的接口,封裝放在了一個(gè)地方,簡(jiǎn)單明了,針對(duì)于開(kāi)發(fā)者來(lái)說(shuō),不容易忘記和輔助養(yǎng)成習(xí)慣
但是,但是對(duì)于我們實(shí)際開(kāi)發(fā)使用來(lái)說(shuō),依然有以下幾點(diǎn)問(wèn)題:
- 1)setPresenter接口完全沒(méi)有必要存在,因?yàn)镻resenter對(duì)象一定是在View類(lèi)中new出來(lái)的,我既然都有它自己的對(duì)象了,干嘛還要在Presenter內(nèi)部,去調(diào)用mView.setPresenter(this);,再返回View中,再去保存一個(gè)引用,代碼看著很怪
- 2)我們知道MVP的精髓在與,P、V肯定要相互交互,他們互相要持有對(duì)方的引用,通過(guò)上面一點(diǎn),我們知道Presenter對(duì)象一定是在View類(lèi)中new出來(lái)的,所以View肯定有Presenter對(duì)象的引用,這個(gè)沒(méi)問(wèn)題了。但是Presenter要想擁有View的引用,只能通過(guò)Presenter構(gòu)造方法、或者Presenter內(nèi)部有一個(gè)setView方法,讓開(kāi)發(fā)者自己去調(diào)用setView或者實(shí)現(xiàn)帶參構(gòu)造方法,從設(shè)計(jì)角度來(lái)說(shuō),這明顯是一個(gè)坑,因?yàn)橐粋€(gè)框架的設(shè)計(jì),是為了更加方便開(kāi)發(fā),而不是讓開(kāi)發(fā)人員設(shè)置這個(gè)、設(shè)置那個(gè)、必須調(diào)用某個(gè)方法。既然是必須調(diào)用的方法,我們應(yīng)該通過(guò)框架去內(nèi)部消化掉,而不是給開(kāi)發(fā)者制造麻煩。
- 3)Presenter中擁有View的引用,如果activity、fragment銷(xiāo)毀,但是presenter依然在執(zhí)行某些任務(wù),這樣會(huì)導(dǎo)致activity、fragment無(wú)法GC回收,導(dǎo)致內(nèi)存泄露,甚至與崩潰,所以這也是一個(gè)框架必須解決的問(wèn)題。
3.V1.1 My MVP V1
基于上面提出的三點(diǎn),我們?nèi)?yōu)化Google的MVP框架。 我們首先將第一點(diǎn)和第二點(diǎn)通過(guò)抽象來(lái)解決一下。 IPresenter
package com.itbird.design.principle.mvp.v1; /** * 自定義MVP框架,BasePresenter * Created by itbird on 2022/2/25 */ public interface IPresenter { /** * 與view班定 * * @param view */ void onAttach(IView view); /** * 與view解綁 */ void onDetach(); /** * 是否與view已經(jīng)班定成功 * * @return */ boolean isViewAttached(); /** * 獲取view * @return */ IView getView(); }
IView
package com.itbird.design.principle.mvp.v1; /** * 自定義MVP框架,BaseView * Created by itbird on 2022/2/25 */ public interface IView { }
接下來(lái)是借助activity生命周期,對(duì)presenter的初始化進(jìn)行封裝 BaseActivity
package com.itbird.design.principle.mvp.v1; import android.app.Activity; import android.os.Bundle; import androidx.annotation.Nullable; /** * Created by itbird on 2022/3/29 */ public abstract class BaseActivity extends Activity implements IView { IPresenter mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = createPresenter(); if (mPresenter != null) { mPresenter.onAttach(this); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.onDetach(); mPresenter = null; } } abstract IPresenter createPresenter(); }
BasePresenter
package com.itbird.design.principle.mvp.v1; import java.lang.ref.WeakReference; /** * Created by itbird on 2022/3/29 */ public class BasePresenter<V extends IView> implements IPresenter { WeakReference<V> mIView; @Override public void onAttach(IView iView) { mIView = new WeakReference<>((V) iView); } @Override public void onDetach() { mIView = null; } @Override public V getView() { if (mIView != null) { mIView.get(); } return null; } @Override public boolean isViewAttached() { return mIView != null && mIView.get() != null; } }
4.V1.2 My MVP V2
看上圖類(lèi)圖,我們依然發(fā)現(xiàn)有一些不滿足的點(diǎn):
1)activity中,依然需要初始化mPresenter
@Override IPresenter createPresenter() { mTaskPresenter = new TaskMyPresenter(); return mTaskPresenter; }
是否可以做到在activity中,自己像presenter中調(diào)用view一樣,自己一句話getView就可以搞定
2)觀察類(lèi)圖,其實(shí)IView、IPresenter沒(méi)有必要存在,因?yàn)楫吘怪皇莃aseActivity、basePresenter的行為
所以改造如下: BasePresenter
package com.itbird.design.principle.mvp.v2; import java.lang.ref.WeakReference; /** * Created by itbird on 2022/3/29 */ public abstract class BasePresenter<V> { WeakReference<V> mIView; public void onAttach(V iView) { mIView = new WeakReference<>(iView); } public void onDetach() { mIView = null; } public V getView() { if (mIView != null) { mIView.get(); } return null; } public boolean isViewAttached() { return mIView != null && mIView.get() != null; } }
BaseActivity
package com.itbird.design.principle.mvp.v2; import android.app.Activity; import android.os.Bundle; import androidx.annotation.Nullable; /** * Created by itbird on 2022/3/29 */ public abstract class BaseActivity<V, T extends BasePresenter<V>> extends Activity { T mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = initPresenter(); if (mPresenter != null) { mPresenter.onAttach((V) this); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.onDetach(); mPresenter = null; } } public T getPresenter() { return mPresenter; } abstract T initPresenter(); }
此時(shí),契約類(lèi),不再需要依賴(lài)BasePresenter/BaseView相關(guān)接口,而且View中也可以自己獲取到presenter的引用了。
package com.itbird.design.principle.mvp.v2; /** * my Demo * Created by itbird on 2022/2/25 */ public interface TaskMyContract { interface View { //界面UI刷新方法 void updateTextView(String s); } interface Presenter { void loadDataFromModel(); } }
package com.itbird.design.principle.mvp.v2; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import com.itbird.design.R; public class TaskMyActivity extends BaseActivity<TaskMyContract.View, TaskMyPresenter> implements TaskMyContract.View { private static final String TAG = TaskMyActivity.class.getSimpleName(); TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.textview); Log.e(TAG, TAG + " onCreate"); mPresenter.loadDataFromModel(); } @Override TaskMyPresenter initPresenter() { return new TaskMyPresenter(); } @Override public void updateTextView(String s) { mTextView.setText(s); } }
此時(shí)的類(lèi)圖,是否更加明確,而且上面各個(gè)問(wèn)題都已經(jīng)得到了解決。
以上就是Google 開(kāi)發(fā)Android MVP架構(gòu)Demo深入解析的詳細(xì)內(nèi)容,更多關(guān)于Google Android MVP 架構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果
這篇文章主要介紹了Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果,本文通過(guò)實(shí)例代碼結(jié)合文字說(shuō)明的形式給大家介紹的非常詳細(xì),需要的朋友參考下吧2018-03-03Android評(píng)論功能的實(shí)現(xiàn)過(guò)程
這篇文章為大家詳細(xì)介紹了Android評(píng)論功能的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08DialogFragment運(yùn)行原理及使用方法詳解
這篇文章主要介紹了DialogFragment運(yùn)行原理及使用方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Android ListView與getView調(diào)用卡頓問(wèn)題解決辦法
這篇文章主要介紹了Android ListView與getView調(diào)用卡頓問(wèn)題解決辦法的相關(guān)資料,這里提供實(shí)例及解決辦法幫助大家解決這種問(wèn)題,需要的朋友可以參考下2017-08-08Android監(jiān)聽(tīng)手機(jī)電話狀態(tài)與發(fā)送郵件通知來(lái)電號(hào)碼的方法(基于PhoneStateListene實(shí)現(xiàn))
這篇文章主要介紹了Android監(jiān)聽(tīng)手機(jī)電話狀態(tài)與發(fā)送郵件通知來(lái)電號(hào)碼的方法,通過(guò)Android的PhoneStateListene實(shí)現(xiàn)該功能,需要的朋友可以參考下2016-01-01Android使用vitamio插件實(shí)現(xiàn)視頻播放器
這篇文章主要為大家詳細(xì)介紹了Android使用vitamio實(shí)現(xiàn)視頻播放器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04Android音視頻開(kāi)發(fā)之MediaCodec的使用教程
在Android開(kāi)發(fā)中提供了實(shí)現(xiàn)音視頻編解碼工具M(jìn)ediaCodec,針對(duì)對(duì)應(yīng)音視頻解碼類(lèi)型通過(guò)該類(lèi)創(chuàng)建對(duì)應(yīng)解碼器就能實(shí)現(xiàn)對(duì)數(shù)據(jù)進(jìn)行解碼操作。本文通過(guò)示例詳細(xì)講解了MediaCodec的使用,需要的可以參考一下2022-04-04