王者荣耀 MVP- 不不不, 一个小例子彻底搞懂 Android 的 -MVP 到底是什么
MVP 的封装
很显然,MVP 的实现套路是大致相同的,如果在一个应用中,存在大量的 Activity 和 Fragment,并且都使用 MVP 的架构,那么难免会有很多重复工作,所以封装就很有必要性了。
在说 MVP 的封装之前,需要强调一点,MVP 更多的是一种思想,而不是一种模式,每个开发者都可以按照自己的思路来实现具有个性化的 MVP,所以不同的人写出的 MVP 可能会有一些差别,笔者在此仅提供一种实现思路,供读者参考。
首先 Model、View 和 Presenter 都可能会有一些通用性的操作,所以可以分别定义三个对应的底层接口。
interface?BaseModel?{}
interface?BaseView?{void?showError(String?msg);}
public?abstract?class?BasePresenter<V?extends?BaseView,?M?extends?BaseModel>?{protected?V?view;protected?M?model;
public?BasePresenter()?{model?=?createModel();}
void?attachView(V?view)?{this.view?=?view;}
void?detachView()?{this.view?=?null;}
abstract?M?createModel();}
这里的 View 层添加了一个通用的方法,显示错误信息,写在接口层,可以在实现处按照需求来显示,比如有的地方可能会是弹出一个 Toast,或者有的地方需要将错误信息显示在 TextView 中,Model 层也可以根据需要添加通用的方法,重点来看一下 Presenter 层。
这里的 BasePresenter 采用了泛型,为什么要这么做呢?主要是因为 Presenter 必须同时持有 View 和 Model 的引用,但是在底层接口中无法确定他们的类型,只能确定他们是 BaseView 和 BaseModel 的子类,所以采用泛型的方式来引用,就巧妙的解决了这个问题,在 BasePresenter 的子类中只要定义好 View 和 Model 的类型,就会自动引用他们的对象了。Presenter 中的通用的方法主要就是 attachView 和 detachView,分别用于创建 View 对象和把 View 的对象置位空,前面已经说过,置空是为了防止内存泄漏,Model 的对象可以在 Presenter 的构造方法中创建。另外,这里的 Presenter 也可以写成接口的形式,读者可以按照自己的喜好来选择。
然后看一下在业务代码中该如何使用 MVP 的封装,代码如下
interface?TestContract?{
interface?Model?extends?BaseModel?{void?getData1(Callback1?callback1);void?getData2(Callback2?callback2);void?getData3(Callback3?callback3);}
interface?View?extends?BaseView?{void?updateUI1();void?updateUI2();void?updateUI3();}
abstract?class?Presenter?extends?BasePresenter<View,?Model>?{abstract?void?request1();abstract?void?request2();void?request3()?{model.getData3(new?Callback3()?{@Overridepublic?void?onResult(String?text)?{view.updateUI3();}});}}}
首先定义一个 Contract 契约接口,然后把 Model、View、和 Presenter 的子类分别放入 Contract 的内部,这里的一个 Contract 就对应一个页面(一个 Activity 或者一个 Fragment),放在 Contract 内部是为了让同一个页面的逻辑方法都放在一起,方便查看和修改。Presenter 中的 request3 方法演示了如何通过 Presenter 来进行 View 和 Model 的交互。
接下来要做的就是实现这三个模块的逻辑方法了,在 Activity 或 Fragment 中实现 TextContract.View 的接口,再分别创建两个类用来实现 TextContract.Model 和 TextContract.Presenter,复写里面的抽象方法就好了。
扩展:用 RxJava 简化代码
上面的代码中,Model 层中的每个方法都传入了一个回调接口,这是因为获取数据往往是异步的,在获取的数据时需要用回调接口通知 Presenter 来更新 View。
如果想要避免回调接口,可以采用 RxJava 的方式来 Model 获取的数据直接返回一个 Observable,接下来用 RxJava 的方式来改造前面的例子
public?class?HttpModel?{public?Observable<String>?request()?{return?Observable.create(new?ObservableOnSubscribe<String>()?{@Overridepublic?void?subscribe(ObservableEmitter<String>?emitter)?throws?Exception?{Thread.sleep(2000);emitter.onNext("从网络获取到的数据");emitter.onComplete();}}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());}}
public?class?Presenter?{private?MVPView?view;private?HttpModel?model;
public?Presenter(MVPView?view)?{this.view?=?view;model?=?new?HttpModel();}
private?Disposable?disposable;
public?void?request()?{disposable?=?model.request().subscribe(new?Consumer<String>()?{@Overridepublic?void?accept(String?s)?throws?Exception?{view.updateTv(s);}},?new?Consumer<Throwable>()?{@Overridepublic?void?accept(Throwable?throwable)?throws?Exception?{
}});}
public?void?detachView()?{view?=?null;if?(disposable?!=?null?&&?!disposable.isDisposed())?{disposable.dispose();}}}
Model 的 request 方法直接返回一个 Observable,然后在 Presenter 中调用 subscribe 方法来通知 View 更新,这样就避免了使用回调接口。
开源库推荐
最后,推荐一个 MVP 架构的开源库,正如笔者所说,MVP 更多的是一种思想,所以 github 上关于 MVP 的开源库并不多,大多是在完整的 APP 内部自己封装的 MVP。如果想要比较简单的集成 MVP 的架构,笔者推荐这个库:https://github.com/sockeqwe/mosby 它的使用方法比较简单,可以直接参考官方的 demo,接下来简单的分析一下作者的封装思想。
首先 View 层和 Presenter 层分别有一个基础的接口
public?interf
ace?MvpView?{}
public?interface?MvpPresenter<V?extends?MvpView>?{
/***?Set?or?attach?the?view?to?this?presenter*/@UiThreadvoid?attachView(V?view);
/***?Will?be?called?if?the?view?has?been?destroyed.?Typically?this?method?will?be?invoked?from*?<code>Activity.detachView()</code>?or?<code>Fragment.onDestroyView()</code>*/@UiThreadvoid?detachView(boolean?retainInstance);}
这里加 @UIThread 注解是为了确保 attachView 和 detachView 都运行在主线程中。然后业务代码的 Activity 需要继承 MvpActivity
public?abstract?class?MvpActivity<V?extends?MvpView,?P?extends?MvpPresenter<V>>extends?AppCompatActivity?implements?MvpView,com.hannesdorfmann.mosby3.mvp.delegate.MvpDelegateCallback<V,P>?{
protected?ActivityMvpDelegate?mvpDelegate;protected?P?presenter;protected?boolean?retainInstance;
@Override?protected?void?onCreate(Bundle?savedInstanceState)?{super.onCreate(savedInstanceState);getMvpDelegate().onCreate(savedInstanceState);}
@Override?protected?void?onDestroy()?{super.onDestroy();getMvpDelegate().onDestroy();}
@Override?protected?void?onSaveInstanceState(Bundle?outState)?{super.onSaveInstanceState(outState);getMvpDelegate().onSaveInstanceState(outState);}
@Override?protected?void?onPause()?{super.onPause();getMvpDelegate().onPause();}
@Override?protected?void?onResume()?{super.onResume();getMvpDelegate().onResume();}
@Override?protected?void?onStart()?{super.onStart();getMvpDelegate().onStart();}
@Override?protected?void?onStop()?{super.onStop();getMvpDelegate().onStop();}
@Override?protected?void?onRestart()?{super.onRestart();getMvpDelegate().onRestart();}
@Override?public?void?onContentChanged()?{super.onContentChanged();getMvpDelegate().onContentChanged();}
@Override?protected?void?onPostCreate(Bundle?savedInstanceState)?{super.onPostCreate(savedInstanceState);getMvpDelegate().onPostCreate(savedInstanceState);}
/***?Instantiate?a?presenter?instance**?@return?The?{@link?MvpPresenter}?for?this?view*/@NonNull?public?abstract?P?createPresenter();
/***?Get?the?mvp?delegate.?This?is?internally?used?for?creating?presenter,?attaching?and?detaching*?view?from?presenter.**?<p><b>Please?note?that?only?one?instance?of?mvp?delegate?should?be?used?per?Activity*?instance</b>.*?</p>**?<p>*?Only?override?this?method?if?you?really?know?what?you?are?doing.*?</p>**?@return?{@link?ActivityMvpDelegateImpl}*/@NonNull?protected?ActivityMvpDelegate<V,?P>?getMvpDelegate()?{if?(mvpDelegate?==?null)?{mvpDelegate?=?new?ActivityMvpDelegateImpl(this,?this,?true);}
return?mvpDelegate;}
@NonNull?@Override?public?P?getPresenter()?{return?presenter;}
@Override?public?void?setPresenter(@NonNull?P?presenter)?{this.presenter?=?presenter;}
@NonNull?@Override?public?V?getMvpView()?{return?(V)?this;}}
MvpActivity 中持有一个 ActivityMvpDelegate 对象,它的实现类是 ActivityMvpDelegateImpl,并且需要传入 MvpDelegateCallback 接口,ActivityMvpDelegateImpl 的代码如下
public?class?ActivityMvpDelegateImpl<V?extends?MvpView,?P?extends?MvpPresenter<V>>implements?ActivityMvpDelegate?{
protected?static?final?String?KEY_MOSBY_VIEW_ID?=?"com.hannesdorfmann.mosby3.activity.mvp.id";
public?static?boolean?DEBUG?=?false;private?static?final?String?DEBUG_TAG?=?"ActivityMvpDelegateImpl";
private?MvpDelegateCallback<V,?P>?delegateCallback;protected?boolean?keepPresenterInstance;protected?Activity?activity;protected?String?mosbyViewId?=?null;
/***?@param?activity?The?Activity*?@param?delegateCallback?The?callback*?@param?keepPresenterInstance?true,?if?the?presenter?instance?should?be?kept?across?screen*?orientation?changes.?Otherwise?false.*/public?ActivityMvpDelegateImpl(@NonNull?Activity?activity,@NonNull?MvpDelegateCallback<V,?P>?delegateCallback,?boolean?keepPresenterInstance)?{
if?(activity?==?null)?{throw?new?NullPointerException("Activity?is?null!");}
if?(delegateCallback?==?null)?{throw?new?NullPointerException("MvpDelegateCallback?is?null!");}this.delegateCallback?=?delegateCallback;this.activity?=?activity;this.keepPresenterInstance?=?keepPresenterInstance;}
/***?Determines?whether?or?not?a?Presenter?Instance?should?be?kept**?@param?keepPresenterInstance?true,?if?the?delegate?has?enabled?keep*/static?boolean?retainPresenterInstance(boolean?keepPresenterInstance,?Activity?activity)?{return?keepPresenterInstance?&&?(activity.isChangingConfigurations()||?!activity.isFinishing());}
/***?Generates?the?unique?(mosby?internal)?view?id?and?calls?{@link*?MvpDelegateCallback#createPresenter()}*?to?create?a?new?presenter?instance**?@return?The?new?created?presenter?instance*/private?P?createViewIdAndCreatePresenter()?{
P?presenter?=?delegateCallback.createPresenter();if?(presenter?==?null)?{throw?new?NullPointerException("Presenter?returned?from?createPresenter()?is?null.?Activity?is?"?+?activity);}if?(keepPresenterInstance)?{mosbyViewId?=?UUID.randomUUID().toString();PresenterManager.putPresenter(activity,?mosbyViewId,?presenter);}return?presenter;}
@Override?public?void?onCreate(Bundle?bundle)?{
P?presenter?=?null;
if?(bundle?!=?null?&&?keepPresenterInstance)?{
mosbyViewId?=?bundle.getString(KEY_MOSBY_VIEW_ID);
if?(DEBUG)?{Log.d(DEBUG_TAG,"MosbyView?ID?=?"?+?mosbyViewId?+?"?for?MvpView:?"?+?delegateCallback.getMvpView());}
if?(mosbyViewId?!=?null&&?(presenter?=?PresenterManager.getPresenter(activity,?mosbyViewId))?!=?null)?{////?Presenter?restored?from?cache//if?(DEBUG)?{Log.d(DEBUG_TAG,"Reused?presenter?"?+?presenter?+?"?for?view?"?+?delegateCallback.getMvpView());}}?else?{////?No?presenter?found?in?cache,?most?likely?caused?by?process?death//presenter?=?createViewIdAndCreatePresenter();if?(DEBUG)?{Log.d(DEBUG_TAG,?"No?presenter?found?although?view?Id?was?here:?"+?mosbyViewId+?".?Most?likely?this?was?caused?by?a?process?death.?New?Presenter?created"+?presenter+?"?for?view?"+?getMvpView());}}}?else?{////?Activity?starting?first?time,?so?create?a?new?presenter//presenter?=?createViewIdAndCreatePresenter();if?(DEBUG)?{Log.d(DEBUG_TAG,?"New?presenter?"?+?presenter?+?"?for?view?"?+?getMvpView());}}
if?(presenter?==?null)?{throw?new?IllegalStateException("Oops,?Presenter?is?null.?This?seems?to?be?a?Mosby?internal?bug.?Please?report?this?issue?here:?https://github.com/sockeqwe/mosby/issues");}
delegateCallback.setPresenter(presenter);getPresenter().attachView(getMvpView());
if?(DEBUG)?{Log.d(DEBUG_TAG,?"View"?+?getMvpView()?+?"?attached?to?Presenter?"?+?presenter);}}
private?P?getPresenter()?{P?presenter?=?delegateCallback.getPresenter();if?(presenter?==?null)?{throw?new?NullPointerException("Presenter?returned?from?getPresenter()?is?null");}return?presenter;}
private?V?getMvpView()?{V?view?=?delegateCallback.getMvpView();if?(view?==?null)?{throw?new?NullPointerException("View?returned?from?getMvpView()?is?null");}return?view;}
@Override?public?void?onDestroy()?{boolean?retainPresenterInstance?=?retainPresenterInstance(keepPresenterInstance,?activity);getPresenter().detachView(retainPresenterInstance);if?(!retainPresenterInstance?&&?mosbyViewId?!=?null)?{PresenterManager.remove(activity,?mosbyViewId);}
if?(DEBUG)?{if?(retainPresenterInstance)?{Log.d(DEBUG_TAG,?"View"+?getMvpView()+?"?destroyed?temporarily.?View?detached?from?presenter?"+?getPresenter());}?else?{Log.d(DEBUG_TAG,?"View"
评论