MVP框架的演化
MVP這種架構在Android界已經基本成為標配,MVP本身也有很多寫法和變種,當然,沒有最好的架構,只有最合適的架構,具體架構要怎麼寫,還是要看實際專案的需要。
我們在這裡簡單梳理一下MVP的一些演化版本,希望為具體的專案實現提供一點參考。
MVP本身的概念,就是把Model、View和Presenter相互解耦,大概可以這樣理解:
各自分工如下:
- View負責外部介面互動,不直接處理業務邏輯
- Presenter負責內部業務邏輯,不直接處理業務資料
- Model負責核心業務資料,與資料庫和網路進行資料互動
原始MVP
如果僅從分工的角度實現MVP,只需要發生兩次引用:
- 向View裡引用Presenter,(因為View都是Fragment或Activity,有特定的建構函式,所以一般採用set方式引用),以處理具體的內部業務邏輯,程式碼形如:
public class TasksFragment extends Fragment{
...
private Presenter mPresenter;
public void setPresenter(Presenter presenter) {
mPresenter = presenter;
}
...
}
- 向Presenter裡引用Model和View,其中View需要通過介面封裝一下再引用(一般在建構函式中引用),引用Model為業務邏輯提供核心的業務資料,引用View操作與介面相關的業務邏輯,程式碼形如:
public class Presenter{
...
private Repository mRepository;//model的實現這裡不再展開
private TasksFragment mView;
public Presenter(Repository tasksRepository, TasksFragment tasksView) {//presenter裡引用model
mRepository = tasksRepository;
mTView = tasksView;
mTasksView.setPresenter(this);//view裡引用presenter
}
...
}
這就是一個最原始的MVP的實現,這個版本有一個嚴重的問題,就是可維護性太差!
這版MVP雖然實現了各司其職,但其實質只不過是把程式碼拆到了不同的檔案裡,在實現中,M、V和P都引用了實體類的例項,形成了非常緊密的耦合,它其實只是實現了這樣的效果:
很顯然,難以複用,難以擴充套件,未來的維護簡直是個災難。
為了解耦合,很自然地,要使用介面去解耦合。
演化1-Google Architecture
Google在github上開源的architecture是個教科書般的MVP框架,它是這樣做的:
- V和P的介面化和注入
View和Presenter不再引用實體類,而是引用抽象介面,View裡引用的Presenter的介面,Presenter裡引用的也是View的介面,這樣的View和Presenter的程式碼形如:
public class TasksFragment extends Fragment implements IView{//實現介面以便注入到Presenter
...
private IPresenter mPresenter;
public void setPresenter(IPresenter presenter) {//view已有特定的建構函式,以set方式注入為宜
mPresenter = presenter;
}
...
}
和
public class Presenter implements IPresenter{//實現介面以便注入到view
...
private Repository mRepository;//model的實現這裡不再展開
private IView mView;
public Presenter(Repository tasksRepository, IView tasksView) {//presenter裡注入model和view
mRepository = tasksRepository;
mTView = tasksView;
mTasksView.setPresenter(this);//view裡注入presenter
}
...
}
如果願意的話,model也可以採用介面注入的形式(google architecture並沒有做model的介面注入,是為了確保引用的例項是一個全域性唯一的資料層單例,這樣容易避免汙染資料),這樣就能實現一個完好解耦的MVP:
- 集中管理V和P的介面
其實就是把V和P的介面放在同一個介面檔案下了,程式碼形如:
public interface ITasksContract {
interface IView{...}
interface IPresenter{...}
}
這樣做有兩個好處:
1.從一組業務來講,業務邏輯和介面邏輯在同一個檔案中定義,極具連貫性,極大地方便了閱讀、理解和維護(這也會引導你先從介面開始寫程式碼)
2.從多組業務來講(App一般有多組業務),便於管理好多個V和P的介面,這些介面天然按照業務分別寫在不同檔案裡,擴充套件和引用更加清晰,不易出錯
google architecture還做了一項改進,為V和P的介面定義了更基礎的介面,在基礎介面中統一定義了View注入Presenter的行為和Presenter開啟業務工作的行為,程式碼形如:
public interface BaseView<T> {//用泛型定義Presenter
void setPresenter(T presenter);//用set注入Presenter
}
和
public interface BasePresenter {
void start();//開啟業務工作
}
你自己實現的V和P的介面,只要繼承基礎介面,就能保證MVP基礎行為的一致性,這樣你的V和P就可以更加專注於業務
(除了教科書般的MVP,google architecture還提供了教科書般的資料Model層實現,不過這裡就不做展開了)
演化2-洩露的問題
上面的這種做法,有一個潛在的問題,就是記憶體洩露
我們知道,Presenter為了實現業務邏輯,一手持有資料Model,一手持有View,這裡面有一個隱含的bug:
資料Model在處理資料時,無論是處理本地資料還是網路資料,都是耗時操作,是不能在主執行緒執行的;而View,是必須在主執行緒執行的。這就容易產生一個問題,當View關閉退出時,Presenter可能還在非同步執行緒裡工作,而且Presenter還持有著View的例項——標準的記憶體洩露場景
要避免持有型的記憶體洩露,一個很有效的辦法就是把強引用的持有變成弱引用,就是說,在Presenter裡,要用WeakReference的方式去持有View,實現程式碼形如:
protected WeakReference<T> mViewRef; // view 的弱引用
public void attachView(T view){//持有View
mViewRef = new WeakReference<T>(view);
}
public void detachView(){
if (mViewRef != null){
mViewRef.clear();
mViewRef = null;
}
}
public T getView() {//獲取view的例項
return mViewRef.get();
}
這段程式碼其實是通用程式碼,根據聚焦業務的原則,應該抽象為基礎行為,而介面是不能實現任何方法的,所以,這段程式碼只能通過抽象類實現通用化,整個類的程式碼形如:
public abstract class MVPBasePresenter<T> {
protected WeakReference<T> mViewRef; // view 的弱引用
public void attachView(T view){//持有View
mViewRef = new WeakReference<T>(view);
}
public void detachView(){
if (mViewRef != null){
mViewRef.clear();
mViewRef = null;
}
}
public T getView() {//獲取view的例項
return mViewRef.get();
}
}
其中,attachView和detachView要在View的相應的生命週期中呼叫,這樣的話,我們又需要為View實現相關的抽象類,Fragment和Activity都需要
//需要兩個泛型型別,一個用來繼承Presenter的抽象類,而這個Presenter抽象類又需要一個View的泛型
public abstract class MVPBaseFragment<V,T extends MVPBasePresenter<V>> extends Fragment {
protected T mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
mPresenter.attachView((V)this);
}
@Override
public void onDestroy() {
super.onDestroy();
if(mPresenter!=null)
mPresenter.detachView();
}
protected abstract T createPresenter();
}
這樣一個業務Fragment在例項化時,程式碼形如:
//ICategoryContract.View是IView介面
//我們還定義了ICategoryContract.Presenter作為IPresenter介面
//CategoryPresenter繼承了MVPBasePresenter抽象了和IPresenter介面
public class MainFragment
extends MVPBaseFragment<ICategoryContract.View,CategoryPresenter>
implements ICategoryContract.View {...}
我們實際上把Presenter抽象類和IPresenter業務介面做了分離,把View抽象類和IView業務介面做了分離,基礎行為和業務邏輯互不干擾。
Activity的程式碼內容類似,這裡不再重複。
到了實際專案中,V和P分別繼承對應的抽象類,因為抽象類裡已經實現了弱引用和相關的管理,所以我們可以專注於業務邏輯的實現。
不過,這樣做帶來兩個問題:
- 如果在View的建構函式中自動處理Presenter的例項化,實際上會束縛了我們自己的寫作方式,比如我們的Presenter需要注入Model,就不能用構造方式注入;更嚴重的是,如果我們在Presenter初始化時需要設定某些UI控制元件,因為抽象類的oncreate需要先於業務類的oncreate去執行(業務類裡需要先執行super.oncreate),會遇到UI控制元件不能及時初始化的問題。
- Android的View其實是在不斷擴張的,以Activity為例,常見的就包括Activity、AppCompatActivity、FragmentActivity、RxAppCompatActivity等,如果使用這種抽象類的模式,每遇到一種Activity,就得去做一個對應的抽象類,可擴充套件性很差。
參照Google的做法,我們應該再多做一點介面的文章
演化3-View的剝離
我們回頭再看一遍Presenter抽象類
//Presenter抽象類
public abstract class MVPBasePresenter<T>{...}
其實在Presenter抽象類裡,用來處理View的泛型是與業務無關的,我們此前是做了一個View的抽象類來配合Presenter做弱引用處理,其實細想起來,這個View的角色沒必要使用抽象類,我們用一個IView基礎介面就可以滿足需要:
//基礎介面,不需要定義任何方法
public interface IMVPBaseView {}
我們的業務介面裡,IView業務介面繼承這個基礎介面:
public interface CategoryContract {
...
interface View implements IMVPBaseView{
...
}
}
我們的Fragement可以恢復Google教科書那樣的簡潔:
public class TasksFragment extends Fragment implements IView{//實現的介面中包含基礎行為和業務邏輯
...
}
最終,我們的MVP結構是這樣的:
Model:介面注入(更靈活)或引用一個全域性單例(更乾淨)
View:IMVPBaseView(基礎行為)-> IContract.View(業務邏輯)-> XXFragment(V的具體實現)
Presenter:(MVPBasePresenter(基礎行為) + IContract.Presenter)-> XXPresenter(P的具體實現)
當然,在這種方式下,Presenter的建立、初始化、銷燬等行為,也還給了最終的業務Fragment(或Activity)。
演化4-Dagger
MVP裡面其實有大量的依賴關係和注入行為,程式碼會顯得比較複雜,而Dagger是一個專門處理依賴注入的框架,可以用配置的方式實現複雜的依賴關係,所以我們完全可以用Dagger來實現MVP
在Dagger(Dagger2)裡,核心要素就是Module、Inject和Component,它們分別起這樣的作用:
- Module:提供依賴,其實就是把我們此前用set或構造引數注入的依賴例項,改用module配置出來,由Dagger負責傳給要注入的類,比如把IView和資料Model注入到Presenter裡,程式碼形如:
//為presenter提供IView引數例項
@Module
public class TasksPresenterModule {
private final TasksContract.View mView;
public TasksPresenterModule(TasksContract.View view) {
mView = view;
}
@Provides//提供引數的函式方法
TasksContract.View provideTasksContractView() {
return mView;
}
}
和
//為presenter提供資料Model引數例項(google官方示例裡又巢狀了幾層component)
@Singleton//要求dagger實現單例
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}
- Inject:指定依賴,就是說明某個屬性物件是需要用Module注入進來的,比如在Presenter裡說明某個modle物件和某個view物件是需要dagger注入進來的,程式碼形如:
//presenter類的引數改用Dagger注入
class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
/**
* Dagger strictly enforces that arguments not marked with {@code @Nullable} are not injected
* with {@code @Nullable} values.
*/
@Inject //引數是需要注入的
TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
mTasksRepository = tasksRepository;
mTasksView = tasksView;
}
...
}
同樣,在Activity裡也要用dagger注入persenter,程式碼形如:
public class TasksActivity extends AppCompatActivity {
@Inject TasksPresenter mTasksPresenter;//內部物件是需要注入的
...
}
- Component:組裝器,做兩件事:1-把做好的Module物件作為引數提供給要注入的類,比如把Modle物件和IView物件例項化,作為Presenter的引數,完成Presenter的例項化;2-把完成注入和例項化的類,注入到當前類裡,比如把完成例項化的Presenter注入到Activity裡,程式碼形如:
public class TasksActivity extends AppCompatActivity {
@Inject TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Create the presenter
DaggerTasksComponent.builder()
//component會做出presenter需要的兩個引數
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment))
.build()//構造出Presenter的例項
.inject(this);//把Presenter注入到當前Activity中
...
}
...
}
Dagger只是用註解來配置依賴關係,編譯時還是用工廠類和傳參等形式實現的依賴注入,例如,針對上述程式碼,Dagger的apt外掛會在編譯時把它轉成形如這樣的程式碼:
...
//生成的Component類裡,Module工廠類實現Module的例項化
this.provideTasksViewProvider = MainModule_ProvideTasksViewFactory.create(builder.mainModule);
...
//生成的Component類裡,Presenter工廠類實現Presenter的例項化
mainPresenterProvider = MainPresenter_Factory.create(provideTasksRepositoryProvider,provideTasksViewProvider);
...
//生成的Activity的Injector類裡,用構造引數實現依賴注入
this.mainPresenterProvider = mainPresenterProvider;
這樣用Dagger實現的MVP,最開始會有點彆扭,因為類之間的注入關係好像不像直接程式碼實現那樣熟悉,但習慣之後,你會發現這麼幾個好處:
1.基於JSR330的穩定和標準的依賴注入方法
2.依賴關係是配置化的,程式碼可讀性更強,也容易聚焦業務
3.可以通過註解實現全域性單例
演化5-Kotlin的引入
作為基礎通用框架,我們必須有一個Kotlin的版本,當然,不同的演化版本,會有不同的寫法,如果參照演化3的版本,對應的Kotlin版本形如:
//基礎IMVPView介面
interface IMvpView {
}
//基礎MvpPresenter抽象類
abstract class MvpPresenter<T:IMvpView> {
protected var mViewRef:WeakReIference<T>?=null
fun attachView(view:T){
mViewRef= WeakReference(view)
}
fun detachView(){
if(mViewRef!= null){
mViewRef!!.clear()
mViewRef=null
}
}
val view:T? get() = mViewRef!!.get()
}
//業務邏輯介面
interface ICatContract {
interface View<Presenter>{
fun refreshUI()
}
interface Presenter{
fun doInitPage()
}
}
//業務Presenter
class CatPresenter : MvpPresenter<CatActivity>(),ICatContract.Presenter {
override fun doInitPage() {
}
}
//業務Activity
class CatActivity : AppCompatActivity(),MvpView,ICatContract.View<CatPresenter> {
val TAG: String = "CatActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
override fun refreshUI() {
}
}
總結
MVP作為一個基礎型的結構,核心作用在於輔助我們實行良好的可讀性和可維護性,我們可以為一個Presenter提供多種View的實現(例如,一個業務可以同時有全屏Activity和對話方塊Activity兩種形式,分別提供給不同的業務環節,背後卻使用同一個Presenter),也可以為一個Presenter提供不同的資料Model(例如,在兩個根據後臺資料動態繪製介面的Activity例項中,業務邏輯一致,可以使用同一種Presenter,但資料內容不同,就可以使用兩個分別注入了不同Model的Presenter例項)
MVP裡有Passive View(Presenter通過View的介面操作View,並作為主要的業務驅動方)和Supervisor Controller(Presenter負責大量複雜的View邏輯)兩種衍生,
MVP還是一個開放性的結構,你可以根據自己的需要,去規避某些缺陷,或取得某些優勢,如何去演化一個適合自己需求的MVP框架,一方面滿足需求,一方面保持靈活,完全看自己的發揮了
關於MVVM
MVP的結構比較通透明瞭,不過其中的View總是要寫一些業務邏輯相關的程式碼,比如操縱Presenter,處理生命週期,例項化Model物件等,如果需要更進一步,把View的角色限定為純粹的UI,不做任何業務邏輯,不涉及任何資料,就需要用到MVVM模式了。
在MVVM模式裡,不再有Presenter,用ViewModel來處理業務邏輯,ViewModel不處理UI,而View只負責UI,與ViewModel建立資料繫結關係,通過databinding自動實現UI和Model之間的資料操作。
附錄;
附錄二;Android進階實戰技術視訊
獲取方式;
加Android進階群;701740775。即可前往免費領取。免費備註一下csdn
相關文章
- 基於AOP的MVP框架(一)GoMVP的使用MVP框架Go
- 基於AOP的MVP框架(三)GoMVP進階註解MVP框架Go
- 基於AOP的MVP框架(二)GoMVP進階註解MVP框架Go
- Java日誌框架演化歷史Java框架
- 一個基於Android的MVP框架DemoAndroidMVP框架
- 【Android】一鍵生成MVP程式碼-DevMvp快速開發框架AndroidMVPdev框架
- 完全元件化框架Atoms-mvp元件化框架MVP
- 淺談框架模式 MVC、MVP 和 MVVM框架模式MVCMVPMVVM
- Android框架模式之MVC與MVPAndroid框架模式MVCMVP
- MVP框架模板,方便快速專案開發MVP框架
- 擁有生命週期的Presenter的MVP框架,支援Multi PMVP框架
- 對MVP、Flux和RxAndroid框架的理解和選擇MVPUXAndroid框架
- mvp+rxjava2+retrofit2專案框架demoMVPRxJava框架
- 基於 MVP 的 Android 元件化開發框架實踐MVPAndroid元件化框架
- Riot.js:不足1KB的MVP客戶端框架JSMVP客戶端框架
- Android專案框架搭建:mvp+retrofit+rxjava+rxbusAndroid框架MVPRxJava
- 運用MVP框架寫一個完整的請求(RegisterActivity為例)MVP框架
- Golang的演化歷程Golang
- 《慾望的演化》總結
- mysql semi-sync的演化MySql
- 架構演化架構
- KCommon-使用Kotlin編寫,基於MVP的極速開發框架KotlinMVP框架
- UI架構設計的演化UI架構
- Go 語言的演化歷程Go
- Go語言的演化歷程Go
- mysql online ddl的演化MySql
- 程式語言的演化過程
- mvp模式MVP模式
- 微服務架構在阿里的演化微服務架構阿里
- Java應用架構的演化之路Java應用架構
- mysql多執行緒slave的演化MySql執行緒
- 從google todo-mvp示例再次學習MVPGoMVP
- 基於MVP模式,設計自己的RxJava+Retrofit2+Okhttp3+Rxlifecycle開發框架MVP模式RxJavaHTTP框架
- 架構演化-初識架構
- MVC,MVP,MVVMMVCMVPMVVM
- MVC——MVP——MVVMMVCMVPMVVM
- 淺談MVPMVP
- MVC、MVP、MVVMMVCMVPMVVM