MVP架构由浅入深篇二(进阶版)
前言:在MVP基础版的讨论中我们发现了基础版MVP存在的不足之处,1.通用性差;2.存在漏洞;3.有重复代码。在本篇内容中,我们会对基础版MVP进行升级,到进阶版MVP解决基础版MVP遇到的问题。
目录
一、在Presenter层调用MvpView接口方法时可能出现空指针异常
一、在Presenter层调用MvpView接口方法时可能出现空指针异常
在Presenter层因为需要给UI层反馈数据,所以持有View层的实例,然后通过实例回调MvpView接口的回调方法。但是如果此使Activity突然异常退出,则view就会出现空指针异常,在MainActivity中有这一行代码:presenter = new MvpPresenter(this);其中this就是实例,如果程序退出就会产生空指针。
想要避免这种情况的发生就需要每次调用View前都知道宿主Activity的生命状态。
先看一下进阶版文件结构:建一个base包,里面创建基类用来继承
1.新增Callback接口
在基础版的MvpCallback接口中onSuccess(String data)方法,由于String类型限制了数据类型,所以我们新增Callback接口,采用泛型的方法,使调用者自己去规定接收的数据类型。
package com.example.mvpapplication;
public interface Callback<T> {
/**
* 数据请求成功
* @param data 请求到的数据
*/
void onSuccess(T data);
/**
* 使用网络API接口请求方式时,虽然已经请求成功但是由
* 于{@code msg}的原因无法正常返回数据。
*/
void onFailure(String msg);
/**
* 请求数据失败,指在请求网络API接口请求方式时,出现无法联网、
* 缺少权限,内存泄露等原因导致无法连接到请求数据源。
*/
void onError();
/**
* 当请求数据结束时,无论请求结果是成功,失败或是抛出异常都会执行此方法给用户做处理,通常做网络
* 请求时可以在此处隐藏“正在加载”的等待控件
*/
void onComplete();
}
2.新增BaseView接口
View接口中定义Activity的UI逻辑。因为有很多方法几乎在每个Activity中都会用到,例如显示和隐藏正在加载进度条,显示Toast提示等,索性将这些方法变成通用的。
package com.example.mvpapplication;
import android.content.Context;
public interface BaseView {
/**
* 显示正在加载view
*/
void showLoading();
/**
* 关闭正在加载view
*/
void hideLoading();
/**
* 显示提示
* @param msg
*/
void showToast(String msg);
/**
* 显示请求错误提示
*/
void showErr();
}
3.新增BasePresenter类
Presenter类主要是中间层,负责与Model和View打交道,由于上面我们已经定义好了BaseView,所以我们希望BasePresenter中View都是BaseView。
package com.example.mvpapplication;
public class BasePresenter <V extends BaseView>{
/**
* 绑定的view
*/
private V mvpView;
/**
* 获取连接的view
*/
public V getView(){
return mvpView;
}
/**
* 绑定view,一般在初始化中调用该方法
*/
public void attachView(V mvpView) {
this.mvpView = mvpView;
}
/**
* 断开view,一般在onDestroy中调用
*/
public void detachView() {
this.mvpView = null;
}
/**
* 是否与View建立连接
* 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接
*/
public boolean isViewAttached(){
return mvpView != null;
}
}
4.新增BaseActivity
BaseActivity主要是负责实现 BaseView 中通用的UI逻辑方法,如此这些通用的方法就不用每个Activity都要去实现一遍了。
package com.example.mvpapplication;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
public abstract class BaseActivity extends Activity implements BaseView {
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setCancelable(false);
}
@Override
public void showLoading() {
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
@Override
public void hideLoading() {
if (mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void showErr() {
showToast("数据错误");
}
}
5.MvpCallback修改
package com.example.mvpapplication;
import com.example.mvpapplication.base.Callback;
/**
* callback负责presenter和model交互
* 相当于:中介和房东交互接口
*/
public interface MvpCallback extends Callback<String> {}
由于MvpCallback extends Callback<String>,所以指定了该业务回调数据类型为String类型。
6.MvpView的修改
package com.example.mvpapplication;
import com.example.mvpapplication.base.BaseView;
/**
* 这里相当于找房子的自己--对UI进行操作的接口
*/
public interface MvpView extends BaseView {
/**
* 展示数据
* @param data
*/
void showData(String data);
}
在MvpView中只用声明该业务特有的功能即可,因为其他通用功能已经在BaseView中声明好了。
7.MvpPresenter修改
package com.example.mvpapplication;
import com.example.mvpapplication.base.BasePresenter;
/**
* 这里相当于中介的作用
*/
public class MvpPresenter extends BasePresenter<MvpView> {
//因为中介要和雇主打交道,所以要声明MvpView
private MvpView mView;
public MvpPresenter(){}
public MvpPresenter(MvpView view){
this.mView = view;
}
/**
* 相当于帮我找房子--请求数据
* @param param
*/
public void getData(String param){
if (!isViewAttached())
return;
//显示正在加载进度条
getView().showLoading();
//调用Model请求数据
MvpModel.getNetData(param, new MvpCallback(){
@Override
public void onSuccess(String data) {
//调用view接口显示数据
if (isViewAttached()){
getView().showData(data);
}
}
@Override
public void onFailure(String msg) {
if (isViewAttached()){
getView().showToast(msg);
}
}
@Override
public void onError() {
if (isViewAttached()){
getView().showErr();
}
}
@Override
public void onComplete() {
if (isViewAttached()){
getView().hideLoading();
}
}
});
}
}
该类中只用实现getData方法即可,因为相关生命周期的绑定在BasePresenter中已经实现了。注意一点就是要指定BasePresenter的泛型参数类型为MvpView类型。
8.MianActivity修改
package com.example.mvpapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.example.mvpapplication.base.BaseActivity;
public class MainActivity extends BaseActivity implements MvpView{
//进度条
ProgressDialog progressDialog;
TextView text;
//找到中介
MvpPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = findViewById(R.id.text);
//初始化进度条
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在加载中...");
//初始化presenter
presenter = new MvpPresenter();
//绑定View
presenter.attachView(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//断开引用
presenter.detachView();
}
/**
* button事件
*/
public void getData(View view){
presenter.getData("normal");
}
public void getDataForFailure(View view){
presenter.getData("failure");
}
public void getDataForError(View view){
presenter.getData("error");
}
@Override
public void showData(String data) {
text.setText(data);
}
}
到此为止,进阶版MVP框架已经改造完成了!可以看出代码复用技术虽然增加了代码量,但是使结构非常清晰,维护也更加轻松。