Rx-Mvp

Project Url: RuffianZhong/Rx-Mvp
Introduction: RxJava2+Retrofit2+RxLifecycle2 使用 MVP 模式构建项目
More: Author   ReportBugs   
Tags:
retrofit2-rxjava2-rxlifecycle-mvp-

Rx-Mvp 项目包含两个部分功能/模块

RHttp : 基于 RxJava2 + Retrofit2 + OkHttp3 + RxLifecycle2 框架整合封装的网络请求框架

MVP : MVC 框架的升级版本,通过 Presenter 桥梁连接 View 和 Model,使得模块之间更好的解耦

app : Demo 代码,示例如何使用 MVP 架构项目;示例如何使用 RHttp 进行网络数据请求

RHttp

RHttp 是基于 RxJava2 + Retrofit2 + OkHttp3 + RxLifecycle2 框架整合封装的网络请求框架

  • 基本的 get、post、put、delete、4 种请求
  • 单/多文件上传
  • 断点续传下载
  • 基本回调包含 onSuccess、onError、onCancel、onProgress(上传/下载带进度)
  • 支持自定义 Callback
  • 支持 https
  • 支持 tag 取消,支持取消全部请求
  • 支持绑定组件生命周期自动管理网络请求
  • 支持链式调用
  • 支持表单格式,String,json 格式数据提交请求

RHttp 很屌吗?算不上,基本满足应用开发的所有需求,代码很简洁,体积小,功能齐全。这就够了

  1. 基于主流网络框架 Retrofit2 + OkHttp3 作为底层框架
  2. 使用 RxJava2 处理异步事件,线程间切换逻辑
  3. 使用 RxLifecycle2 管理生命周期,再也不用担心 RxJava 使用不当造成内存泄漏
  4. 基础网络请求,数据转换为 String 并提供 onConvert 方法提供开发者自定义转化数据
  5. 上传带进度回调,多文件情况下,可以区分当前文件下标
  6. 断点续传大文件,多种下载状态满足不同下载需求
RHttp-初始化(全局配置)
        //初始化 RHttp
        RHttp.Configure.get().init(this);


        //初始化 RHttp(全局配置)
        RHttp.Configure.get()
                .timeout(30)//请求超时时间
                .timeUnit(TimeUnit.SECONDS)//超时时间单位
                .baseHeader(new HashMap<String, Object>())//全局基础 header,所有请求会包含
                .baseParameter(new HashMap<String, Object>())//全局基础参数,所有请求会包含
                .baseUrl("http://com.ruffian.http.cn")//基础 URL
                .showLog(true)//是否显示调试 log
                .init(this);//初始化,必须调用
RHttp-简单使用
        new RHttp.Builder()
                .post()                     //请求方式
                .apiUrl("user/login")       //接口地址
                .addParameter(parameter)    //参数
                .addHeader(header)          //请求头
                .lifecycle(this)            //自动管理生命周期,可以不传,如果未及时取消 RxJava 可能内存泄漏
                .build()
                .execute(new RHttpCallback<UserBean>() {
                    @Override
                    public UserBean onConvert(String data) {
                        /*数据解析转换*/
                        //String 转为 Response(自定义)
                        Response response = new Gson().fromJson(data, Response.class);
                        // Response 转为 JavaBean(目标对象)
                        UserBean userBean = new Gson().fromJson(response.getResult(), UserBean.class);
                        return userBean;
                    }

                    @Override
                    public void onSuccess(UserBean value) {
                        //do sth.
                    }

                    @Override
                    public void onError(int code, String desc) {
                        //do sth.
                    }

                    @Override
                    public void onCancel() {
                          //do sth.
                    }
                });


        RHttp http = new RHttp.Builder()
                .tag("login_request")//设置请求 tag,以便后续根据 tag 取消请求
                .build();//构建 http 对象

        http.execute(new RHttpCallback() {});//执行请求(get/post.delete/put)
        http.execute(new RUploadCallback() {});//执行请求(文件上传)
        http.cancel();//取消当前网络请求
        http.isCanceled();//当前网络请求是否已经取消

        //静态方法
        RHttp.cancel("login_request");//根据 tag 取消指定网络请求
        RHttp.cancelAll();//取消所有网络请求

RHttp 使用 demo 文档

RHttp 详解文档

MVP

MVP 是 MVC 框架的升级版本,通过 Presenter 桥梁连接 View 和 Model,使得模块之间更好的解耦

MVP -> P

Presenter 桥梁连接 View 和 Model,使得模块之间更好的解耦。 主要职责绑定/解绑 View/销毁时释放资源 通过动态代理方式解决 getView 判空和容错问题

1.Presenter 基类定义


/**
 * MVP  根 Presenter
 */
public interface IMvpPresenter<V extends IMvpView> {

    /**
     * 将 View 添加到当前 Presenter
     */
    @UiThread
    void attachView(@NonNull V view);

    /**
     * 将 View 从 Presenter 移除
     */
    @UiThread
    void detachView();

    /**
     * 销毁 V 实例
     */
    @UiThread
    void destroy();

}


/**
 * Presenter 基础实现
 *
 * @param <V>
 */
public abstract class MvpPresenter<V extends IMvpView> implements IMvpPresenter<V> {

    protected V mView;

    //View 代理对象
    protected MvpViewProxy<V> mMvpViewProxy;

    /**
     * 获取 view
     *
     * @return
     */
    @UiThread
    public V getView() {
        return mView;
    }

    /**
     * 判断 View 是否已经添加
     *
     * @return
     */
    @UiThread
    public boolean isViewAttached() {
        return mView != null;
    }

    /**
     * 绑定 View
     *
     * @param view
     */
    @UiThread
    @Override
    public void attachView(V view) {
        mMvpViewProxy = new MvpViewProxy<V>();
        mView = (V) mMvpViewProxy.newProxyInstance(view);
    }

    /**
     * 移除 View
     */
    @Override
    public void detachView() {
        if (mMvpViewProxy != null) {
            mMvpViewProxy.detachView();
        }
    }

}

2.Presenter 使用

/**
 * 登录 Presenter
 * 备注:继承 MvpPresenter 指定 View 类型
 *
 * @author ZhongDaFeng
 */
public class LoginPresenter extends MvpPresenter<AccountContract.ILoginView> {

    /**
     * 登录
     */
    public void login(String userName, String password) {

        //显示 loading 框
        getView().showProgressView();

        //调用 model 获取网络数据
        ModelFactory.getModel(AccountModel.class).login(getView().getActivity(), userName, password, getView().getRxLifecycle(), new ModelCallback.Http<UserBean>() {
            @Override
            public void onSuccess(UserBean data) {
                //model 数据回传

                //关闭弹窗
                getView().dismissProgressView();

                StringBuffer sb = new StringBuffer();
                sb.append("登录成功")
                        .append("\n")
                        .append("用户 ID:")
                        .append(data.getUid())
                        .append("\n")
                        .append("Token:")
                        .append("\n")
                        .append(data.getToken())
                        .append("\n")
                        .append("最后登录:")
                        .append("\n")
                        .append(data.getTime());

                //用户信息展示
                getView().showResult(sb.toString());

            }

            @Override
            public void onError(int code, String desc) {
                //model 数据回传

                //关闭弹窗
                getView().dismissProgressView();

                //错误信息提示
                getView().showError(code, desc);
            }

            @Override
            public void onCancel() {
                //关闭弹窗
                getView().dismissProgressView();
            }
        });

    }

    /**
     * 获取本地缓存数据
     */
    public void getLocalCache() {

        //调用 model 获取本地数据
        ModelFactory.getModel(AccountModel.class).getLocalCache(getView().getActivity(), getView().getRxLifecycle(), new ModelCallback.Data<String>() {
            @Override
            public void onSuccess(String object) {
                //model 数据回传

                //关闭弹窗
                getView().dismissProgressView();

                //用户信息展示
                getView().showResult(object);

            }
        });
    }

    @Override
    public void destroy() {
        //一些对象的释放
    }
}

MVP -> V

定义 View 接口在具体组件中实现,定义常用公用 View 方法,具体业务接口开发者自行定义

另外一套思想:MvpView 定义三个基础接口,具体组件实现
loading/data/error
1. lde 思想: 页面通用  加载中/展示数据/错误处理
2. action 方式: 考虑多个请求时 根据 action 区分处理

  void mvpLoading(String action, boolean show);
  <M> void mvpData(String action, M data);
  void mvpError(String action, int code, String msg);

作者最终放弃此方式,感兴趣查看分支 master.release.mvp_lde

    /**
     * IMvpView
     */
    public interface IMvpView {
    }


    /**
     * 基础 View 接口
     */
    public interface MvpView extends IMvpView {

        /**
         * RxLifecycle 用于绑定组件生命周期
         *
         * @return
         */
        LifecycleProvider getRxLifecycle();

        /**
         * 获取 Activity 实例
         *
         * @return
         */
        Activity getActivity();

        /**
         * 展示吐司
         *
         * @param msg 吐司文本
         */
        @UiThread
        void showToast(@NonNull String msg);

        /**
         * 显示进度 View
         */
        @UiThread
        void showProgressView();

        /**
         * 隐藏进度 View
         */
        @UiThread
        void dismissProgressView();

    }

     /**------------View 具体使用--------------**/

    /**
     * 登录 view
     * 备注: MvpView 未能满足需求时新增方法
     */
    interface ILoginView extends MvpView {

        /*登录成功展示结果*/
        @UiThread
        void showResult(String data);

        /*登录错误处理逻辑*/
        @UiThread
        void showError(int code, String msg);
    }

MVP -> M

Model 这里认为只负责获取和解析数据,再回调给 Presenter

    /**
     * MVP  根 Model
     * MvpModel 创建之后全局静态持有,因此不能持有短生命周期的对象,避免内存泄漏
     *
     * @author ZhongDaFeng
     */
    public interface IMvpModel {

    }


    /**
     * 模块数据回调接口
     *
     * @author ZhongDaFeng
     */
    public interface ModelCallback {

        /**
         * 网络数据回调,泛指 http
         *
         * @param <T>
         */
        interface Http<T> {

            public void onSuccess(T object);

            public void onError(int code, String desc);

            public void onCancel();
        }

        /**
         * 其他数据回调<本地数据,数据库等>
         *
         * @param <T>
         */
        interface Data<T> {

            public void onSuccess(T object);
        }

    }

     /**------------Model 具体使用--------------**/

    /**
     * 登录模块 model 接口.此处根据具体项目决定是否需要此接口层
     */
    interface LoginModel extends IMvpModel {
        /**
         * 用户密码登录
         *
         * @param lifecycle     组件生命周期
         * @param modelCallback model 回调接口(网络)
         */
        void login(final Context context, String userName, String password, LifecycleProvider lifecycle, ModelCallback.Http<UserBean> modelCallback);

        /**
         * 获取本地缓存数据
         *
         * @param modelCallback model 回调接口(普通数据)
         */
        void getLocalCache(Context context, LifecycleProvider lifecycle, ModelCallback.Data<String> modelCallback);

        /**
         * 缓存数据
         */
        void saveLocalCache(Context context, UserBean data);
    }

    /**
     * AccountModel
     *
     * @author ZhongDaFeng
     */
    public class AccountModel implements AccountContract.LoginModel {

        private final String key_user_cache = "key_user_info";

        @Override
        public void login(final Context context, String userName, String password, LifecycleProvider lifecycle, final ModelCallback.Http<UserBean> modelCallback) {

            //Biz 发起网络请求
            BizFactory.getBiz(UserBiz.class).login(userName, password, lifecycle, new RHttpCallback<UserBean>() {

                @Override
                public UserBean convert(JsonElement data) {
                    return new Gson().fromJson(data, UserBean.class);
                }

                @Override
                public void onSuccess(UserBean value) {

                    String time = new SimpleDateFormat("yyyy/mm/dd HH:mm:ss").format(System.currentTimeMillis());
                    value.setTime(time);
                    //回调给 Presenter
                    modelCallback.onSuccess(value);
                    //保存到本地数据
                    saveLocalCache(context, value);
                }

                @Override
                public void onError(int code, String desc) {
                    //回调给 Presenter
                    modelCallback.onError(code, desc);
                }

                @Override
                public void onCancel() {
                    //回调给 Presenter
                    modelCallback.onCancel();
                }
            });

        }

        @Override
        public void getLocalCache(final Context context, LifecycleProvider lifecycle, final ModelCallback.Data<String> modelCallback) {
            //RxJava 异步解析本地数据
            Observable.create(new ObservableOnSubscribe<String>() {
                @Override
                public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {

                    //模拟工作线程获取并解析数据
                    String userInfo = SpUtils.getSpUtils(context).getSPValue(key_user_cache, "");

                    e.onNext(userInfo);
                    e.onComplete();

                }
            }).subscribeOn(Schedulers.io())//工作线程
                    .observeOn(AndroidSchedulers.mainThread())
                    .compose(lifecycle.<String>bindToLifecycle())//绑定生命周期
                    .subscribe(new Observer<String>() {
                        @Override
                        public void onSubscribe(@NonNull Disposable d) {

                        }

                        @Override
                        public void onNext(@NonNull String userBean) {
                            modelCallback.onSuccess(userBean);  //回调给 Presenter
                        }

                        @Override
                        public void onError(@NonNull Throwable e) {
                            modelCallback.onSuccess("");
                        }

                        @Override
                        public void onComplete() {

                        }
                    });
        }

        @Override
        public void saveLocalCache(Context context, UserBean data) {
            StringBuffer sb = new StringBuffer();
            sb.append("用户 ID:")
                    .append(data.getUid())
                    .append("\n")
                    .append("Token:")
                    .append("\n")
                    .append(data.getToken())
                    .append("\n")
                    .append("最后登录:")
                    .append("\n")
                    .append(data.getTime());

            SpUtils.getSpUtils(context).putSPValue(key_user_cache, sb.toString());
        }
    }

MvpActivity/MvpFragment

public abstract class MvpActivity<V extends IMvpView, P extends IMvpPresenter<V>> extends RxActivity
 implements IMvpView, MvpDelegateCallback<V, P> {

    /**
     * 获取 Presenter 数组
     */
    protected abstract P[] getPresenterArray();
}
 P[] getPresenterArray() 返回 Presenter 数组,可用于一个 Activity 对应多个 Presenter 问题

Activity 使用

public class LoginActivity extends BaseActivity implements AccountContract.ILoginView {

    @BindView(R.id.et_user_name)
    EditText etUserName;
    @BindView(R.id.et_password)
    EditText etPassword;
    @BindView(R.id.tv_result)
    TextView tvResult;

    private LoginPresenter mLoginPresenter = new LoginPresenter();

    @Override
    protected int getContentViewId() {
        return R.layout.activity_login;
    }

    @Override
    protected void initBundleData() {
    }

    @Override
    protected void initView() {
    }

    @Override
    protected void initData() {
        //获取缓存数据
        mLoginPresenter.getLocalCache();
    }

    @OnClick({R.id.login})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.login:
                String userName = etUserName.getText().toString();
                String password = etPassword.getText().toString();
                if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(password)) {
                    return;
                }
                //登录
                mLoginPresenter.login(userName, password);
                break;
        }
    }

    @Override
    protected IMvpPresenter[] getPresenterArray() {
        return new IMvpPresenter[]{mLoginPresenter};
    }

    @Override
    public LifecycleProvider getRxLifecycle() {
        return this;
    }

    @Override
    public void showResult(String data) {
        tvResult.setText(data);
    }

    @Override
    public void showError(int code, String msg) {
        showToast(msg);
    }
}

MVP -> 文件过多?

这里为了解决 MVP 模式创建过多的接口类,引入 Contract(协议)

/**
 * Account 业务的 Contract(协议)
 * 目的:避免 mvp 架构 view/model 文件过多
 * 综合管理某业务的 view/model 接口
 *
 * @author ZhongDaFeng
 */
public interface AccountContract {

    /*登录模块 View 接口*/
    interface ILoginView extends MvpView {

        /*登录成功展示结果*/
        @UiThread
        void showResult(String data);

        /*登录错误处理逻辑*/
        @UiThread
        void showError(int code, String msg);
    }

    /*登录模块 model 接口.此处根据具体项目决定是否需要此接口层*/
    interface LoginModel extends IMvpModel {
        /**
         * 用户密码登录
         *
         * @param lifecycle     组件生命周期
         * @param modelCallback model 回调接口(网络)
         */
        void login(final Context context, String userName, String password, LifecycleProvider lifecycle, ModelCallback.Http<UserBean> modelCallback);

        /**
         * 获取本地缓存数据
         *
         * @param modelCallback model 回调接口(普通数据)
         */
        void getLocalCache(Context context, LifecycleProvider lifecycle, ModelCallback.Data<String> modelCallback);

        /**
         * 缓存数据
         */
        void saveLocalCache(Context context, UserBean data);
    }

}

注意事项

混淆代码

# OkHttp3
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**

# Okio
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }

# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

# Gson
-keep public class com.google.gson.**
-keep public class com.google.gson.** {public private protected *;}
-keepattributes Signature
-keepattributes *Annotation*
-keep public class com.project.mocha_patient.login.SignResponseData { private *; }

##特殊提醒

#API 接口不混淆
-keep class com.r.http.cn.api.**{*;}

#RHttp 实体不混淆
-keep class com.r.http.cn.model.**{*;}
#下载相关实体不混淆(如果有自定义下载实体的情况下)
#com.rx.mvp.cn.model.load.DownloadBean => you  package
-keep class com.rx.mvp.cn.model.load.DownloadBean { *; }

# LiteOrm (DB 库混淆配置,后续将会移除第三方 DB 库)
-keep public class com.litesuits.orm.LiteOrm { *; }
-keep public class com.litesuits.orm.db.* { *; }
-keep public class com.litesuits.orm.db.model.** { *; }
-keep public class com.litesuits.orm.db.annotation.** { *; }
-keep public class com.litesuits.orm.db.enums.** { *; }
-keep public class com.litesuits.orm.log.* { *; }
-keep public class com.litesuits.orm.db.assit.* { *; }

# 实体类解析字段使用 @SerializedName("XXX") 可以忽略,如果不是使用 @SerializedName 则自己的实体类不需要混淆
# 使用 Gson 时需要配置 Gson 的解析对象及变量都不混淆。不然 Gson 会找不到变量。
# 将下面替换成自己的实体类 com.rx.mvp.cn.model.account.entity => you  package
-keep class com.rx.mvp.cn.model.account.entity.** { *; }

Tips

   /**
     * 文档说明有限
     *
     * 强烈建议阅读代码,在此基础上改造成适用自己项目的框架
     *
     * 欢迎提供建议/意见,不断完善框架
     *
     * 喜欢就 star 吧,收获知识激励别人
     */

License

MIT License

Copyright (c) 2018 Ruffian-痞子
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools