引言
Android代码在没有应用MVP架构时,很多人认为符合MVC:
· View:对应于布局文件
· Model:业务逻辑和实体模型
· Controllor:对应于Activity
这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller。
而当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:
· View 对应于Activity,负责View的绘制以及与用户交互
· Model 依然是业务逻辑和实体模型
· Presenter 负责完成View于Model间的交互
之所以让人觉得耳目一新,是因为从并不标准的
MVC
到MVP
的转变可以减少了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接口是Activity与Presenter层的中间层,它的作用是根据具体业务的需要,为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接口中定义Activity的UI逻辑。因为有很多方法几乎在每个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的方法,还可以定义一些通用的数据请求方法,比如说网络请求的Get和Post方法。
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。
但如果这样设计的话,在实际开发中每次添加一个数据请求接口,不光需要新建对应的Model和Token,还需要在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层做处理即可。