概述
MVC与MVP的概念及优缺点我们心中大概应该都有个了解。MVC基本上把所有的逻辑实现都放到界面代码里了,这样的好处就是没那么多的拐弯抹角,逻辑简单的界面完全适用。但是在我们日常开发当中,总会出现这种情况,随着功能改进及版本迭代,界面(Fragment或Activity)代码越来越多。这个时候出问题了想定位可能就没那么简单,要是多人协作就更蛋疼,只因各模块间各种耦合。下面我们将会看看MVP(Model-View-Presenter)是怎么解耦的,先从最基础的方式入手,分析存在的问题。然后针对性地解决。
简单使用
我们就以Activity进行网络加载数据回调为例,先创建一个简单的Retrofit网络加载工具:
public interface RetrofitRequestInterface {
@GET
Call getRequest(@Url String url);
@GET
Call getRequestWithToken(@Header("token") String token, @Url String url);
@POST
@Headers("content-type: application/json")
Call postRequest(@Url String url, @Body String param);
@POST
@Headers("content-type: application/json")
Call postRequestWithToken(@Header("token") String token, @Url String url, @Body String param);
@Multipart
@POST
Call uploadMemberIcon(@Header("token") String token, @Url String url, @PartMap Map params, @Part MultipartBody.Part file);
@POST
@Multipart
Call uploadECGraph(@Header("token") String token, @Url String url, @Part MultipartBody.Part file);
}
/*******
* Retrofit基于OkHttp。
* 同时提供网络响应数据转换。
*
* *********/
public class RetrofitInstance {
private volatile static Retrofit retrofit;
private static final int READ_TIME_OUT = 20;
private static final int CONNECT_TIME_OUT = 20;
private static final int WRITE_TIME_OUT = 20;
private static final String CACHE_NAME = "cache";
private static boolean DEBUG_MODE = true;
public static Retrofit getRetrofitInstance() {
final String baseUrl = "" + "/";
if (retrofit == null) {
synchronized (RetrofitInstance.class) {
if (retrofit == null) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
if (DEBUG_MODE) {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
} else {
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
}
File cacheFile = new File(BaseApplication.applicationContext.getExternalCacheDir(), CACHE_NAME);
Cache cache = new Cache(cacheFile,1024 * 1024 * 20);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.addInterceptor(cacheControlInterceptor)
.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(true);
retrofit = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
.client(builder.build())
.build();
}
}
}
return retrofit;
}
private static final Interceptor cacheControlInterceptor = new Interceptor() { //缓存设置
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetAvailable(BaseApplication.applicationContext)) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response originalResponse = chain.proceed(request);
if (isNetAvailable(BaseApplication.applicationContext)) {
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
int maxStale = 60 * 60 * 24 * 7;
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
}
};
public static boolean isNetAvailable(Context context) {
ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
return true;
} else {
return false;
}
}
}
上面先写一个 Retrofit网络加载模块。这不是重点,我们接下来继续完成一个最简单的MVP(Model-View-Presenter)样式。
- Model
我们先创建一个 Model数据加载协议:
public interface IModel {
// 获取用于网络加载的 Call对象
Call getData(String s);
}
上面定义了接口,下面 Model模块实现它:
public class DataModel implements IModel {
@Override
public Call getData(String s) {
// 调用 Retrofit,返回 Call对象
Call call = RetrofitInstance.getRetrofitInstance().create(RetrofitRequestInterface.class).getRequest(s);
return call;
}
}
- View
下面先定义 View的规范:
public interface IView {
// 加载成功
void onDataSuccess(String s);
// 加载失败
void onDataError();
// 加载中
void onDataDownloading();
}
上面定义了View的数据回调规范,也就是Activity或Fragment会实现它,这是后话。
- Presenter
来看看这个解耦关联器:
public interface IPresenter {
void getData(String s);
}
// 持有 Model模块及 View模块的引用
public class Presenter implements IPresenter {
private IModel mModel;
private IView mView;
public Presenter(IView iView){
this.mModel = new DataModel();
mView = iView;
}
@Override
public void getData(String s) {
Call call = this.mModel.getData(s);
// 加载中
mView.onDataDownloading();
// 子线程,其实网络加载也可以放在 Model模块,再用接口回调
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
try {
String result = response.body().string();
// 加载成功,回调
mView.onDataSuccess(result);
} catch (IOException e) {
e.printStackTrace();
// 加载错误,回调
mView.onDataError();
}
}
@Override
public void onFailure(Call call, Throwable t) {
// 加载失败,回调
mView.onDataError();
}
});
}
}
上面就是Presenter 模块。作为中间模块,它持有Model及View模块的引用,在 getData() 方法里执行网络请求,完了回调数据。
下面再看Activity:
public class MvpDataActivity extends AppCompatActivity implements IView {
private static final String TAG = "MvpDataActivity";
private TextView mvpText;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_mvp);
init();
}
private void init(){
// 创建 P模块并发起网络请求
Presenter presenter = new Presenter(this);
presenter.getData("www.baidu.com");
}
@Override
public void onDataSuccess(String s) {
// 加载成功 ,回调
// show();
}
@Override
public void onDataError() {
// 加载失败 ,回调
// show();
}
@Override
public void onDataDownloading() {
// 加载中 ,回调
// show();
}
}
实现了 IView 接口,分别实现了3 个回调方法。上面 init()方法创建了 P模块,把当前 Activity作为 V模块给 P持有。
咋一看似乎比 MVC简单不少?我信你个鬼!因为搞了个MVP多创建了好几个类。咋一看又不划算了。其实客观地讲,无论是 MVP还是 MVC都有其市场,都有其特定的使用场景。在逻辑比较复杂的应用或者界面下使用 MVP确实清爽很多,定位问题也可以按模块来。那么 MVP模式的结构大概是下面这样的:
上面就是 MVP模式的最简单形式了。下面我们来考虑这样的写法存在的几个问题,然后解决一下。
1、数据回调时页面已经退出了
网络加载需要时间,当我们的数据回调的时候,用户可能已经退出页面了。这样的话再更新 UI就蛋疼了。那咋办呢?退出的时候解绑吧:
public interface IPresenter {
void getData(String s);
// 解绑
void detach();
}
//
public class Presenter implements IPresenter {
private IModel mModel;
private IView mView;
public Presenter(IView iView){
this.mModel = new DataModel();
this.mView = iView;
}
@Override
public void detach() { // 解绑
this.mView = null;
}
@Override
public void getData(String s) {
Call call = this.mModel.getData(s);
// 加载中
if (mView == null) return;
mView.onDataDownloading();
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
try {
String result = response.body().string();
// 加载成功
if (mView == null) return;
mView.onDataSuccess(result);
} catch (IOException e) {
e.printStackTrace();
// 加载错误
if (mView == null) return;
mView.onDataError();
}
}
@Override
public void onFailure(Call call, Throwable t) {
// 加载失败
if (mView == null) return;
mView.onDataError();
}
});
}
}
上面给 Presenter 加了个 detach()方法,将 mView 模块置空。数据回调时进行判空一下,然后在 Activity 的 onDestroy()方法里调用 detach()解绑就好了。
- 2、每创建一个 Activity就要在该 Activity里创建一个 Presenter对象岂不是很麻烦?
我们一个应用可能会有很多个 Activity,那么每个 页面都要去创建一个 Presenter 对象,还要绑定、解绑是不是很麻烦呢?有没有方法可以优化呢?下面我们来使用一下Base模板设计模式,看看效果。我们先创建一个BaseActivity,实现 IView接口:
public abstract class BaseMvpActivity> extends AppCompatActivity implements IView {
private P mPresenter;
// 获取 mPresenter
public P getPresenter() {
return mPresenter;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView();
initView();
initData();
// P 是泛型,只能交由子类创建
mPresenter = createP();
// 绑定
mPresenter.attach(this);
}
// 子类实现创建 Presenter对象的方法
protected abstract P createP();
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑
mPresenter.detach();
}
abstract void setContentView();
abstract void initView();
abstract void initData();
}
上面我们看到,这里我们在 BaseMvpActivity对 Presenter进行了初始化、绑定及解绑的的工作。这样其他 Activity只要继承了 BaseMvpActivity之后,就不需要进行这些繁琐的操作了。当然,上面也定义了几个抽象方法需要子类实现。下面看一下 BaseMvpActivity的子类怎么实现:
public class MvpDataActivity extends BaseMvpActivity {
private static final String TAG = "MvpDataActivity";
private TextView mvpText;
@Override
protected Presenter createP() {
return new Presenter();
}
@Override
void setContentView() {
Log.d(TAG, "----------setContentView");
setContentView(R.layout.activity_mvp);
}
@Override
void initView() {
Log.d(TAG, "-----------initView");
mvpText = findViewById(R.id.id_mvp);
}
@Override
void initData() {
Log.d(TAG, "-----------initData");
mvpText.setText("碧云天");
}
public void onClick(View view) {
getPresenter().getData("");
}
@Override
public void onDataSuccess(String s) {
Log.d(TAG, "-----------onDataSuccess");
}
@Override
public void onDataError() {
Log.d(TAG, "-----------onDataError");
}
@Override
public void onDataDownloading() {
Log.d(TAG, "-----------onDataDownloading");
}
}
把 Presenter初始化的工作交给了模板父类 BaseMvpActivity,这样界面代码逻辑是不是又少了一点了?不仅如此,Base模板里我们用了泛型 P,这样的话不同的子类 Activity就可以使用不同形式的 Presenter子类。其实 Presenter模块也可以使用模板来初始化 View模块,使得子类 Presenter更简洁,这里同理就不再讲了。
Demo:MVP