zoukankan      html  css  js  c++  java
  • 一步步搭建Retrofit+RxJava+MVP网络请求框架(二),个人认为这次封装比较强大了

    在前面已经初步封装了一个MVP的网络请求框架,那只是个雏形,还有很多功能不完善,现在进一步进行封装。添加了网络请求时的等待框,retrofit中添加了日志打印拦截器,添加了token拦截器,并且对DataManager类进行了扩展,真正体现它的作用,并且对大量的重复代码做了一定封装,减少代码的冗余。

    下面结合上篇文章,进行下一步的封装。

    1、首先完善Result.java这个类。

    通常在我们写API接口文档的时候,后端返回的数据格式都是

    "code":1    //1:成功

                     //-1:token验证失败

    “msg”:”success”, //返回的消息提示

               “token验证失败”  

    “data”:   //数据

    {

    “username”:” xdw” ,  //用户名

    "age":30  //年龄

    }

    具体的Result.java的代码如下,里面还加入了一个对返回码的判断方法

    package com.xdw.retrofitrxmvpdemo.model;
    
    import com.xdw.retrofitrxmvpdemo.constant.Constant;
    
    /**
     * Created by 夏德旺 on 2017/12/8.
     */
    
    public class Result<T> {
        private int code;
        private String msg;
        private T data;
    
        public Result(int code, String msg, T data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
        //添加对返回状态成功的判断
        public boolean isSuccess() {
            return code == Constant.SUCCESS;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public T getData() {
            return data;
        }
    
    }

    2、添加ProgressDialogHandler和ProgressCancelListener,用来处理网络请求等待框。代码如下

    ProgressCancelListener:

    package com.xdw.retrofitrxmvpdemo.model;
    
    
    /**
    * Created by 夏德旺 on 2017/12/8.
    */
    public interface ProgressCancelListener {
    void onCancelProgress();
    }
     

    ProgressDialogHandler:

    package com.xdw.retrofitrxmvpdemo.http;
    
    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.os.Handler;
    import android.os.Message;
    
    /**
     * Created by 夏德旺 on 2017/12/8.
     */
    public class ProgressDialogHandler extends Handler {
    
        public static final int SHOW_PROGRESS_DIALOG = 1;
        public static final int DISMISS_PROGRESS_DIALOG = 2;
    
        private ProgressDialog pd;
    
        private Context context;
        private boolean cancelable;
        private boolean show;
        private ProgressCancelListener mProgressCancelListener;
    
        public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
                                     boolean cancelable,boolean show) {
            super();
            this.context = context;
            this.mProgressCancelListener = mProgressCancelListener;
            this.cancelable = cancelable;
            this.show = show;
        }
    
        private void initProgressDialog(){
            if (pd == null) {
                pd = new ProgressDialog(context);
    
                pd.setCancelable(cancelable);
    
                if (cancelable) {
                    pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            mProgressCancelListener.onCancelProgress();
                        }
                    });
                }
    
                if (!pd.isShowing()&&show) {
                    pd.show();
                }
            }
        }
    
        private void dismissProgressDialog(){
            if (pd != null) {
                pd.dismiss();
                pd = null;
            }
        }
    
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_PROGRESS_DIALOG:
                    initProgressDialog();
                    break;
                case DISMISS_PROGRESS_DIALOG:
                    dismissProgressDialog();
                    break;
            }
        }
    
    }

    3、改写RetrofitApiService,将返回结果由原来的UserInfo改为Result<UserInfo>。

    public interface RetrofitApiService {
    
        @GET("user")
        Observable<Result<UserInfo>> getUserInfo(@Query("uid") int uid);
    
    }

    4、完善之前的RetrofitUtil,加入日志与token拦截器,token这段我注释掉了,根据自己的实际项目进行添加

    package com.xdw.retrofitrxmvpdemo.http;
    
    import android.content.Context;
    
    import com.google.gson.GsonBuilder;
    import com.xdw.retrofitrxmvpdemo.BuildConfig;
    import com.xdw.retrofitrxmvpdemo.constant.UrlConstant;
    
    import java.io.IOException;
    
    import okhttp3.Interceptor;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    import okhttp3.logging.HttpLoggingInterceptor;
    import retrofit2.Retrofit;
    import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
    import retrofit2.converter.gson.GsonConverterFactory;
    
    /**
     * Created by 夏德旺 on 2017/12/8.
     */
    
    public class RetrofitUtil {
        private Context mCntext;
        //声明Retrofit对象
        private Retrofit mRetrofit;
        //声明RetrofitApiService对象
        private RetrofitApiService retrofitApiService;
        GsonConverterFactory factory = GsonConverterFactory.create(new GsonBuilder().create());
        //由于该对象会被频繁调用,采用单例模式,下面是一种线程安全模式的单例写法
        private volatile static RetrofitUtil instance;
    
        public static RetrofitUtil getInstance(Context context){
            if (instance == null) {
                synchronized (RetrofitUtil.class) {
                    if (instance == null) {
                        instance = new RetrofitUtil(context);
                    }
                }
            }
            return instance;
        }
        private RetrofitUtil(Context mContext){
            mCntext = mContext;
            init();
        }
    
        //初始化Retrofit
        private void init() {
    
            //添加token拦截
    /*        final String token = AppSPUtils.getValueFromPrefrences(AppConstants.SP_TOKEN, "");
            final int uid = AppSPUtils.getValueFromPrefrences(AppConstants.SP_USERID, 0);
            Interceptor mTokenInterceptor = new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request authorised = chain.request().newBuilder()
                            .addHeader("userId", String.valueOf(uid))
                            .addHeader("token", token)
                            .build();
                    return chain.proceed(authorised);
                }
            };*/
            //打印请求log日志
            OkHttpClient httpClient = new OkHttpClient();
            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
                httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                httpClient = new OkHttpClient.Builder()
                        .addInterceptor(httpLoggingInterceptor)
    //                    .addInterceptor(mTokenInterceptor)
                        .build();
            }
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(UrlConstant.BASE_URL)
                    .client(httpClient)
                    .addConverterFactory(factory)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
            retrofitApiService = mRetrofit.create(RetrofitApiService.class);
        }
    
        public RetrofitApiService getRetrofitApiService(){
            return retrofitApiService;
        }
    }

    5、重要的地方来了,完善之前的DataManager。之前这个类大家可以觉得非常鸡肋,因为它什么也没干,就是把RetrofitApiService和RetrofitUtil 该干的活移动到了这类中,非但没有减轻任务量,反而要多写一大堆重复代码。等现在封装之后就可以发现它的大作用了。

    我们现在在RetrofitApiService中的getUserInfo方法的返回值变成了Result<UserInfo>,但是实际上最后我们要的数据仅仅是UserInfo,这时可以对之前的DataManager中的getUserInfo方法修改下,如下

        public Observable<UserInfo>  getUserInfo(int uid){
            return mRetrofitService.getUserInfo(uid).map(new ResultFunc<UserInfo>() );
        }

    这里使用了rxjava中的map这个关键方法将数据进行了剥离出来,不懂这个方法的请自己去查阅rxjava的资料。

    在DataManager中同时定义了一个内部类,如下

     public class ResultFunc<T> implements Func1<Result<T>, T> {
            @Override
            public T call(Result<T> result) {
                //在这里对对服务端返回的resultCode进行判断,如果返回码不是成功,
                // 则抛出自定义的API异常,比如token登陆失败时。抛出异常的异常可以统一
                // 在Subscriber的子类ProgressSubscriber中进行处理,这样就不用到
                // 每个Activity中再来进行处理
                if (!result.isSuccess()) {
                    throw new APIException(result.getCode(), result.getMsg());
                }
                return result.getData();
            }
        }

    这里补充自己定义的一个异常类APIException的代码,如下

    package com.xdw.retrofitrxmvpdemo.util;
    
    /**
     * 自定义异常
     * Created by 夏德旺 on 2017/12/8.
     */
    public class APIException extends RuntimeException{
        public int code;
        public String message;
    
        public APIException(int code, String message) {
            this.code = code;
            this.message = message;
        }
    
        @Override
        public String getMessage() {
            return message;
        }
    
        public int getCode() {
            return code;
        }
    }

    DataManager的封装到此完成,具体它的应用请看后面的UserInfoPresenter中的调用

    6、在BasePresenter中添加一个addSubscription方法,这个是对每次的订阅进行封装,简化重复代码量

        //将每次的订阅操作进行封装,简化重复代码量
        public <T> void addSubscription(Observable<T> o, Subscriber<T> s) {
            mCompositeSubscription.add(o.unsubscribeOn(Schedulers.io())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(s));
    
        }


    7、再来看看具体处理业务逻辑的UserInfoPresenter的代码是不是会清爽很多

    package com.xdw.retrofitrxmvpdemo.presenter;
    
    import android.content.Context;
    import android.util.Log;
    
    import com.xdw.retrofitrxmvpdemo.http.ProgressSubscriber;
    import com.xdw.retrofitrxmvpdemo.manager.DataManager;
    import com.xdw.retrofitrxmvpdemo.model.Result;
    import com.xdw.retrofitrxmvpdemo.model.UserInfo;
    import com.xdw.retrofitrxmvpdemo.pv.PresentView;
    import com.xdw.retrofitrxmvpdemo.pv.UserInfoPv;
    
    import rx.Observable;
    import rx.Observer;
    import rx.Subscriber;
    import rx.android.schedulers.AndroidSchedulers;
    import rx.functions.Func1;
    import rx.schedulers.Schedulers;
    
    /**
     * Created by 夏德旺 on 2017/12/8.
     */
    
    //该类是具体业务presenter,如需增加另一个业务,比如Order
    //则可以再创建一个OrderPresenter
    public class UserInfoPresenter extends BasePresenter {
        private Context mContext;
        private UserInfoPv mUserInfoPv;
        private UserInfo mUserInfo;
    
        public UserInfoPresenter(Context context) {
            this.mContext = context;
        }
    
        @Override
        public void BindPresentView(PresentView presentView) {
            mUserInfoPv = (UserInfoPv) presentView;
        }
    
        //在presenter中实现业务逻辑,此处会调用前面封装好的retrofit的东西
        //将处理结果绑定到对应的PresentView实例,这样Activity和PresentView实例绑定好之后,
        //Activity->PresentView->Presenter->retrofit的关系就打通了
        public void getUserInfo(int uid) {
            Observable<UserInfo> observable = DataManager.getInstance(mContext).getUserInfo(uid);
            addSubscription(observable,new ProgressSubscriber<UserInfo>(mUserInfoPv, mContext, true) {
                @Override
                public void onNext(UserInfo userInfo) {
                    super.onNext(userInfo);
                    mUserInfoPv.onSuccess(userInfo);
                }
            } );
        }
    }

    这个getUserInfo是不是清爽了很多,首先通过DataManager的封装,可以很方便的获取剥离出了核心数据的observable,然后调用父类中的addSubscription来处理具体的业务。大家看到这里会发现业务逻辑中的onCompleted与onError这2个核心方法怎么不见了。这些我们都统一封装到了ProgressSubscriber中。继续往下看

    8、封装ProgressSubscriber,它继承Subscriber类,并且实现ProgressCancelListener接口。在该类中统一对报错(并且包括自定义的API异常)和网络对话框的出现与消失做了处理。这样就简化了我们大量的操作,不用再到每个界面中单独对其进行判断处理。具体代码如下

    package com.xdw.retrofitrxmvpdemo.http;
    import android.content.Context;
    import android.util.Log;
    import android.widget.Toast;
    
    import com.xdw.retrofitrxmvpdemo.pv.PresentView;
    import com.xdw.retrofitrxmvpdemo.util.APIException;
    
    import java.net.ConnectException;
    import java.net.SocketTimeoutException;
    
    import rx.Subscriber;
    
    /**
     * 用于在Http请求开始时,自动显示一个ProgressDialog
     * 在Http请求结束时,关闭ProgressDialog
     * 调用者自己对请求数据进行处理
     * Created by 夏德旺 on 2017/12/8.
     */
    public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{
    
        private PresentView mPresentView;
        private ProgressDialogHandler mProgressDialogHandler;
    
        private Context context;
    
        public ProgressSubscriber(PresentView mPresentView, Context context, boolean show) {
            this.mPresentView = mPresentView;
            this.context = context;
            mProgressDialogHandler = new ProgressDialogHandler(context, this, true,show);
        }
        public ProgressSubscriber(Context context,boolean show) {
            this.context = context;
            mProgressDialogHandler = new ProgressDialogHandler(context, this, true,show);
        }
    
        private void showProgressDialog(){
            if (mProgressDialogHandler != null) {
                mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
            }
        }
    
        private void dismissProgressDialog(){
            if (mProgressDialogHandler != null) {
                mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
                mProgressDialogHandler = null;
            }
        }
    
        /**
         * 订阅开始时调用
         * 显示ProgressDialog
         */
        @Override
        public void onStart() {
            showProgressDialog();
        }
    
        /**
         * 完成,隐藏ProgressDialog
         */
        @Override
        public void onCompleted() {
            dismissProgressDialog();
        }
    
        /**
         * 对错误进行统一处理
         * 隐藏ProgressDialog
         * @param e
         */
        @Override
        public void onError(Throwable e) {
            if (e instanceof SocketTimeoutException) {
                Toast.makeText(context,"网络中断,请检查您的网络状态",Toast.LENGTH_SHORT).show();
            } else if (e instanceof ConnectException) {
                Toast.makeText(context,"网络中断,请检查您的网络状态",Toast.LENGTH_SHORT).show();
            } else if(e instanceof APIException){
                Toast.makeText(context,e.getMessage(),Toast.LENGTH_SHORT).show();
                Log.e("xdw","apiCode="+((APIException) e).getCode());
            }else{
                Toast.makeText(context,"未知异常",Toast.LENGTH_SHORT).show();
                Log.e("xdw","apiCode="+((APIException) e).getCode());
            }
            dismissProgressDialog();
    
        }
    
        /**
         * 将onNext方法中的返回结果交给Activity或Fragment自己处理
         *
         * @param t 创建Subscriber时的泛型类型
         */
        @Override
        public void onNext(T t) {
    
        }
    
        /**
         * 取消ProgressDialog的时候,取消对observable的订阅,同时也取消了http请求
         */
        @Override
        public void onCancelProgress() {
            if (!this.isUnsubscribed()) {
                this.unsubscribe();
            }
        }
    }


    9、由于已经对错误进行了统一处理,那么这里在PresentView接口中去掉了之前定义的onError方法。

    package com.xdw.retrofitrxmvpdemo.pv;
    
    /**
     * Created by 夏德旺 on 2017/12/8.
     */
    
    public interface PresentView{
        //定义一个最基础的接口,里面就包含一个出错信息的回调
        //因为大多数时候报错的时候都是采用一条信息提示
        //如果需要负责的报错接口,请重载onError,是重载不是重写
        //经过后面加入第二次封装之后,这里的onError删除了,统一封装到了ProgressSubscriber中
        //如果需要特定的具体要到每个Activity中去重写报错信息的,可以在此再添加一个onError处理
    }

    10、最后我们来看看MainActivity中的处理,基本和之前没什么变化,就是少了个onError的方法

    package com.xdw.retrofitrxmvpdemo.activity;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.xdw.retrofitrxmvpdemo.R;
    import com.xdw.retrofitrxmvpdemo.model.UserInfo;
    import com.xdw.retrofitrxmvpdemo.presenter.UserInfoPresenter;
    import com.xdw.retrofitrxmvpdemo.pv.UserInfoPv;
    
    public class MainActivity extends AppCompatActivity {
        private TextView text;
        private Button button;
        //定义需要调用的presenter对象
        private UserInfoPresenter mUserInfoPresenter =new UserInfoPresenter(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            text = (TextView)findViewById(R.id.text);
            button = (Button)findViewById(R.id.button);
            //在Activity创建的时候同时初始化presenter,这里onCreater不是指的创建presenter对象,
            // 而是做一些presenter的初始化操作,名字应该取名init更好理解点,我这里就不重命名了
            mUserInfoPresenter.onCreate();
            //将presenter和PresentView进行绑定,实际上就是将presenter和Activity视图绑定,
            //这个是MVP模式中V与P交互的关键
            mUserInfoPresenter.BindPresentView(mUserInfoPv);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //点击按钮触发presenter里面的方法
                    mUserInfoPresenter.getUserInfo(1);
                }
            });
    
        }
    
        //采用内部类方法定义presentView对象,该对象用来将Activity和presenter进行绑定
        //绑定了以后主线程中就可以通过回调来获取网络请求的数据
        private UserInfoPv mUserInfoPv = new UserInfoPv(){
            @Override
            public void onSuccess(UserInfo userInfo) {
                text.setText(userInfo.toString());
            }
        };
    
        //在Activity销毁的时候,一定要对CompositeSubscription进行释放,否则会造成内存泄漏
        //释放操作封装到了presenter的ondestroy方法中
        @Override
        protected void onDestroy(){
            super.onDestroy();
            mUserInfoPresenter.onDestroy();
        }
    }

    到此整个封装完毕,我也用自己之前做的项目检验了下,确实也很好用。下面跟上篇博客一样介绍下使用方法。

    列举下之后像该项目中扩展业务的步骤,比如加一个订单功能。


    操作步骤:


    1、添加对应的model类Order


    2、RetrofitApiService中添加对应的网络请求api,此时的api格式是带上Result的


    3、将新添加的api映射到DataManager中,此时在DataManager中的api是剥离出实际数据之后的


    4、添加业务对应的PrensentView实例OrderPv


    5、添加业务对应的Presenter实例OrderPresenter


    6、在需要该业务的UI线程(Activity或Fragment)中调用具体业务对应的Presenter

    其实操作步骤没有太大变化,但是将返回结果换成了统一的数据格式,加入了返回码和返回消息,方便客户端对错误进行统一处理。同时添加了log与token的拦截器,并且简化了RxJava相关的代码操作。至此,一个完整的MVP框架封装完成。

     

  • 相关阅读:
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之用户管理(1)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之创建Viewport(2)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之用户管理(2)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之完成登录功能
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之登录窗口调试
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之创建Viewport(1)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之创建输出验证码图片的控制器
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之调整首页显示
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之登录窗口
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之用户管理(3)
  • 原文地址:https://www.cnblogs.com/xiadewang/p/8022777.html
Copyright © 2011-2022 走看看