Retrofit+RxJava实现MVP模式

在软件开发中,开发一个新的项目之前,我们肯定会先对整个项目搭建一个框架,以便于提高开发效率,统一代码风格,简化维护成本。当然,Android开发也一样。对于Android而言,普遍化的实现方式可能就是组合开源库,一般包括网络框架(OkHttp, Retrofit, Vollay等),
数据传输协议(Gson, Json, ProtocolBuf等),图片加载框架(ImageLoader, Picasco, Fresco, Glide等),事件通知组件(EventBus, RxJava等),然后通过二次封装来完成。

关于MVP模式可参见上一篇文章:

Android MVP架构解读

这里我们通过Rxtrofit+RxJava+Gson组合的形式搭建一个简单的MVP模式的框架。其基本结构如下:

下面我们先通过Rxtrofit+RxJava对网络层进行二次封装,以简化用户的数据请求过程。

这里我们就按照MVP的三层接口来进行封装,将网络层归档到Model层中。

Model层

采用的数据传输格式:

{
    "code": 1,
    "msg": "\u64cd\u4f5c\u6210\u529f",
    "data": []
}

code: 表示服务器的返回代码(如1表示成功,0表示失败等);

msg: 表示服务器对客户端返回的消息;

data: 存储服务器返回给客户端的数据;

代码实现:

数据模型的基类(实现Model Gson化,便于调试与数据转化):

public abstract class BaseBean {
    public String toJson() {
        return toJson(this);
    }

    public static String toJson(BaseBean bean) {
        if (bean != null) {
            return new Gson().toJson(bean);
        }

        return null;
    }

    public static <T extends BaseBean> T fromJson(String jsonStr,
            Class<? extends BaseBean> subClass) {
        BaseBean newObj = new Gson().fromJson(jsonStr,
                subClass);

        return (T) newObj;

    }

    public String toString() {
        return toJson();
    }
}

数据协议的基类:

public class BaseProtocolBean extends BaseBean implements Serializable {

    /**
     * 服务器的返回代码
     */
    public int code;

    /**
     * 服务器的返回消息
     */
    public String msg;
}

具体接口使用的数据模型示例:

public class ExampleBean extends BaseProtocolBean {

    public ExampleDetailBean data;

    public static class ExampleDetailBean extends BaseBean {

        // 具体的字段定义
    }
}

至此,数据协议部分我们就完成了,在使用时,我们只需要按照上面的使用示例,对Model进行定义,然后在网络请求中使用即可。下面我们通过Retrofit+RxJava对网络层进行封装。(这里我们只对异步请求过程进行封装)

数据请求过程:

public class BaseHttpQuery<T extends BaseProtocolBean> { 

    private BasePresenter mPresenter;
    private APIService apiService;
    private final int ERROR_CODE_DEFAULT = -1;
    private final String BASE_URL = "http://192.168.1.1/";

    public BaseHttpQuery(BasePresenter presenter) {
        this.mPresenter = presenter;
    }

    public APIService getAPIService() {
        if (apiService == null) {
                   Retrofit retrofit = new Retrofit.Builder()
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(BASE_URL)
                    .build();
            apiService = retrofit.create(APIService.class);
        }

        return apiService;
    }

    public void setObservable(Observable observable) {
        if (observable != null && mPresenter != null) {
            observable.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Observer<T>() {
                        @Override
                        public void onNext(T value) {
                                if (value != null) {
                                    if (value.code == 1) {
                                        mPresenter.onSuccess(value);
                                    } else {
                                        mPresenter.onFailure(value.code, value.msg);
                                    }
                                } else {
                                    mPresenter.onFailure(ERROR_CODE_DEFAULT, "数据解析失败");
                                }
                        }

                        @Override
                        public void onError(Throwable e) {
                            mPresenter.onFailure(ERROR_CODE_DEFAULT, e.getMessage());
                        }

                        @Override
                        public void onCompleted() {

                        }
                    });
        }
    }
}

通过传入的Presenter, 在网络请求完成后,回调Presenter层相应的方法(onSuccess或onFailure), 这样用户只需要在Presenter中这两个方法中处理相应的逻辑即可。

实际上,目前市场上的App,为了统计数据,一般都需要App上传手机的一些基本参数作为每个接口的公共信息,既然OKHttp可以办到,那么作为OkHttp的封装框架自然也提供了这样的功能。Retrofit可以设置自定义的okhttp客户端,所以我们只需要自定义一个拦截器, 将这些公共参数拼接在请求中, 然后设置Retrofit的OKHttpClient即可:

在请求中拼接公共参数

public class BaseInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl.Builder builder = request.url()
                .newBuilder()
                .scheme(request.url().scheme())
                .host(request.url().host())
                .addQueryParameter("appver", "")
                .addQueryParameter("sysver", "android")
                .addQueryParameter("channel", "")
                .addQueryParameter("carrier", "")
                .addQueryParameter("network", "")
                .addQueryParameter("res", "")
                .addQueryParameter("did", "")
                .addQueryParameter("mac", "")
                .addQueryParameter("token", "");

        Request newRquest = request.newBuilder()
                .method(request.method(), request.body())
                .url(builder.build())
                .build();

        return chain.proceed(newRquest);
    }
}

然后在创建Retrofit对象时,设置新的OkHttpClient:

OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(new BaseInterceptor()).build();
Retrofit retrofit = new Retrofit.Builder()
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .client(httpClient)
    .baseUrl(BASE_URL)
    .build();

这样在网络请求时每个接口都会携带公共参数了。至此,整个Model层和网络层就已完成。

Presenter层

Presenter的基本结构

public interface BasePresenter<T> {

    /**
     * request data from remote or database.
     */
    void requestData();

    /**
     * callback onSuccess when request data success.
     * @param data
     */
    void onSuccess(T data);

    /**
     * callback onFailure when request data fail.
     *
     * @param code: error code.
     * @param msg: error message.
     */
    void onFailure(int code, String msg);
}

Presenter层的使用示例:

public class TestPresenter<T extends BaseView> implements BasePresenter<DesignerListBean> {

    private T mBaseView;

    public TestPresenter(T baseView) {
        requestData();
        this.mBaseView = baseView;
    }

    @Override
    public void requestData() {
          // 请求数据的过程
        BaseHttpQuery<DesignerListBean> query = new BaseHttpQuery<DesignerListBean>(this);
        query.setObservable(query.getAPIService().getData("1");
    }

    @Override
    public void onSuccess(DesignerListBean data) {
        // 数据请求成功后调用View(Fragment/Activity中的loadView获取View,init(data)加载数据等)
        mBaseView.loadView();
        mBaseView.init(data);
    }

    @Override
    public void onFailure(int code, String msg) {
        // 数据请求失败后加载相应的布局
        mBaseView.showFailureUI();
    }

    // shixian
    ...
}

View层

为了提供开发效率,统一代码风格,我们对View层也进行简单的封装。

BaseView

public interface BaseView {
/**

 * set presenter makes it associated with activity/fragment,
 * and request data from remote or database.
 */
void startPresenter();

/**
 * 获取布局文件 example: return R.layout.test_layout;
 */
int getLayoutId();

/**
 * 获取布局中的View
 */
void getViews();

/**
 * 为View关联数据,设置View事件
 *
 * @param data
 */
void init(T data);

/**
 * 网络请求失败时加载相应的布局
 */
void showFailureUI();

/**
 * 数据请求成功后被调用:加载布局,获取View
 * /
void loadView();
/**
 * 显示提醒消息
 * @param hint
 */
void showHint(String hint);

}

BaseActivity

public abstract class BaseActivity<T extends BasePresenter, G extends BaseBean> extends Activity implements BaseView<G> {

    // 请求网络时是否显示加载布局,true表示显示,否则不显示
    protected boolean showLoadingUI = true;
    // 页面是否需要网络请求 true表示需要,否则不需要
    protected boolean needRequest = true;
    protected T mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startPresenter();
        if (needRequest) {
            if (showLoadingUI) {
                setContentView(R.layout.activity_loading_layout);
            } else {
                setContentView(getLayoutId());
            }
        } else {
            setContentView(getLayoutId());
            getViews();
            init(null);
        }
    }

    @Override
    public void loadView() {
        if (showLoadingUI) {
            setContentView(getLayoutId());
        }
        getViews();
    }

    @Override
    public void showFailureUI() {
        View emptyView = getLayoutInflater().inflate(R.layout.activity_empty_layout, null);
        setContentView(emptyView);
        emptyView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.requestData();
            }
        });
    }

    @Override
    public void showHint(String hint) {
        ToastUtil.show(this, hint);
    }
}

当然,作为View层的Fragment也应该进行简单的封装:

BaseFragment

public abstract class BaseProgressFragment2<T extends BasePresenter, G extends BaseBean> extends Fragment implements BaseView<G> {

    protected View mRootLayout;
    protected T mPresenter;
    private FrameLayout mViewContaner;
    private LayoutInflater mInflater;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        startPresenter();
        this.mInflater = inflater;
        View loadingView = inflater.inflate(R.layout.fragment_loading_layout, container, false);
        mViewContaner = (FrameLayout) loadingView.findViewById(R.id.content_container);
        return loadingView;
    }

    @Override
    public void loadView() {
        mViewContaner.removeAllViews();
        mRootLayout = mInflater.inflate(getLayoutId(), null);
        mViewContaner.addView(mRootLayout);
        getViews();
    }

    public void showFailureUI() {
        mViewContaner.removeAllViews();
        View emptyLayout = mInflater.inflate(R.layout.fragment_loading_layout, null);
        mViewContaner.addView(emptyLayout);
        emptyLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPresenter != null) {
                    mPresenter.requestData();
                }
            }
        });
    }

    @Override
    public void showHint(String hint) {
        ToastUtil.show(getActivity(), hint);
    }
}

至此,View层的封装就完成了。在页面开发中,我们只需要继承BaseActivity或BaseFragment实现相应的函数即可。

总结

这里我们只是实现了Retrofit+RxJava实现了一个MVP的基本框架,其目的是展现如何将MVP的架构应用在项目中。对于不同的需求,不同的业务,还是需要做出相应的调整。对于其中存在的问题,欢迎指正!

,