面试理论

MVP基础架构搭建浅析+模板设计模式

2021-07-27  本文已影响0人  碧云天EthanLee
概述

MVC与MVP的概念及优缺点我们心中大概应该都有个了解。MVC基本上把所有的逻辑实现都放到界面代码里了,这样的好处就是没那么多的拐弯抹角,逻辑简单的界面完全适用。但是在我们日常开发当中,总会出现这种情况,随着功能改进及版本迭代,界面(Fragment或Activity)代码越来越多。这个时候出问题了想定位可能就没那么简单,要是多人协作就更蛋疼,只因各模块间各种耦合。下面我们将会看看MVP(Model-View-Presenter)是怎么解耦的,先从最基础的方式入手,分析存在的问题。然后针对性地解决。

简单使用

我们就以Activity进行网络加载数据回调为例,先创建一个简单的Retrofit网络加载工具:

public interface RetrofitRequestInterface {
    @GET
    Call<ResponseBody> getRequest(@Url String url);

    @GET
    Call<ResponseBody> getRequestWithToken(@Header("token") String token, @Url String url);

    @POST
    @Headers("content-type: application/json")
    Call<ResponseBody> postRequest(@Url String url, @Body String param);

    @POST
    @Headers("content-type: application/json")
    Call<ResponseBody> postRequestWithToken(@Header("token") String token, @Url String url, @Body String param);

    @Multipart
    @POST
    Call<ResponseBody> uploadMemberIcon(@Header("token") String token, @Url String url, @PartMap Map<String, RequestBody> params, @Part MultipartBody.Part file);

    @POST
    @Multipart
    Call<ResponseBody> 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)样式。

public interface IModel {
   // 获取用于网络加载的 Call对象
    Call<ResponseBody> getData(String s);
}

上面定义了接口,下面 Model模块实现它:

public class DataModel implements IModel {

    @Override
    public Call<ResponseBody> getData(String s) {
        // 调用 Retrofit,返回 Call对象
        Call<ResponseBody> call =  RetrofitInstance.getRetrofitInstance().create(RetrofitRequestInterface.class).getRequest(s);
        return call;
    }
}
public interface IView {
    // 加载成功
    void onDataSuccess(String s);
   // 加载失败
    void onDataError();
  // 加载中
    void onDataDownloading();
}

上面定义了View的数据回调规范,也就是Activity或Fragment会实现它,这是后话。

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<ResponseBody> call = this.mModel.getData(s);
        // 加载中
        mView.onDataDownloading();
        // 子线程,其实网络加载也可以放在 Model模块,再用接口回调
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    String result = response.body().string();
                    // 加载成功,回调
                    mView.onDataSuccess(result);
                } catch (IOException e) {
                    e.printStackTrace();
                    // 加载错误,回调
                    mView.onDataError();
                }
            }
            @Override
            public void onFailure(Call<ResponseBody> 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.png

上面就是 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<ResponseBody> call = this.mModel.getData(s);
        // 加载中
        if (mView == null) return;
        mView.onDataDownloading();
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> 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<ResponseBody> call, Throwable t) {
                // 加载失败
                if (mView == null) return;
                mView.onDataError();
            }
        });
    }
}

上面给 Presenter 加了个 detach()方法,将 mView 模块置空。数据回调时进行判空一下,然后在 Activity 的 onDestroy()方法里调用 detach()解绑就好了。

public abstract class BaseMvpActivity<P extends BasePresenter<IView>> 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<Presenter> {
    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

上一篇下一篇

猜你喜欢

热点阅读