在上一篇博客中,我们介绍了了MVP的基本使用步骤点击打开链接 ,但是还有许多问题等着我们做相应的优化。
问题 1:调用View可能会引发空指针异常。
在上面的例子中,MVP架构中的应用请求网络数据的时候,需要等待后台反馈数据后更新界面,但是在请求的过程中,可能当前的Activity因为某种原因销毁了.Presenter 收到后台反馈并调用View接口处理UI时,由于Activity已经被销毁,就会引发空指针异常。
解决方案:
之前是在Presenter的构造方法中得到View的引用,现在我们需要修改Presenter引用View的方式,来让View接口与宿主共存亡。
public class NewsPresenterImpl implements NewsPresenter, OnLoadNewsListListener {
private static final String TAG = "NewsPresenterImpl";
private NewsView mNewsView;
private NewsModel mNewsModel;
public NewsPresenterImpl(NewsView newsView) {
this.mNewsModel = new NewsModelImpl();
}
/**
* 绑定View
*
* @param
*/
public void attachView(NewsView newsView) {
mNewsView = newsView;
}
/**
* 断开View
*
* @param
*/
public void detachView() {
mNewsView = null;
}
}
相应的,在调用之前需要做一下非空判断
@Override
public void onSuccess(List list) {
if (mNewsView != null) {
mNewsView.hideProgress();
mNewsView.addNews(list);
}
}
接下来,把绑定View的方法与View的生命周期关联起来。
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNewsPresenter = new NewsPresenterImpl();
((NewsPresenterImpl)mNewsPresenter).attachView(this);
}
@Override
public void onDestroy() {
super.onDestroy();
((NewsPresenterImpl)mNewsPresenter).detachView(this);
}
问题 2:有太多的冗余代码。
在上面的demo中
public interface OnLoadNewsListListener {
void onSuccess(List list);
void onFailure(String msg, Exception e);
}
OnLoadNewsListListener 接口中的onSuccess()方法需要根据请求数据的不同类型设置为不同类型的参数,所以每当有新的数据类型都需要新建一个OnLoadNewsListListener接口。
解决方案:引入泛型的概念,让调用者去定义具体想要接收的数据类型。
public interface OnLoadNewsListListener {
void onSuccess(T list);
void onFailure(String msg, Exception e);
}
BaseView:
View接口中定义Activity的UI逻辑。因为有很多方法几乎在每个Activity中都会用到,例如显示和隐藏正在加载进度条,显示Toast提示等,索性将这些方法变成通用的:
public interface BaseView {
void showProgress();
void hideProgress();
void showLoadFailMsg();
}
public interface NewsView extends BaseView{
void addNews(List newsList);
}
BasePresenter:
Presenter中可共用的代码就是对View引用的方法了,值得注意的是,上面已经定义好了BaseView,所以我们希望Presenter中持有的View都是BaseView的子类,这里同样需要泛型来约束:
public class BasePresenter {
/**
* 绑定的View
*/
private T mvpView;
/**
* 绑定View,一般在初始化中调用该方法
*/
public void attachView(T mvpView){
this.mvpView = mvpView;
}
/**
* 断开View,一般在onDestory中调用
*/
public void detachView(){
mvpView = null;
}
/**
* 是否与View建立连接
*/
public boolean isViewAttached(){
return mvpView!=null;
}
/**
* 获取连接的View
*/
public T getView(){
return mvpView;
}
}
BaseActivity:
BaseActivity主要负责实现BaseView中通用的UI逻辑方法,比如这些通用的方法就不用每个Activity都要去实现一遍了。
public abstract class BaseActivity extends Activity implements BaseView{
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setCancelable(false);
}
public void showLoading(){
if(!mProgressDialog.isShowing()){
mProgressDialog.show();
}
}
public void hideLoading(){
if(mProgressDialog.isShowing()){
mProgressDialog.dismiss();
}
}
}
BaseFragment:
public abstract class BaseFragment extends Fragment implements BaseView {
public abstract int getContentViewId();
protected abstract void initAllMembersView(Bundle savedInstanceState);
protected Context mContext;
protected View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
mRootView = inflater.inflate(getContentViewId(), container, false);
this.mContext = getActivity();
initAllMembersView(savedInstanceState);
return mRootView;
}
@Override
public void showLoading() {
checkActivityAttached();
((BaseFragmentActivity) mContext).showLoading();
}
@Override
public void showLoading(String msg) {
checkActivityAttached();
((BaseFragmentActivity) mContext).showLoading(msg);
}
@Override
public void hideLoading() {
checkActivityAttached();
((BaseFragmentActivity) mContext).hideLoading();
}
@Override
public void showToast(String msg) {
checkActivityAttached();
((BaseFragmentActivity) mContext).showToast(msg);
}
@Override
public void showErr() {
checkActivityAttached();
((BaseFragmentActivity) mContext).showErr();
}
protected boolean isAttachedContext() {
return getActivity() != null;
}
/**
* 检查activity连接情况
*/
public void checkActivityAttached() {
if (getActivity() == null) {
throw new ActivityNotAttachedException();
}
}
针对Model层的优化:
Model层相比其他单元来说比较特殊,因为它们更像一个整体,只是单纯的帮上层拿数据而已。再就是MVP的理念是让业务逻辑相互独立,这就导致每个网络请求也被独立成了单个Model,这种方式在实际开发中会出现一些问题:
1.无法对所有的Model统一管理
2.每个Model对外提供的获取数据方法不一样,上层请求数据没有规范。
3.对已存在的Model管理困难,不能直观的统计已经存在的Model
所以我们更希望Model层是一个庞大且独立的单个模块,请求方式规范化,管理Model更加直观。
如图所示,MVP架构的Model层中,Presenter请求数据不再直接调用具体的Model对象,统一以DataModel类作为请求层的入口,以常量Token区别具体的请求。DataModel会根据Token的不同拉取底层对应的具体Model。
优化之后的Model层是一个庞大而且独立的模块,对外提供统一的请求方法与请求规则,这样做的好处有很多:
1.数据请求单独编写,无需配合上层界面测试。
2.统一管理,修改方便
3.利用Token类可以直观的统计出已存在的请求接口。
代码实现:
根据上节结构图中的描述在考虑到实际请求,我们需要设计一下几个类:
1.DataModel:数据层顶级入口,项目中所有的数据都由该类流入和流出,负责分发所以的请求数据。
2.Token:数据请求标识类,定义了项目中所有的数据请求。
3.BaseMode: 所有的Model的顶级父类,负责对外提供数据请求标准,对内为所有Model提供请求的底层支持。
最后实现后理想的请求数据方法是:
BaseModel:
BaseModel中定义了对外的请求数据规则,包括设置参数的方法和设置Canback的方法,还可以定义一些通用的数据请求方法,比如说网络请求的Get和Post方法。
1. public abstract class BaseModel {
2.
3. //数据请求参数
4. protected String[] mParams;
5.
6. /**
7. * 设置数据请求参数
8. * @param args 参数数组
9. */
10. public BaseModel params(String... args){
11. mParams = args;
12. return this;
13. }
14.
15. // 添加Callback并执行数据请求
16. // 具体的数据请求由子类实现
17. public abstract void execute(Callback callback);
18.
19. // 执行Get网络请求,此类看需求由自己选择写与不写
20. protected void requestGetAPI(String url,Callback callback){
21. //这里写具体的网络请求
22. }
23.
24. // 执行Post网络请求,此类看需求由自己选择写与不写
25. protected void requestPostAPI(String url, Map params,Callback callback){
26. //这里写具体的网络请求
27. }
28.
29. }
写好BaseModel后再看实现具体Model的方法:
1. public class UserDataModel extends BaseModel {
2.
3. @Override
4. public void execute(final Callback callback) {
5.
6. // 模拟网络请求耗时操作
7. new Handler().postDelayed(new Runnable() {
8. @Override
9. public void run() {
10. // mParams 是从父类得到的请求参数
11. switch (mParams[0]){
12. case "normal":
13. callback.onSuccess("根据参数"+mParams[0]+"的请求网络数据成功");
14. break;
15.
16. case "failure":
17. callback.onFailure("请求失败:参数有误");
18. break;
19.
20. case "error":
21. callback.onError();
22. break;
23. }
24. callback.onComplete();
25. }
26. },2000);
27. }
28. }
从上面的代码可以看出,实现具体的Model请求时必须要重写BaseModel的抽象方法execute。
由于DataModel负责数据请求的分发,所以我们可以用一个简单工厂模式,然后通过switch(token)语句判断要调用的Model。但是如果这样的话,在实际开发中,我们每次添加一个数据请求的接口,不光需要新建对应的Model和Token,还需要在DataModel类的switch(token)语句中新增加对应的判断,太麻烦。
于是,我们可以考虑使用反射机制。请求数据时以具体Model的报名+类型作为Token,利用反射机制直接找到对应的Model。
1. public class DataModel {
2.
3. public static BaseModel 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. public class Token {
2.
3. // 包名
4. private static final String PACKAGE_NAME = "com.jesse.mvp.data.model.";
5.
6. // 具体Model
7. public static final String 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(new Callback() {
8.
9. @Override
10. public void onSuccess(String data) {
11. //调用view接口显示数据
12. mView.showData(data);
13. }
14.
15. @Override
16. public void onFailure(String msg) {
17. //调用view接口提示失败信息
18. mView.showFailureMessage(msg);
19. }
20.
21. @Override
22. public void onError() {
23. //调用view接口提示请求异常
24. mView.showErrorMessage();
25. }
26.
27. @Override
28. public void onComplete() {
29. // 隐藏正在加载进度条
30. mView.hideLoading();
31. }
32. });
添加Model的步骤:
1.新建一个Model并继承BaseModel,完成具体的数据请求。
2.在Token中添加对应的Model包名+类名。注意写好注释,方便以后查询。
总结:
经过优化的model层很好的统一化了请求方法规范,利用BaseModel不仅有效的减少了请求数据的冗余代码,最关键的还是得到了所有model的集中控制权。例如我们想给所有的请求都加上cookie,直接在BaseModel层做处理即可。