MVP架构浅析

引言

Android代码在没有应用MVP架构时,很多人认为符合MVC

·        View:对应于布局文件

·        Model:业务逻辑和实体模型

·        Controllor:对应于Activity

 

这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller

而当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:

·        View 对应于Activity,负责View的绘制以及与用户交互

·        Model 依然是业务逻辑和实体模型

·        Presenter 负责完成ViewModel间的交互

 

之所以让人觉得耳目一新,是因为从并不标准的MVCMVP的转变可以减少了Activity的职责,简化了Activity中的代码,将复杂的逻辑代码提取到了Presenter中进行处理。与之对应的好处就是,耦合度更低,更方便的进行测试。

为什么用 MVP架构?

MVP 模式将Activity 中的业务逻辑全部分离出来,让Activity 只做 UI 逻辑的处理,所有跟Android API无关的业务逻辑由 Presenter 层来完成。

将业务处理分离出来后最明显的好处就是管理方便,但是缺点就是增加了代码量。

MVP 理论知识

MVP 架构中跟MVC类似的是同样也分为三层。

Activity Fragment 视为View层,负责处理 UI

Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API

Model 层中包含着具体的数据请求,数据源。

三层之间调用顺序为view->presenter->model,为了调用安全着想不可反向调用!不可跨级调用!

Model 层如何反馈给Presenter 层的呢?Presenter 又是如何操控View 层呢?看图!

 

上图中说明了低层的不会直接给上一层做反馈,而是通过 View Callback 为上级做出了反馈,这样就解决了请求数据与更新界面的异步操作。上图中 View Callback 都是以接口的形式存在的。

View 中定义了 Activity 的具体操作,主要是些将请求到的数据在界面中更新之类的。

Callback 中定义了请求数据时反馈的各种状态:成功、失败、异常等。

入门版MVP架构模式的代码实现

MVP 模式构造一个简易模拟请求网络的小程序。效果图如下:

 

 

有三个请求数据的按钮分别对应成功、失败、异常三种不同的反馈状态。

下面是Demo中的Java文件目录:

Callback接口

Callback 接口是Model层给Presenter层反馈请求信息的传递载体,所以需要在Callback中定义数据请求的各种反馈状态:

1.  publicinterfaceMvpCallback {

2.     /**

3.       * 数据请求成功

4.       * @param data 请求到的数据

5.       */

6.      void onSuccess(String data);

7.      /**

8.       使用网络API接口请求方式时,虽然已经请求成功但是由

9.       *  {@code msg}的原因无法正常返回数据。

10.     */

11.    void onFailure(String msg);

12.     /**

13.     * 请求数据失败,指在请求网络API接口请求方式时,出现无法联网、

14.     * 缺少权限,内存泄露等原因导致无法连接到请求数据源。

15.     */

16.    void onError();

17.    /**

18.     * 当请求数据结束时,无论请求结果是成功,失败或是抛出异常都会执行此方法给用户做处理,通常做网络

19.     * 请求时可以在此处隐藏正在加载的等待控件

20.     */

21.    void onComplete();

22.}

 

 

 

Model

Model 类中定了具体的网络请求操作。为模拟真实的网络请求,利用postDelayed方法模拟耗时操作,通过判断请求参数反馈不同的请求状态:

 

1.  publicclassMvpModel {

2.   

3.      /**

4.       * 获取网络接口数据

5.       * @param param请求参数

6.       * @paramcallback 数据回调接口

7.       */

8.      publicstaticvoid getNetData(finalString param, finalMvpCallback callback){

9.   

10.        // 利用postDelayed方法模拟网络请求数据的耗时操作

11.        newHandler().postDelayed(newRunnable() {

12.            @Override

13.            publicvoid run() {

14.                switch (param){

15. 

16.                    case"normal":

17.                       callback.onSuccess("根据参数"+param+"的请求网络数据成功");

18.                       break;

19. 

20.                   case"failure":

21.                       callback.onFailure("请求失败:参数有误");

22.                       break;

23. 

24.                    case"error":

25.                       callback.onError();

26.                       break;

27.                }

28.               callback.onComplete();

29.            }

30. 

31.        },2000);

32.    }

33. 

34.}

 

View 接口

View接口是ActivityPresenter层的中间层,它的作用是根据具体业务的需要,为Presenter提供调用Activity中具体UI逻辑操作的方法。

1.  publicinterfaceMvpView {

2.   

3.      /**

4.       * 显示正在加载进度框

5.       */

6.      void showLoading();

7.   

8.      /**

9.       * 隐藏正在加载进度框

10.     */

11.    void hideLoading();

12. 

13.    /**

14.     * 当数据请求成功后,调用此接口显示数据

15.     * @param data 数据源

16.     */

17.    void showData(String data);

18. 

19.    /**

20.     * 当数据请求失败后,调用此接口提示

21.     * @param msg 失败原因

22.     */

23.    void showFailureMessage(String msg);

24. 

25.    /**

26.     * 当数据请求异常,调用此接口提示

27.     */

28.    void showErrorMessage();

29. 

30.}

 

Presenter

Presenter类是具体的逻辑业务处理类,该类为纯Java类,不包含任何Android API,负责请求数据,并对数据请求的反馈进行处理。

Presenter类的构造方法中有一个View接口的参数,是为了能够通过View接口通知Activity进行更新界面等操作。

1.  publicclassMvpPresenter {

2.   

3.      // View接口

4.      privateMvpView mView;

5.   

6.      publicMvpPresenter(MvpView view){

7.          this.mView = view;

8.      }

9.   

10.    /**

11.     * 获取网络数据

12.     * @paramparams 参数

13.     */

14.    publicvoid getData(Stringparams){

15. 

16.        //显示正在加载进度条

17.       mView.showLoading();

18.        // 调用Model请求数据

19.        MvpModel.getNetData(params, newMvpCallback() {

20.            @Override

21.            publicvoid onSuccess(String data) {

22.                //调用view接口显示数据

23.               mView.showData(data);

24.            }

25. 

26.            @Override

27.            publicvoid onFailure(String msg) {

28.                //调用view接口提示失败信息

29.               mView.showFailureMessage(msg);

30.            }

31. 

32.            @Override

33.            publicvoid onError() {

34.                //调用view接口提示请求异常

35.               mView.showErrorMessage();

36.            }

37. 

38.            @Override

39.            publicvoid onComplete() {

40.                // 隐藏正在加载进度条

41.                mView.hideLoading();

42.            }

43.        });

44.    }

45. 

46.}

 

xml布局文件

1.  <?xml version="1.0" encoding="utf-8"?>

2.  <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

3.      xmlns:tools="http://schemas.android.com/tools"

4.      android:layout_width="match_parent"

5.      android:layout_height="match_parent"

6.      android:padding="16dp"

7.      android:orientation="vertical"

8.      tools:context="com.jessewu.mvpdemo.MainActivity">

9.   

10.    <TextView

11.        android:id="@+id/text"

12.        android:layout_width="match_parent"

13.        android:layout_height="0dp"

14.        android:layout_weight="1"

15.        android:text="点击按钮获取网络数据"/>

16. 

17.    <Button

18.        android:layout_width="match_parent"

19.        android:layout_height="wrap_content"

20.        android:text="获取数据【成功】"

21.        android:onClick="getData"

22.        />

23.    <Button

24.        android:layout_width="match_parent"

25.        android:layout_height="wrap_content"

26.        android:text="获取数据【失败】"

27.        android:onClick="getDataForFailure"

28.        />

29.    <Button

30.        android:layout_width="match_parent"

31.        android:layout_height="wrap_content"

32.        android:text="获取数据【异常】"

33.        android:onClick="getDataForError"

34.        />

35.</LinearLayout>

 

Activity

Activity代码中需要强调的是如果想要调用Presenter就要先实现Presenter需要的对应的View接口。

1.  publicclassMainActivityextendsAppCompatActivityimplementsMvpView  {

2.   

3.      //进度条

4.      ProgressDialog progressDialog;

5.      TextView text;

6.   

7.      MvpPresenter presenter;

8.   

9.      @Override

10.    protectedvoid onCreate(Bundle savedInstanceState) {

11.        super.onCreate(savedInstanceState);

12.       setContentView(R.layout.activity_main);

13. 

14.        text = (TextView)findViewById(R.id.text);

15. 

16.        // 初始化进度条

17.       progressDialog = newProgressDialog(this);

18.       progressDialog.setCancelable(false);

19.       progressDialog.setMessage("正在加载数据");

20. 

21.        //初始化Presenter

22.        presenter =newMvpPresenter(this);

23. 

24.    }

25. 

26.    // button 点击事件调用方法

27.    publicvoid getData(View view){

28.       presenter.getData("normal");

29.    }

30. 

31.    // button 点击事件调用方法

32.    publicvoid getDataForFailure(View view){

33.       presenter.getData("failure");

34.    }

35. 

36.    // button 点击事件调用方法

37.    publicvoid getDataForError(View view){

38.        presenter.getData("error");

39.    }

40. 

41.    @Override

42.    publicvoid showLoading() {

43.        if(!progressDialog.isShowing()) {

44.           progressDialog.show();

45.        }

46.    }

47. 

48.    @Override

49.    publicvoid hideLoading() {

50.        if (progressDialog.isShowing()){

51.           progressDialog.dismiss();

52.        }

53.    }

54. 

55.    @Override

56.    publicvoid showData(String data) {

57.       text.setText(data);

58.    }

59. 

60.    @Override

61.    publicvoid showFailureMessage(String msg) {

62.        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();

63.       text.setText(msg);

64.    }

65. 

66.    @Override

67.    publicvoid showErrorMessage() {

68.        Toast.makeText(this, "网络请求数据出现异常", Toast.LENGTH_SHORT).show();

69.       text.setText("网络请求数据出现异常");

70.    }

71.}

 

 

至此,已经完整的实现了一个简易的MVP架构。

注意!以上代码中还存在很大的问题,不能用到实际开发中,下面会讨论目前存在的问题以及如何优化。

 

提升版MVP架构 - base层顶级父类

之前说过入门版MVP架构模式中还存在很多问题不能应用到实际的开发中,大概存在的问题有:

·        构架存在漏洞

·        代码冗余量大

·        通用性差

针对这些问题我们需要进一步优化,单车变摩托,升级为可以在实际开发中使用的提升版MVP架构。

调用View可能引发的空指针异常

举一个例子,在上述入门版MVP架构中的应用请求网络数据时需要等待后台反馈数据后更新界面,但是在请求过程中当前Activity突然因为某种原因被销毁,Presenter收到后台反馈并调用View接口处理UI逻辑时由于Activity已经被销毁,就会引发空指针异常。

想要避免这种情况的发生就需要每次调用View前都知道宿主Activity的生命状态。

之前是在Presenter的构造方法中得到View接口的引用,现在我们需要修改Presenter引用View接口的方式让View接口与宿主Activity共存亡:

1.  publicclassMvpPresenter {

2.   

3.      // View接口

4.      privateMvpView mView;

5.   

6.      publicMvpPresenter(){

7.          //构造方法中不再需要View参数

8.      }

9.   

10.  /**

11.     * 绑定view,一般在初始化中调用该方法

12.     */

13.    publicvoid attachView(MvpView  mvpView) {

14.        this.mView= mvpView;

15.    }

16. 

17.    /**

18.     * 断开view,一般在onDestroy中调用

19.     */

20.    publicvoid detachView() {

21.        this.mView= null;

22.    }

23. 

24.    /**

25.     * 是否与View建立连接

26.     * 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接

27.     */

28.    publicboolean isViewAttached(){

29.        return mView!= null;

30.    }

31. 

32.    /**

33.     * 获取网络数据

34.     * @paramparams 参数

35.     */

36.    publicvoid getData(Stringparams){

37. 

38.        //显示正在加载进度条

39.       mView.showLoading();

40.        // 调用Model请求数据

41.        MvpModel.getNetData(params, newMvpCallback() {

42.            @Override

43.            publicvoid onSuccess(String data) {

44.                //调用view接口显示数据

45.                if(isViewAttached()){

46.                   mView.showData(data);

47.                }

48. 

49.            }

50. 

51.            @Override

52.            publicvoid onFailure(String msg) {

53.                //调用view接口提示失败信息

54.                if(isViewAttached()){

55.                    mView.showFailureMessage(msg);

56.               }         

57.            }

58. 

59.            @Override

60.            publicvoid onError() {

61.                //调用view接口提示请求异常

62.                if(isViewAttached()){

63.                   mView.showErrorMessage();

64.                }

65.            }

66. 

67.            @Override

68.            publicvoid onComplete() {

69.                // 隐藏正在加载进度条

70.                if(isViewAttached()){

71.                   mView.hideLoading();

72.                }

73.            }

74.        });

75.    }

76. 

77.}

 

上面Presenter代码中比之前增加了三个方法:

·        attachView() 绑定View引用。

·        detachView 断开View引用。

·        isViewAttached() 判断View引用是否存在。

其中attachView()detachView()是为Activity准备的,isViewAttached()作用是Presenter内部每次调用View接口中的方法是判断View 的引用是否存在。

把绑定View的方法写到Activity的生命周期中:

1.  publicclassMainActivityextendsActivityimplementsMvpView{

2.    MvpPresenter presenter;

3.   

4.    @Override

5.      protectedvoid onCreate(Bundle savedInstanceState) {

6.          super.onCreate(savedInstanceState);

7.          setContentView(R.layout.activity_main);

8.   

9.          //初始化Presenter

10.        presenter =newMvpPresenter();

11.        // 绑定View引用

12.       presenter.attachView(this);

13.    }

14. 

15.    @Override

16.    protectedvoid onDestroy() {

17.        super.onDestroy();

18.        // 断开View引用

19.       presenter.detachView();

20.    }

21.}

 

 

结合Activity构建base

入门版MVP模式代码量的巨大,冗余代码实在太多,所以需要为MVP中所有单元都设计一个顶级父类来减少重复的冗余代码。同样的道理,需要为Activity设计一个父类方便与MVP架构更完美的结合。最后将所有父类单独分到一个base包中供外界继承调用。

 

Callback

在入门版中Callback接口中的onSuccess()方法需要根据请求数据类型的不同设置为不同类型的参数,所以每当有新的数据类型都需要新建一个Callback,解决方法是引入泛型的概念,用调用者去定义具体想要接收的数据类型:

 

1.  publicinterfaceCallback<T> {

2.   

3.     /**

4.       * 数据请求成功

5.       * @param data 请求到的数据

6.       */

7.      void onSuccess(T data);

8.   

9.      /**

10.     使用网络API接口请求方式时,虽然已经请求成功但是由

11.     {@code msg}的原因无法正常返回数据。

12.     */

13.    void onFailure(String msg);

14. 

15.     /**

16.     * 请求数据失败,指在请求网络API接口请求方式时,出现无法联网、

17.     * 缺少权限,内存泄露等原因导致无法连接到请求数据源。

18.     */

19.    void onError();

20. 

21.    /**

22.     * 当请求数据结束时,无论请求结果是成功,失败或是抛出异常都会执行此方法给用户做处理,通常做网络

23.     * 请求时可以在此处隐藏正在加载的等待控件

24.     */

25.    void onComplete();

26. 

27.}

 

 

BaseView

View接口中定义ActivityUI逻辑。因为有很多方法几乎在每个Activity中都会用到,例如显示和隐藏正在加载进度条,显示Toast提示等,索性将这些方法变成通用的:

1.  publicinterfaceBaseView {

2.   

3.       /**

4.       * 显示正在加载view

5.       */

6.      void showLoading();

7.   

8.      /**

9.       * 关闭正在加载view

10.     */

11.    void hideLoading();

12. 

13.    /**

14.     * 显示提示

15.     * @param msg

16.     */

17.    void showToast(String msg);

18. 

19.    /**

20.     * 显示请求错误提示

21.     */

22.    void showErr();

23. 

24.    /**

25.     * 获取上下文

26.     * @return 上下文

27.     */

28.    Context getContext();

29.}

 

BasePresenter

Presenter中可共用的代码就是对View引用的方法了,值得注意的是,上面已经定义好了BaseView,最好Presenter中持有的View都是BaseView的子类,这里同样需要泛型来约束:


 

 

1.  publicclassBasePresenter<V extendsIBaseView> {

2.   

3.      /**

4.       * 绑定的view

5.       */

6.      private V mvpView;

7.   

8.      /**

9.       * 绑定view,一般在初始化中调用该方法

10.     */

11.    @Override

12.    publicvoid attachView(V mvpView){

13.        this.mvpView = mvpView;

14.    }

15. 

16.    /**

17.     * 断开view,一般在onDestroy中调用

18.     */

19.    @Override

20.    publicvoid detachView() {

21.        this.mvpView = null;

22.    }

23. 

24.    /**

25.     * 是否与View建立连接

26.     * 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接

27.     */

28.    publicboolean isViewAttached(){

29.        return mvpView != null;

30.    }

31. 

32.    /**

33.     * 获取连接的view

34.     */

35.    public V getView(){

36.        return mvpView;

37.    }

 

BaseActivity

BaseActivity主要是负责实现 BaseView 中通用的UI逻辑方法,如此这些通用的方法就不用每个Activity都要去实现一遍了。

1.  publicabstractclassBaseActivityextendsActivityimplementsIBaseView {

2.   

3.      privateProgressDialogmProgressDialog;

4.   

5.      @Override

6.      protectedvoid onCreate(Bundle savedInstanceState) {

7.          super.onCreate(savedInstanceState);

8.          mProgressDialog = newProgressDialog(this);

9.          mProgressDialog.setCancelable(false);

10.    }

11. 

12.    @Override

13.    publicvoid showLoading() {

14.        if(!mProgressDialog.isShowing()) {

15.           mProgressDialog.show();

16.        }

17.    }

18. 

19.    @Override

20.    publicvoid hideLoading() {

21.        if (mProgressDialog.isShowing()){

22.           mProgressDialog.dismiss();

23.        }

24.    }

25. 

26.    @Override

27.    publicvoid showToast(String msg) {

28.        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();

29.    }

30. 

31.    @Override

32.    publicvoid showErr() {

33.       showToast(getResources().getString(R.string.api_error_msg));

34.    }

35. 

36.    @Override

37.    publicContext getContext() {

38.        returnBaseActivity.this;

39.    }

 

提升版MVP架构代码实现

封装好了base层我们的提升版MVP架构就完成了,下面再来实现一遍之前用入门版MVP实现的应用。

Model

1.  publicclassMvpModel {

2.   

3.      /**

4.       * 获取网络接口数据

5.       * @param param请求参数

6.       * @paramcallback 数据回调接口

7.       */

8.      publicstaticvoid getNetData(finalString param, finalMvpCallback<String> callback){

9.   

10.        // 利用postDelayed方法模拟网络请求数据的耗时操作

11.        newHandler().postDelayed(newRunnable() {

12.            @Override

13.            publicvoid run() {

14.                switch (param){

15. 

16.                   case"normal":

17.                       callback.onSuccess("根据参数"+param+"的请求网络数据成功");

18.                       break;

19. 

20.                   case"failure":

21.                        callback.onFailure("请求失败:参数有误");

22.                       break;

23. 

24.                   case"error":

25.                       callback.onError();

26.                       break;

27.                }

28.               callback.onComplete();

29.            }

30. 

31.        },2000);

32.    }

33. 

34.}

 

View 接口

1.  publicinterfaceMvpViewextendsBaseView{

2.      /**

3.       * 当数据请求成功后,调用此接口显示数据

4.       * @param data 数据源

5.       */

6.      void showData(String data);

7.   

8.  }

 

Presenter

1.  publicclassMvpPresenterextendsBasePresenter<MvpView > {

2.   

3.      /**

4.       * 获取网络数据

5.       * @paramparams 参数

6.       */

7.      publicvoid getData(Stringparams){

8.   

9.          if (!isViewAttached()){

10.            //如果没有View引用就不加载数据

11.            return;

12.        }

13. 

14.        //显示正在加载进度条

15.       getView().showLoading();

16. 

17.        // 调用Model请求数据

18.        MvpModel.getNetData(params, newMvpCallback()<String> {

19.            @Override

20.            publicvoid onSuccess(String data) {

21.                //调用view接口显示数据

22.                if(isViewAttached()){

23.                   getView().showData(data);

24.                }

25.            }

26. 

27.            @Override

28.            publicvoid onFailure(String msg) {

29.                //调用view接口提示失败信息

30.                if(isViewAttached()){

31.                    getView().showToast(msg);

32.               }          

33.            }

34. 

35.            @Override

36.            publicvoid onError() {

37.                //调用view接口提示请求异常

38.                if(isViewAttached()){

39.                   getView().showErr();

40.                }

41.            }

42. 

43.            @Override

44.            publicvoid onComplete() {

45.                // 隐藏正在加载进度条

46.                if(isViewAttached()){

47.                   getView().hideLoading();

48.                }

49.            }

50.        });

51.    }

52. 

53.}

 

Activity

1.  publicclassMainActivityextendsBaseActivityimplementsMvpView  {

2.   

3.      TextView text;

4.   

5.      MvpPresenter presenter;

6.   

7.      @Override

8.      protectedvoid onCreate(Bundle savedInstanceState) {

9.          super.onCreate(savedInstanceState);

10.       setContentView(R.layout.activity_main);

11. 

12.        text = (TextView)findViewById(R.id.text);

13. 

14.        //初始化Presenter

15.        presenter =newMvpPresenter();

16.       presenter.attachView(this);

17.    }

18. 

19.    @Override

20.    protectedvoid onDestroy() {

21.        super.onDestroy();

22.        //断开View引用

23.       presenter.detachView();

24.    }

25. 

26.    @Override

27.    publicvoid showData(String data) {

28.       text.setText(data);

29.    }

30. 

31.    // button 点击事件调用方法

32.    publicvoid getData(View view){

33.       presenter.getData("normal");

34.    }

35. 

36.    // button 点击事件调用方法

37.    publicvoid getDataForFailure(View view){

38.       presenter.getData("failure");

39.    }

40. 

41.    // button 点击事件调用方法

42.    publicvoid getDataForError(View view){

43.       presenter.getData("error");

44.   

45.}

 

Fragment怎么办?

日常开发中,并不是所有的UI处理都在Activity中进行,Fragment也是其中很重要的一员,那么如何将Fragment结合到MVP中呢?

实现BaseFragement做法跟BaseActivity很类似,需要注意一下Fragement与宿主Activity的链接情况就可以。

1.  publicabstractclassBaseFragmentextendsFragmentimplementsBaseView {

2.   

3.      publicabstractint getContentViewId();

4.   

5.      protectedabstractvoid initAllMembersView(Bundle savedInstanceState);

6.   

7.      protectedContext mContext;

8.      protectedView mRootView;

9.   

10.    @Nullable

11.    @Override

12.    publicView onCreateView(LayoutInflater inflater, @NullableViewGroup container, @NullableBundle savedInstanceState) {

13.        mRootView =inflater.inflate(getContentViewId(), container, false);

14.        this.mContext =getActivity();

15.       initAllMembersView(savedInstanceState);

16.        return mRootView;

17.    }

18. 

19.    @Override

20.    publicvoid showLoading() {

21.       checkActivityAttached();

22.        ((BaseFragmentActivity) mContext).showLoading();

23.    }

24. 

25.    @Override

26.    publicvoid showLoading(String msg) {

27.       checkActivityAttached();

28.        ((BaseFragmentActivity) mContext).showLoading(msg);

29.    }

30. 

31.    @Override

32.    publicvoid hideLoading() {

33.        checkActivityAttached();

34.        ((BaseFragmentActivity) mContext).hideLoading();

35.    }

36. 

37.    @Override

38.    publicvoid showToast(String msg) {

39.       checkActivityAttached();

40.        ((BaseFragmentActivity) mContext).showToast(msg);

41.    }

42. 

43.    @Override

44.    publicvoid showErr() {

45.       checkActivityAttached();

46.        ((BaseFragmentActivity) mContext).showErr();

47.    }

48. 

49.    protectedboolean isAttachedContext(){

50.        return getActivity() != null;

51.    }

52. 

53.    /**

54.     * 检查activity连接情况

55.     */

56.    publicvoidcheckActivityAttached() {

57.        if (getActivity() == null) {

58.            thrownewActivityNotAttachedException();

59.        }

60.    }

61. 

62.    publicstaticclassActivityNotAttachedExceptionextendsRuntimeException {

63.        publicActivityNotAttachedException() {

64.            super("Fragment has disconnected from Activity ! --.");

65.        }

66.    }

67. 

68.}

 

高级版MVP架构 - Model层的单独优化

在从入门版MVP架构优化成提升版MVP架构的过程中,几乎每个单元都做了很大优化并封装到了base层,但是唯独Model层没什么变化。所以,高级版MVP架构的优化主要就是对Model层的优化。

Model层相比其他单元来说比较特殊,因为它们更像一个整体,只是单纯的帮上层拿数据而已。再就是MVP的理念是让业务逻辑互相独立,这就导致每个的网络请求也被独立成了单个Model,不光没必要这么做而且找起来贼麻烦,所以高级版MVP架构中Model层被整体封装成了庞大且独立单一模块。

优化之后的Model层是一个庞大而且独立的模块,对外提供统一的请求数据方法与请求规则,这样做的好处有很多:

·        数据请求单独编写,无需配合上层界面测试。

·        统一管理,修改方便。

·        实现不同数据源(NetAAPI,cache,database)的无缝切换。


 

高级版MVP架构 - Model层的优化

在从入门版MVP架构优化成提升版MVP架构的过程中,几乎每个单元都做了很大优化并封装到了base层,但是唯独Model层没什么变化。所以,高级版MVP架构的优化主要就是对Model层的优化。

单独封装,集中管理

Model层相比其他单元来说比较特殊,因为它们更像一个整体,只是单纯的帮上层拿数据而已。再就是MVP的理念是让业务逻辑互相独立,这就导致每个的网络请求也被独立成了单个Model,这种方式在实际开发中就会出现一些问题:

·        无法对所有Model统一管理。

·        每个Model对外提供的获取数据方法不一样,上层请求数据没有规范。

·        代码冗余高,网络数据请求除URL和参数外其他大概都一样的。

·        对已存在的Model管理困难,不能直观的统计已存在的Model

所以最好Model层是一个庞大且独立单一模块,请求方式规范化,管理Model更加直观。

如上图所示,高级版MVP架构的Model层中,Presenter 请求数据不再直接调用具体的Model对象,统一以 DataModel 类作为数据请求层的入口,以常量类 Token 区别具体请求。 DataModel会根据Token的不同拉取底层对应的具体Model

优化之后的Model层是一个庞大而且独立的模块,对外提供统一的请求数据方法与请求规则,这样做的好处有很多:

·        数据请求单独编写,无需配合上层界面测试。

·        统一管理,修改方便。

·        利用Token类可以直观的统计出已存在的请求接口。

代码实现

根据上节结构图中的描述在考虑到实际情况,我们需要设计以下几个类:

·        DataModel数据层顶级入口,项目中所有数据都由该类流入和流出,负责分发所有的请求数据。

·        Token:数据请求标识类,定义了项目中所有的数据请求。

·        BaseModel:所有Model的顶级父类,负责对外提供数据请求标准,对内为所有Model提供请求的底层支持。

最后实现后理想的请求数据方法是:

BaseModel

BaseModel中定义了对外的请求数据规则,包括设置参数的方法和设置Callback的方法,还可以定义一些通用的数据请求方法,比如说网络请求的GetPost方法。

1.  publicabstractclassBaseModel<T>  {

2.      //数据请求参数

3.      protectedString[] mParams;

4.   

5.      /**

6.       * 设置数据请求参数

7.       * @param args 参数数组

8.       */

9.      public  BaseModelparams(String... args){

10.        mParams =args;

11.        returnthis;

12.    }

13. 

14.    // 添加Callback并执行数据请求

15.    // 具体的数据请求由子类实现

16.    publicabstractvoid execute(Callback<T> callback);

17. 

18.    // 执行Get网络请求,此类看需求由自己选择写与不写

19.    protectedvoid requestGetAPI(String url,Callback<T> callback){

20.        //这里写具体的网络请求

21.    }

22. 

23.    // 执行Post网络请求,此类看需求由自己选择写与不写

24.    protectedvoid requestPostAPI(String url, Mapparams,Callback<T> callback){

25.        //这里写具体的网络请求

26.    }

27.}

 

写好了BaseModel后再看实现具体Model的方法:

1.  publicclassUserDataModelextendsBaseModel<String> {

2.      @Override

3.      publicvoid execute(finalCallback<String> callback) {

4.   

5.          // 模拟网络请求耗时操作

6.          newHandler().postDelayed(newRunnable() {

7.              @Override

8.              publicvoid run() {

9.                  // mParams 是从父类得到的请求参数

10.                switch (mParams[0]){

11.                   case"normal":

12.                       callback.onSuccess("根据参数"+mParams[0]+"的请求网络数据成功");

13.                       break;

14. 

15.                   case"failure":

16.                       callback.onFailure("请求失败:参数有误");

17.                       break;

18. 

19.                   case"error":

20.                       callback.onError();

21.                       break;

22.                }

23.               callback.onComplete();

24.            }

25.        },2000);

26.    }

27.}

 

从上面代码段可以看出,实现具体的Model请求时必须要重写BaseModel的抽象方法execute

DataModel

由于DataModel负责数据请求的分发,可以作成一个简单工厂模式的样子,通过switch(token)语句判断要调用的Model

但如果这样设计的话,在实际开发中每次添加一个数据请求接口,不光需要新建对应的ModelToken,还需要在DataModel类的switch(token)语句中新增加对应的判断,麻烦~

考虑利用反射机制会是一个比较理想的办法,请求数据时以具体Model的包名+类型作为Token,利用反射机制直接找到对应的Model

1.  publicclassDataModel {

2.   

3.      publicstaticBaseModel request(String token){

4.   

5.          // 声明一个空的BaseModel

6.          BaseModel model = null;

7.   

8.          try {

9.              //利用反射机制获得对应Model对象的引用

10.            model =(BaseModel)Class.forName(token).newInstance();

11.        } catch (ClassNotFoundException e) {

12.           e.printStackTrace();

13.        } catch (InstantiationException e) {

14.           e.printStackTrace();

15.        } catch (IllegalAccessException e) {

16.           e.printStackTrace();

17.        }

18.        return model;

19.    }

20. 

21.}

 

Token

由于上节中DataModel使用反射机制获取对应Model的引用,所以Token中存的就应该是对应Model的包名+类名:

 

1.  publicclassToken {

2.   

3.      // 包名

4.      privatestaticfinalString PACKAGE_NAME = "com.jesse.mvp.data.model.";

5.   

6.      // 具体Model

7.      publicstaticfinalString API_USER_DATA =PACKAGE_NAME + "UserDataModel";

8.   

9.  }

 

使用方式

完成了Model层之后再去Presenter调用数据时的样子就舒服多了:

1.  DataModel

2.      // 设置请求标识token

3.      .request(Token.API_USER_DATA)

4.      // 添加请求参数,没有则不添加

5.      .params(userId)

6.      // 注册监听回调

7.      .execute(newCallback<String>() {

8.             @Override

9.             publicvoid onSuccess(String data) {

10.               //调用view接口显示数据

11.              mView.showData(data);

12.           }

13. 

14.           @Override

15.           publicvoid onFailure(String msg) {

16.               //调用view接口提示失败信息

17.              mView.showFailureMessage(msg);

18.           }

19.           @Override

20.           publicvoid onError() {

21.               //调用view接口提示请求异常

22.              mView.showErrorMessage();

23.           }

24.           @Override

25.           publicvoid onComplete() {

26.               // 隐藏正在加载进度条

27.              mView.hideLoading();

28.           }

29. });

 

添加Model的步骤

1.    新建一个Model并继承BaseModel,完成具体的数据请求。

2.    Token中添加对用的Model包名+类名。注意写好注释,方便以后查阅。

总结

经过优化的Model层很好的统一化了请求方法规范,利用BaseModel不仅有效的减少了数据请求的冗余代码,最关键的还是得到了将所有Model的集中控制权,例如我们想给所有的请求都加上cookies,直接在BaseModel层做处理即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值