Retrofit全攻略——进阶篇

最近事比较多,距离上次写文章已经过去了一个月了。上一篇文章Retrofit全攻略——基础篇 介绍了Retrofit的基础用法,这篇文章介绍点进阶的用法。

打印网络日志

在开发阶段,为了方便调试,我们需要查看网络日志。因为Retrofit2.0+底层是采用的OKHttp请求的。可以给OKHttp设置拦截器,用来打印日志。

首先可以在app/build.gradle中添加依赖,这是官方的日志拦截器。

compile ‘com.squareup.okhttp3:logging-interceptor:3.3.0‘

然后在代码中设置:

    public static Retrofit getRetrofit() {
        //如果mRetrofit为空  或者服务器地址改变 重新创建
        if (mRetrofit == null) {
            OkHttpClient httpClient;
            OkHttpClient.Builder builder=new OkHttpClient.Builder();

            //阶段分为开发和发布阶段,当前为开发阶段设置拦截器
            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
                //设置拦截器级别
                logging.setLevel(HttpLoggingInterceptor.Level.BODY);
                builder.addInterceptor(logging);
            }
            httpClient=builder.build();
            //构建Retrofit
            mRetrofit = new Retrofit.Builder()
                    //配置服务器路径
                    .baseUrl(mServerUrl)
                    //返回的数据通过Gson解析
                    .addConverterFactory(GsonConverterFactory.create())
                    //配置回调库,采用RxJava
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    //设置OKHttp模板
                    .client(httpClient)
                    .build();
        }
        return mRetrofit;
    }

当处于开发阶段的时候,设置监听日志的拦截器。拦截有4个级别,分别是

  1. BODY
  2. HEADERS
  3. BASIC
  4. NONE

其中BODY输出的日志是最全的。

添加相同的请求参数

为了更好的管理迭代版本,一般每次发起请求的时候都传输当前程序的版本号到服务器。

有些项目我们每次还会传用户id,token令牌等相同的参数。

如果在每个请求的接口都添加这些参数太繁琐。Retrofit可以通过拦截器添加相同的请求参数,无需再每个接口添加了。

步骤一,自己拦截器

public class CommonInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request oldRequest = chain.request();

        // 添加新的参数
        HttpUrl.Builder authorizedUrlBuilder = oldRequest.url()
                .newBuilder()
                .scheme(oldRequest.url().scheme())
                .host(oldRequest.url().host())
                .addQueryParameter("device_type", "1")
                .addQueryParameter("version", BuildConfig.VERSION_NAME)
                .addQueryParameter("token", PreUtils.getString(R.string.token))
                .addQueryParameter("userid", PreUtils.getString(R.string.user_id));

        // 新的请求
        Request newRequest = oldRequest.newBuilder()
                .method(oldRequest.method(), oldRequest.body())
                .url(authorizedUrlBuilder.build())
                .build();

        return chain.proceed(newRequest);
    }
}

实现原理就是拦截之前的请求,添加完参数,再传递新的请求。这个位置我添加了四个公共的参数。

然后再Retrofit初始化的时候配置。

        if (mRetrofit == null) {
            OkHttpClient httpClient;
            OkHttpClient.Builder builder=new OkHttpClient.Builder();
            //添加公共参数
            builder.addInterceptor(new CommonInterceptor());
            httpClient=builder.build();
            //构建Retrofit
            mRetrofit = new Retrofit.Builder()
                    //....
                    .client(httpClient)
                    .build();
        }

处理约定错误

除了常见的404,500等异常,网络请求中我们往往还会约定些异常,比如token失效,账号异常等等。

以token失效为例,每次请求我们都需要验证是否失效,如果在每个接口都处理一遍错误就有点太繁琐了。

我们可以统一处理下错误。

步骤一,Retrofit初始化时添加自定义转化器

mRetrofit = new Retrofit.Builder()
        //配置服务器路径
      baseUrl(mServerUrl)
      //配置回调库,采用RxJava
     .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
     //配置转化库,默认是Gson,这里修改了。
     .addConverterFactory(ResponseConverterFactory.create())
     .client(httpClient)
     .build();

步骤二 创建ResponseConverterFactory

步骤一 ResponseConverterFactory这个类是需要我们自己创建的。

public class ResponseConverterFactory extends Converter.Factory {
    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static ResponseConverterFactory create() {
        return create(new Gson());
    }

    /**
     * Create an instance using {@code gson} for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static ResponseConverterFactory create(Gson gson) {
        return new ResponseConverterFactory(gson);
    }

    private final Gson gson;

    private ResponseConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
      //  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, type);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}

这里面我们自定义了请求和响应时解析JSON的转换器——GsonRequestBodyConverterGsonResponseBodyConverter

其中GsonRequestBodyConverter 负责处理请求时传递JSON对象的格式,不需要额外处理任何事,直接使用默认的GSON解析。代码我直接贴出来:

final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;

    GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter, value);
        jsonWriter.close();
        return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
    }
}

GsonResponseBodyConverter负责把响应的数据转换成JSON格式,这个我们需要处理一下。


public class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final Type type;

    GsonResponseBodyConverter(Gson gson, Type type) {
        this.gson = gson;
        this.type = type;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        try {
            Log.i("YLlibrary", "response>>> "+response);
            //ResultResponse 只解析result字段
            BaseInfo baseInfo = gson.fromJson(response, BaseInfo.class);
            if (baseInfo.getHeader().getCode().equals("1")) {
                //正确
                return gson.fromJson(response, type);
            } else {
                //ErrResponse 将msg解析为异常消息文本 错误码可以自己指定.
                throw new ResultException(-1024, baseInfo,response);
            }
        } finally {
        }
    }

}

这种情况只是应用于后台接口数据统一的情况。比如我们项目的格式是这样的

      {
          header : {"message":"token失效","code":"99"}
          data : {}
      }

当code值是1的时候,表示正确,其它数字表示错误。只有正确的时候data才会有内容。

这里我用BaseInfo解析这个JSON:

public class BaseInfo {

    /**
     * header : {"message":"用户名或密码错误","code":"0"}
     * data : {}
     */

    private HeaderBean header;

    public HeaderBean getHeader() {
        return header;
    }

    public void setHeader(HeaderBean header) {
        this.header = header;
    }

    public static class HeaderBean {
        /**
         * message : 用户名或密码错误
         * code : 0
         */

        private String message;
        private String code;

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }
    }
}

服务器返回的数据实体对象全部继承BaseInfo 只是data内容不一样。

ResultException这个类用于捕获服务器约定的错误类型

/**
 * 这个类用于捕获服务器约定的错误类型
 */
public class ResultException extends RuntimeException {

    private int errCode = 0;
    private BaseInfo info;
    private String response;
    public ResultException(int errCode, BaseInfo info,String response) {
        super(info.getHeader().getMessage());
        this.info=info;
        this.errCode = errCode;
        this.response=response;
    }

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
    }

    public int getErrCode() {
        return errCode;
    }

    public BaseInfo getBaseInfo(){
        return info;
    }
}

最后定义Retrofit处理异常的代码

public abstract class AbsAPICallback<T> extends Subscriber<T> {

    //对应HTTP的状态码
    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    private static final int INTERNAL_SERVER_ERROR = 500;
    private static final int BAD_GATEWAY = 502;
    private static final int SERVICE_UNAVAILABLE = 503;
    private static final int GATEWAY_TIMEOUT = 504;
    //出错提示
    private final String networkMsg;
    private final String parseMsg;
    private final String unknownMsg;

    protected AbsAPICallback(String networkMsg, String parseMsg, String unknownMsg) {
        this.networkMsg = networkMsg;
        this.parseMsg = parseMsg;
        this.unknownMsg = unknownMsg;
    }
    public  AbsAPICallback(){
        networkMsg="net error(联网失败)";
        parseMsg="json parser error(JSON解析失败)";
        unknownMsg="unknown error(未知错误)";
    }
    ProgressBar progressBar;
    public  AbsAPICallback(ProgressBar progressBar){
        this();
        this.progressBar=progressBar;
    }
    @Override
    public void onError(Throwable e) {
        Throwable throwable = e;
        //获取最根源的异常
        while(throwable.getCause() != null){
            e = throwable;
            throwable = throwable.getCause();
        }

        ApiException ex;
        if (e instanceof HttpException){             //HTTP错误
            HttpException httpException = (HttpException) e;
            ex = new ApiException(e, httpException.code());
            switch(httpException.code()){
                case UNAUTHORIZED:
                case FORBIDDEN:
                  //  onPermissionError(ex);          //权限错误,需要实现
                   // break;
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    ex.setDisplayMessage(networkMsg);  //均视为网络错误
                    onNetError(ex);
                    break;
            }
        } else if (e instanceof ResultException){    //服务器返回的错误
            ResultException resultException = (ResultException) e;
            onResultError(resultException);
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException){
            ex = new ApiException(e, ApiException.PARSE_ERROR);
            ex.setDisplayMessage(parseMsg);            //均视为解析错误
            onNetError(ex);
        } else {
            ex = new ApiException(e, ApiException.UNKNOWN);
            ex.setDisplayMessage(unknownMsg);          //未知错误
            onNetError(ex);
        }
    }
    static long time;
    protected  void onNetError(ApiException e){
        long currentTime=System.currentTimeMillis();
        if(currentTime-time>3000){  //防止连续反馈
            time=currentTime;
            UIUtils.showToast("网络加载失败");
        }
        e.printStackTrace();
        onApiError(e);
    }
    /**
     * 错误回调
     */
    protected  void onApiError(ApiException ex){
        Log.i("YLLibrary","onApiError");
        if(progressBar!=null)
            UIUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    progressBar.setVisibility(View.GONE);
                    progressBar=null;
                }
            });
    }

//    /**
//     * 权限错误,需要实现重新登录操作
//     */
//    protected void  onPermissionError(ApiException ex){
//        ex.printStackTrace();
//    }

    /**
     * 服务器返回的错误
     */
    protected  synchronized  void onResultError(ResultException ex){
//        if(ex.getErrCode()== XApplication.API_ERROR){
//            UIUtils.getContext().onApiError(); //可以用来处理Token失效
//            return ;
//        }
        if(ConstantValue.TOKEN_ERROR.equals(ex.getBaseInfo().getHeader().getCode())
                &&!TextUtils.isEmpty(PreUtils.getString(R.string.token))){ //验证token是否为空是为了防止连续两次请求
            PreUtils.putString(R.string.user_id,null);
            PreUtils.putString(R.string.token,null);
            PreUtils.putString(R.string.orgDistrict,null);
            if(BaseActivity.runActivity!=null){
                Intent intent = new Intent(UIUtils.getContext(), LoginActivity.class);
                if(BaseActivity.runActivity instanceof MainActivity){
                    MainActivity activity= (MainActivity) BaseActivity.runActivity;
                    int tabIndex=activity.getCurrentTab();
                    //activity.switchCurrentTab(0);
                    activity.startActivityForResult(intent,tabIndex+10);
                }else {
                    BaseActivity.runActivity.startActivity(intent);
                }
            }
        }

        Log.i("YLLibrary","resultError");
        if(ex.getBaseInfo()!=null&&!TextUtils.isEmpty(ex.getBaseInfo().getHeader().getMessage()))
            UIUtils.showToast(ex.getBaseInfo().getHeader().getMessage());

        ApiException apiException = new ApiException(ex, ex.getErrCode());
        onApiError(apiException);
    }

    @Override
    public void onCompleted() {
        Log.i("YLLibrary","onCompleted");
        if(progressBar!=null)
            UIUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    progressBar.setVisibility(View.GONE);
                    progressBar=null;
                }
            });
    }

}

实际接口请求的代码,使用自定义异常回调的类——AbsAPICallback就可以统一处理异常:

        ApiRequestManager.createApi().problemDetail(dataBean.getId())
                .compose(ApiRequestManager.<QuestionDetailInfo>applySchedulers())
                .subscribe(new AbsAPICallback<QuestionDetailInfo>() {
                    @Override
                    public void onNext(QuestionDetailInfo baseInfo) {
                        fillData(baseInfo);
                    }
                });

更多精彩请关注微信公众账号likeDev

时间: 2025-01-19 22:42:49

Retrofit全攻略——进阶篇的相关文章

Retrofit全攻略——基础篇

实际开发过程中一般都会选择一些网络框架提升开发效率.随着Google对HttpClient 摒弃和Volley框架的逐渐没落.OkHttp開始异军突起.而Retrofit则对OkHttp进行了强制依赖,能够简单理解Retroifit在OKHttp基础上进一步完好. Retrofit是由Square公司出品的针对于Android和Java的类型安全的Httpclient,眼下推出了2.0+的版本号. Retrofit框架项目地址:https://github.com/square/retrofit

独孤九篇之运维进阶:文件共享服务全攻略完结篇

一.了解一下 1.NFS NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源.在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样. 好处: 节省本地存储空间,将常用的数据存放在一台NFS服务器上且可以通过网络访问,那么本地终端将可以减少自身存储空间的使用.用户不需要在网络中的每个机器上都建有Home目录,Home目录可以放在NFS服务器上

FPGA开发全攻略——概念篇

原文链接: FPGA开发全攻略连载之一:FPGA为什么这么热? FPGA开发全攻略连载之二:为什么工程师要掌握FPGA开发知识? FPGA开发全攻略连载之三:FPGA基本知识与发展趋势(part1) FPGA开发全攻略连载之四:FPGA基本知识与发展趋势(part2) 写在前面 2008年,我参加了几次可编程器件供应商举办的技术研讨会,让我留下深刻印象的是参加这些研讨会的工程师人数之多,简直可以用爆满来形容,很多工程师聚精会神地全天听讲,很少出现吃完午饭就闪人的现象,而且工程师们对研讨会上展出的

前台页面优化全攻略(三)

经过前两篇文章的实践,你的网站加载速度一定有了非常明显的变化.能把实践跟到这篇文章的人想必一定是极客中的极客.如果你仍对网站的加载速度不满意,可以看看再尝试一下本文中几近疯狂的终极优化方案. 你可以对网站进行快速的优化,但网站日常的节食却很难.也许你已经花了很大的力气去优化你的CSS和JavaScript代码,但是你所做的努力马上又会因为老板或客户期望的新功能而付之东流.所以看来不论是人还是网页,减肥都贵在坚持. 这篇终极减肥方案可能不适合所有的网站,但是我相信它可以引起你对网页大小的重视. 1

3D计算机图形学零起点全攻略(转)

3D计算机图形学零起点全攻略 这篇文章不包含任何技术知识,但我的希望它能指明一条从零开始通往3D领域的成功之路.我将罗列我看过的相关经典书籍作为学习文献,阅读规则是每进入下个内容,我都会假设已经完成前面全部的文献研习内容.相信若能按照这条路走到最后,会有所进益. 完成整部分内容需要具备基础: 英语:CET4以上 数学:精通数字加减乘除法. 物理:基本力学. 计算机:了解电脑的基本知识,熟练使用Windows. 电脑配置: CPU:双核1.5以上 显卡:NVIDIA GeForce8400G MS

[037] 微信公众帐号开发教程第13篇-图文消息全攻略

引言及内容概要 已经有几位读者抱怨“柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用”,好吧,我错了,原本以为把基础API封装完.框架搭建好,再给出一个文本消息的使用示例,大家就能够照猫画虎的,或许是因为我的绘画功底太差,画出的那只猫本来就不像猫吧…… 本篇主要介绍微信公众帐号开发中图文消息的使用,以及图文消息的几种表现形式.标题取名为“图文消息全攻略”,这绝对不是标题党,是想借此机会把大家对图文消息相关的问题.疑虑.障碍全部清除掉. 图文消息的主要参数说明 通过微信官方

Docker全攻略完全解析电子版pdf下载

Docker全攻略完全解析 链接:https://pan.baidu.com/s/1_ltvH7-jqbranqQ5TnH46w 提取码:z0qt Docker全攻略完整电子书分享,有需要的朋友们可以收下,(#.#)! 作品目录 前言 第一篇 Docker简介 第1章 Docker的前世今生 1.1 什么是LXC 1.2 Docker为什么选择了AUFS 1.3 Docker是如何产生的 第2章 Docker现状 2.1 Docker应用范围 2.2 Docker的优缺点 1.Docker资源利

Linux(CentOS)搭建SVN服务器全攻略

虽然在windows上搭建SVN很简单,但是效能却不高,这当然是和linux相比了.然而在linux上搭建SVN却非常繁琐,所以今天这篇文章就来一步一步教您如何在Centos上搭建SVN 安装#yum install subversion 1)创建svn用户#groupadd svn#useradd -g sky user//是将user加入到sky組內切换用户#su svn以后代码库的创建维护等,都用这个帐户来操作. 2)创建版本库编辑.bash_profile 加上如下配置SVN_HOME=

Gradle脚本基础全攻略

[工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.点我开始Android技术交流] 1 背景 在开始Gradle之前请务必保证自己已经初步了解了Groovy脚本,特别是闭包规则,如果还不了解Groovy则可以先看<Groovy脚本基础全攻略>这一篇博客速成一下Groovy基础,然后再看此文即可.关于Gradle速成干货基础详情也请参考Geadle官方网站,不好意思我太Low了. Gradle核心是基于Groovy的领域特定语言(DSL,具体概念参见<