retrofit_retrofit rxjava - CSDN
精华内容
参与话题
  • Retrofit 从入门封装到源码解析

    万人学习 2018-11-27 16:11:21
    该教程从Retrofit的入门开始讲解,然后搭配Rxjava使用,主要讲解在实战项目中如何进行Retrofit封装(主要涉及到OkHttp,部分涉及到Retrofit),后会对源码进行解析,让你知其然,知其所以然。
  • Retrofit2 完全解析 探索与okhttp之间的关系

    万次阅读 多人点赞 2016-05-04 10:32:37
    转载请标明出处: ...之前写了个okhttputils的工具类,然后有很多同学询问这个工具类和retrofit什么区别,于是上了下官网,发现其底层对网络的访问默认也是基于okhttp,不过retrofit非常适合于restful

    转载请标明出处:
    http://blog.csdn.net/lmj623565791/article/details/51304204
    本文出自:【张鸿洋的博客】

    一、概述

    之前写了个okhttputils的工具类,然后有很多同学询问这个工具类和retrofit什么区别,于是上了下官网,发现其底层对网络的访问默认也是基于okhttp,不过retrofit非常适合于restful url格式的请求,更多使用注解的方式提供功能。

    既然这样,我们本篇博文首先研究其所提供的常用的用法:

    • 一般的get、post请求
    • 动态url,动态参数设置,各种注解的使用
    • 上传文件(单文件,多文件上传等)
    • 下载文件等(这个不推荐retrofit去做,具体看下文)

    此外,由于其内部提供了ConverterFactory用于对返回的requestBody进行转化和特殊的requestBody的构造,所以本文也包含:

    • 如何自定义ConverterFactory

    最后呢,因为其源码并不复杂,本文将对源码进行整体的介绍,即

    • retrofit 源码分析

    ok,说这么多,既然需要restful url,我只能捡起我那个半桶水的spring mvc 搭建一个服务端的小例子~~

    最后本文使用版本:

    compile 'com.squareup.retrofit2:retrofit:2.0.2'

    主要是源码解析,自定义Converter.Factory等一些细节的探索。

    恩,写完后,发现本文很长,中途请没事站起来走两步。

    retrofit2官网地址:https://github.com/square/retrofit/

    二、retrofit 用法示例

    (1)一般的get请求

    retrofit在使用的过程中,需要定义一个接口对象,我们首先演示一个最简单的get请求,接口如下所示:

    public interface IUserBiz
    {
        @GET("users")
        Call<List<User>> getUsers();
    }

    可以看到有一个getUsers()方法,通过@GET注解标识为get请求,@GET中所填写的value和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出。

    下面看如何通过retrofit完成上述的请求:

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://192.168.31.242:8080/springmvc_users/user/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    IUserBiz userBiz = retrofit.create(IUserBiz.class);
    Call<List<User>> call = userBiz.getUsers();
            call.enqueue(new Callback<List<User>>()
            {
                @Override
                public void onResponse(Call<List<User>> call, Response<List<User>> response)
                {
                    Log.e(TAG, "normalGet:" + response.body() + "");
                }
    
                @Override
                public void onFailure(Call<List<User>> call, Throwable t)
                {
    
                }
            });

    依然是构造者模式,指定了baseUrlConverter.Factory,该对象通过名称可以看出是用于对象转化的,本例因为服务器返回的是json格式的数组,所以这里设置了GsonConverterFactory完成对象的转化。

    ok,这里可以看到很神奇,我们通过Retrofit.create就可以拿到我们定义的IUserBiz的实例,调用其方法即可拿到一个Call对象,通过call.enqueue即可完成异步的请求。

    具体retrofit怎么得到我们接口的实例的,以及对象的返回结果是如何转化的,我们后面具体分析。

    这里需要指出的是:

    1. 接口中的方法必须有返回值,且比如是Call<T>类型
    2. .addConverterFactory(GsonConverterFactory.create())这里如果使用gson,需要额外导入:

      compile 'com.squareup.retrofit2:converter-gson:2.0.2'
      

      当然除了gson以外,还提供了以下的选择:

      Gson: com.squareup.retrofit2:converter-gson
      Jackson: com.squareup.retrofit2:converter-jackson
      Moshi: com.squareup.retrofit2:converter-moshi
      Protobuf: com.squareup.retrofit2:converter-protobuf
      Wire: com.squareup.retrofit2:converter-wire
      Simple XML: com.squareup.retrofit2:converter-simplexml
      Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
      

      当然也支持自定义,你可以选择自己写转化器完成数据的转化,这个后面将具体介绍。

    3. 既然call.enqueue是异步的访问数据,那么同步的访问方式为call.execute,这一点非常类似okhttp的API,实际上默认情况下内部也是通过okhttp3.Call实现。

    那么,通过这么一个简单的例子,应该对retrofit已经有了一个直观的认识,下面看更多其支持的特性。

    (2)动态的url访问@PATH

    文章开头提过,retrofit非常适用于restful url的格式,那么例如下面这样的url:

    //用于访问zhy的信息
    http://192.168.1.102:8080/springmvc_users/user/zhy
    //用于访问lmj的信息
    http://192.168.1.102:8080/springmvc_users/user/lmj

    即通过不同的username访问不同用户的信息,返回数据为json字符串。

    那么可以通过retrofit提供的@PATH注解非常方便的完成上述需求。

    我们再定义一个方法:

    public interface IUserBiz
    {
        @GET("{username}")
        Call<User> getUser(@Path("username") String username);
    }

    可以看到我们定义了一个getUser方法,方法接收一个username参数,并且我们的@GET注解中使用{username}声明了访问路径,这里你可以把{username}当做占位符,而实际运行中会通过@PATH("username")所标注的参数进行替换。

    那么访问的代码很类似:

    //省略了retrofit的构建代码
    Call<User> call = userBiz.getUser("zhy");
    //Call<User> call = userBiz.getUser("lmj");
    call.enqueue(new Callback<User>()
    {
    
        @Override
        public void onResponse(Call<User> call, Response<User> response)
        {
            Log.e(TAG, "getUsePath:" + response.body());
        }
    
        @Override
        public void onFailure(Call<User> call, Throwable t)
        {
    
        }
    });
    

    (3)查询参数的设置@Query

    看下面的url

    http://baseurl/users?sortby=username
    http://baseurl/users?sortby=id

    即一般的传参,我们可以通过@Query注解方便的完成,我们再次在接口中添加一个方法:

    public interface IUserBiz
    {
        @GET("users")
        Call<List<User>> getUsersBySort(@Query("sortby") String sort);
    }

    访问的代码,其实没什么写的:

    //省略retrofit的构建代码
    Call<List<User>> call = userBiz.getUsersBySort("username");
    //Call<List<User>> call = userBiz.getUsersBySort("id");
    //省略call执行相关代码

    ok,这样我们就完成了参数的指定,当然相同的方式也适用于POST,只需要把注解修改为@POST即可。

    对了,我能刚才学了@PATH,那么会不会有这样尝试的冲动,对于刚才的需求,我们这么写:

     @GET("users?sortby={sortby}")
     Call<List<User>> getUsersBySort(@Path("sortby") String sort);

    乍一看别说好像有点感觉,哈,实际上运行是不支持的~估计是@ Path的定位就是用于url的路径而不是参数,对于参数还是选择通过@Query来设置。

    (4)POST请求体的方式向服务器传入json字符串@Body

    大家都清楚,我们app很多时候跟服务器通信,会选择直接使用POST方式将json字符串作为请求体发送到服务器,那么我们看看这个需求使用retrofit该如何实现。

    再次添加一个方法:

    public interface IUserBiz
    {
     @POST("add")
     Call<List<User>> addUser(@Body User user);
    }

    提交的代码其实基本都是一致的:

    //省略retrofit的构建代码
     Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com"));
    //省略call执行相关代码

    ok,可以看到其实就是使用@Body这个注解标识我们的参数对象即可,那么这里需要考虑一个问题,retrofit是如何将user对象转化为字符串呢?下文将详细解释~

    下面对应okhttp,还有两种requestBody,一个是FormBody,一个是MultipartBody,前者以表单的方式传递简单的键值对,后者以POST表单的方式上传文件可以携带参数,retrofit也二者也有对应的注解,下面继续~

    (5)表单的方式传递键值对@FormUrlEncoded

    这里我们模拟一个登录的方法,添加一个方法:

    public interface IUserBiz
    {
        @POST("login")
        @FormUrlEncoded
        Call<User> login(@Field("username") String username, @Field("password") String password);
    }

    访问的代码:

    //省略retrofit的构建代码
    Call<User> call = userBiz.login("zhy", "123");
    //省略call执行相关代码

    ok,看起来也很简单,通过@POST指明url,添加FormUrlEncoded,然后通过@Field添加参数即可。

    (6)单文件上传@Multipart

    下面看一下单文件上传,依然是再次添加个方法:

    public interface IUserBiz
    {
        @Multipart
        @POST("register")
        Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
    }
    

    这里@MultiPart的意思就是允许多个@Part了,我们这里使用了3个@Part,第一个我们准备上传个文件,使用了MultipartBody.Part类型,其余两个均为简单的键值对。

    使用:

    File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
    RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
    MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);
    
    Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));

    ok,这里感觉略为麻烦。不过还是蛮好理解~~多个@Part,每个Part对应一个RequestBody。

    这里插个实验过程,其实我最初对于文件,也是尝试的@Part RequestBody,因为@Part("key"),然后传入一个代表文件的RequestBody,我觉得更加容易理解,后来发现试验无法成功,而且查了下issue,给出了一个很奇怪的解决方案,这里可以参考:retrofit#1063

    给出了一个类似如下的方案:

    public interface ApiInterface {
            @Multipart
            @POST ("/api/Accounts/editaccount")
            Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);
        }

    可以看到对于文件的那个@Partvalue竟然写了这么多奇怪的东西,而且filename竟然硬编码了~~这个不好吧,我上传的文件名竟然不能动态指定。

    为了文件名不会被写死,所以给出了最上面的上传单文件的方法,ps:上面这个方案经测试也是可以上传成功的。

    恩,这个奇怪方案,为什么这么做可行,下文会给出非常详细的解释。

    最后看下多文件上传~

    (7)多文件上传@PartMap

    再添加一个方法~~~

     public interface IUserBiz
     {
         @Multipart
         @POST("register")
          Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
    }

    这里使用了一个新的注解@PartMap,这个注解用于标识一个Map,Map的key为String类型,代表上传的键值对的key(与服务器接受的key对应),value即为RequestBody,有点类似@Part的封装版本。

    执行的代码:

    File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
            RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
    Map<String,RequestBody> photos = new HashMap<>();
    photos.put("photos\"; filename=\"icon.png", photo);
    photos.put("username",  RequestBody.create(null, "abc"));
    
    Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
    

    可以看到,可以在Map中put进一个或多个文件,键值对等,当然你也可以分开,单独的键值对也可以使用@Part,这里又看到设置文件的时候,相对应的key很奇怪,例如上例"photos\"; filename=\"icon.png",前面的photos就是与服务器对应的key,后面filename是服务器得到的文件名,ok,参数虽然奇怪,但是也可以动态的设置文件名,不太影响使用~~

    (8)下载文件

    这个其实我觉得直接使用okhttp就好了,使用retrofit去做这个事情真的有点瞎用的感觉~~

    增加一个方法:

    @GET("download")
    Call<ResponseBody> downloadTest();

    调用:

    Call<ResponseBody> call = userBiz.downloadTest();
    call.enqueue(new Callback<ResponseBody>()
    {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
        {
            InputStream is = response.body().byteStream();
            //save file
        }
    
        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t)
        {
    
        }
    });

    可以看到可以返回ResponseBody,那么很多事都能干了~~

    but,也看出这种方式下载感觉非常鸡肋,并且onReponse回调虽然在UI线程,但是你还是要处理io操作,也就是说你在这里还要另外开线程操作,或者你可以考虑同步的方式下载。

    最后还是建议使用okhttp去下载,例如使用okhttputils.

    有人可能会问,使用okhttp,和使用retrofit会不会造成新建多个OkHttpClient对象呢,其实是可设置的,参考下文。

    ok,上面就是一些常用的方法,当然还涉及到一些没有介绍的注解,但是通过上面这么多方法的介绍,再多一二个注解的使用方式,相信大家能够解决。

    三、配置OkHttpClient

    这个需要简单提一下,很多时候,比如你使用retrofit需要统一的log管理,给每个请求添加统一的header等,这些都应该通过okhttpclient去操作,比如addInterceptor

    例如:

    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,统一的header等
    {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException
        {
            return null;
        }
    }).build();

    或许你需要更多的配置,你可以单独写一个OkhttpClient的单例生成类,在这个里面完成你所需的所有的配置,然后将OkhttpClient实例通过方法公布出来,设置给retrofit。

    设置方式:

    Retrofit retrofit = new Retrofit.Builder()
        .callFactory(OkHttpUtils.getClient())
        .build();

    callFactory方法接受一个okhttp3.Call.Factory对象,OkHttpClient即为一个实现类。

    四、retrofit 源码解析

    ok,接下来我们队retrofit的源码做简单的分析,首先我们看retrofit如何为我们的接口实现实例;然后看整体的执行流程;最后再看详细的细节;

    (1)retrofit如何为我们的接口实现实例

    通过上文的学习,我们发现使用retrofit需要去定义一个接口,然后可以通过调用retrofit.create(IUserBiz.class);方法,得到一个接口的实例,最后通过该实例执行我们的操作,那么retrofit如何实现我们指定接口的实例呢?

    其实原理是:动态代理。但是不要被动态代理这几个词吓唬到,Java中已经提供了非常简单的API帮助我们来实现动态代理。

    看源码前先看一个例子:

    public interface ITest
    {
        @GET("/heiheihei")
        public void add(int a, int b);
    
    }
    public static void main(String[] args)
    {
        ITest iTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class<?>[]{ITest.class}, new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                Integer a = (Integer) args[0];
                Integer b = (Integer) args[1];
                System.out.println("方法名:" + method.getName());
                System.out.println("参数:" + a + " , " + b);
    
                GET get = method.getAnnotation(GET.class);
                System.out.println("注解:" + get.value());
                return null;
            }
        });
        iTest.add(3, 5);
    }

    输出结果为:

    方法名:add
    参数:3 , 5
    注解:/heiheihei

    可以看到我们通过Proxy.newProxyInstance产生的代理类,当调用接口的任何方法时,都会调用InvocationHandler#invoke方法,在这个方法中可以拿到传入的参数,注解等。

    试想,retrofit也可以通过同样的方式,在invoke方法里面,拿到所有的参数,注解信息然后就可以去构造RequestBody,再去构建Request,得到Call对象封装后返回。

    ok,下面看retrofit#create的源码:

    public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
                @Override 
                public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
           });
      }

    哈,和上面对应。到这里,你应该明白retrofit为我们接口生成实例对象并不神奇,仅仅是使用了Proxy这个类的API而已,然后在invoke方法里面拿到足够的信息去构建最终返回的Call而已。

    哈,其实真正的动态代理一般是有具体的实现类的,只是在这个类调用某个方法的前后去执行一些别的操作,比如开事务,打log等等。当然,本博文并不需要涉及这些详细的内容,如果你希望详细去了解,可以搜索关键字:Proxy InvocationHandler

    (2)retrofit整体实现流程

    4.2.1 Retrofit的构建

    这里依然是通过构造者模式进行构建retrofit对象,好在其内部的成员变量比较少,我们直接看build()方法。

    public Builder() {
        this(Platform.get());
    }
    
    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }
    
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
    
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }
    
      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    
      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
    • baseUrl必须指定,这个是理所当然的;
    • 然后可以看到如果不着急设置callFactory,则默认直接new OkHttpClient(),可见如果你需要对okhttpclient进行详细的设置,需要构建OkHttpClient对象,然后传入;
    • 接下来是callbackExecutor,这个想一想大概是用来将回调传递到UI线程了,当然这里设计的比较巧妙,利用platform对象,对平台进行判断,判断主要是利用Class.forName("")进行查找,具体代码已经被放到文末,如果是Android平台,会自定义一个Executor对象,并且利用Looper.getMainLooper()实例化一个handler对象,在Executor内部通过handler.post(runnable),ok,整理凭大脑应该能构思出来,暂不贴代码了。
    • 接下来是adapterFactories,这个对象主要用于对Call进行转化,基本上不需要我们自己去自定义。
    • 最后是converterFactories,该对象用于转化数据,例如将返回的responseBody转化为对象等;当然不仅仅是针对返回的数据,还能用于一般备注解的参数的转化例如@Body标识的对象做一些操作,后面遇到源码详细再描述。

    ok,总体就这几个对象去构造retrofit,还算比较少的~~

    4.2.2 具体Call构建流程

    我们构造完成retrofit,就可以利用retrofit.create方法去构建接口的实例了,上面我们已经分析了这个环节利用了动态代理,而且我们也分析了具体的Call的构建流程在invoke方法中,下面看代码:

    public <T> T create(final Class<T> service) {
        Utils.validateServiceInterface(service);
        //...
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
               @Override 
              public Object invoke(Object proxy, Method method, Object... args){
                //...
                ServiceMethod serviceMethod = loadServiceMethod(method);
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
    }
    

    主要也就三行代码,第一行是根据我们的method将其包装成ServiceMethod,第二行是通过ServiceMethod和方法的参数构造retrofit2.OkHttpCall对象,第三行是通过serviceMethod.callAdapter.adapt()方法,将OkHttpCall进行代理包装;

    下面一个一个介绍:

    • ServiceMethod应该是最复杂的一个类了,包含了将一个method转化为Call的所有的信息。
    #Retrofit class
    ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = new ServiceMethod.Builder(this, method).build();
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
    
    #ServiceMethod
    public ServiceMethod build() {
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          if (responseType == Response.class || responseType == okhttp3.Response.class) {
            throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
          }
          responseConverter = createResponseConverter();
    
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }
    
    
          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0; p < parameterCount; p++) {
            Type parameterType = parameterTypes[p];
            if (Utils.hasUnresolvableType(parameterType)) {
              throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                  parameterType);
            }
    
            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
            if (parameterAnnotations == null) {
              throw parameterError(p, "No Retrofit annotation found.");
            }
    
            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }
    
          return new ServiceMethod<>(this);
        }

    直接看build方法,首先拿到这个callAdapter最终拿到的是我们在构建retrofit里面时adapterFactories时添加的,即为:new ExecutorCallbackCall<>(callbackExecutor, call),该ExecutorCallbackCall唯一做的事情就是将原本call的回调转发至UI线程。

    接下来通过callAdapter.responseType()返回的是我们方法的实际类型,例如:Call<User>,则返回User类型,然后对该类型进行判断。

    接下来是createResponseConverter拿到responseConverter对象,其当然也是根据我们构建retrofit时,addConverterFactory添加的ConverterFactory对象来寻找一个合适的返回,寻找的依据主要看该converter能否处理你编写方法的返回值类型,默认实现为BuiltInConverters,仅仅支持返回值的实际类型为ResponseBodyVoid,也就说明了默认情况下,是不支持Call<User>这类类型的。

    接下来就是对注解进行解析了,主要是对方法上的注解进行解析,那么可以拿到httpMethod以及初步的url(包含占位符)。

    后面是对方法中参数中的注解进行解析,这一步会拿到很多的ParameterHandler对象,该对象在toRequest()构造Request的时候调用其apply方法。

    ok,这里我们并没有去一行一行查看代码,其实意义也不太大,只要知道ServiceMethod主要用于将我们接口中的方法转化为一个Request对象,于是根据我们的接口返回值确定了responseConverter,解析我们方法上的注解拿到初步的url,解析我们参数上的注解拿到构建RequestBody所需的各种信息,最终调用toRequest的方法完成Request的构建。

    • 接下来看OkHttpCall的构建,构造函数仅仅是简单的赋值
    OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
        this.serviceMethod = serviceMethod;
        this.args = args;
      }
    • 最后一步是serviceMethod.callAdapter.adapt(okHttpCall)

    我们已经确定这个callAdapter是ExecutorCallAdapterFactory.get()对应代码为:

    final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
      final Executor callbackExecutor;
    
      ExecutorCallAdapterFactory(Executor callbackExecutor) {
        this.callbackExecutor = callbackExecutor;
      }
    
      @Override
      public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if (getRawType(returnType) != Call.class) {
          return null;
        }
        final Type responseType = Utils.getCallResponseType(returnType);
        return new CallAdapter<Call<?>>() {
          @Override public Type responseType() {
            return responseType;
          }
    
          @Override public <R> Call<R> adapt(Call<R> call) {
            return new ExecutorCallbackCall<>(callbackExecutor, call);
          }
        };
      }

    可以看到adapt返回的是ExecutorCallbackCall对象,继续往下看:

    static final class ExecutorCallbackCall<T> implements Call<T> {
        final Executor callbackExecutor;
        final Call<T> delegate;
    
        ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
          this.callbackExecutor = callbackExecutor;
          this.delegate = delegate;
        }
    
        @Override public void enqueue(final Callback<T> callback) {
          if (callback == null) throw new NullPointerException("callback == null");
    
          delegate.enqueue(new Callback<T>() {
            @Override public void onResponse(Call<T> call, final Response<T> response) {
              callbackExecutor.execute(new Runnable() {
                @Override public void run() {
                  if (delegate.isCanceled()) {
                    // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                    callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                  } else {
                    callback.onResponse(ExecutorCallbackCall.this, response);
                  }
                }
              });
            }
    
            @Override public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(new Runnable() {
                @Override public void run() {
                  callback.onFailure(ExecutorCallbackCall.this, t);
                }
              });
            }
          });
        }
        @Override public Response<T> execute() throws IOException {
          return delegate.execute();
        }
      }

    可以看出ExecutorCallbackCall仅仅是对Call对象进行封装,类似装饰者模式,只不过将其执行时的回调通过callbackExecutor进行回调到UI线程中去了。

    4.2.3 执行Call

    在4.2.2我们已经拿到了经过封装的ExecutorCallbackCall类型的call对象,实际上就是我们实际在写代码时拿到的call对象,那么我们一般会执行enqueue方法,看看源码是怎么做的

    首先是ExecutorCallbackCall.enqueue方法,代码在4.2.2,可以看到除了将onResponse和onFailure回调到UI线程,主要的操作还是delegate完成的,这个delegate实际上就是OkHttpCall对象,我们看它的enqueue方法

     @Override
    public void enqueue(final Callback<T> callback)
    {
        okhttp3.Call call;
        Throwable failure;
    
        synchronized (this)
        {
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;
    
            try
            {
                call = rawCall = createRawCall();
            } catch (Throwable t)
            {
                failure = creationFailure = t;
            }
        }
    
        if (failure != null)
        {
            callback.onFailure(this, failure);
            return;
        }
    
        if (canceled)
        {
            call.cancel();
        }
    
        call.enqueue(new okhttp3.Callback()
        {
            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
                    throws IOException
            {
                Response<T> response;
                try
                {
                    response = parseResponse(rawResponse);
                } catch (Throwable e)
                {
                    callFailure(e);
                    return;
                }
                callSuccess(response);
            }
    
            @Override
            public void onFailure(okhttp3.Call call, IOException e)
            {
                try
                {
                    callback.onFailure(OkHttpCall.this, e);
                } catch (Throwable t)
                {
                    t.printStackTrace();
                }
            }
    
            private void callFailure(Throwable e)
            {
                try
                {
                    callback.onFailure(OkHttpCall.this, e);
                } catch (Throwable t)
                {
                    t.printStackTrace();
                }
            }
    
            private void callSuccess(Response<T> response)
            {
                try
                {
                    callback.onResponse(OkHttpCall.this, response);
                } catch (Throwable t)
                {
                    t.printStackTrace();
                }
            }
        });
    }
    

    没有任何神奇的地方,内部实际上就是okhttp的Call对象,也是调用okhttp3.Call.enqueue方法。

    中间对于okhttp3.Call的创建代码为:

    private okhttp3.Call createRawCall() throws IOException
    {
        Request request = serviceMethod.toRequest(args);
        okhttp3.Call call = serviceMethod.callFactory.newCall(request);
        if (call == null)
        {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }

    可以看到,通过serviceMethod.toRequest完成对request的构建,通过request去构造call对象,然后返回.

    中间还涉及一个parseResponse方法,如果顺利的话,执行的代码如下:

    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException
    {
        ResponseBody rawBody = rawResponse.body();
        ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    
        T body = serviceMethod.toResponse(catchingBody);
        return Response.success(body, rawResponse);
    

    通过serviceMethod对ResponseBody进行转化,然后返回,转化实际上就是通过responseConverter的convert方法。

    #ServiceMethod
     T toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
      }

    ok,关于responseConverter后面还会细说,不用担心。

    到这里,我们整个源码的流程分析就差不多了,目的就掌握一个大体的原理和执行流程,了解下几个核心的类。

    那么总结一下:

    • 首先构造retrofit,几个核心的参数呢,主要就是baseurl,callFactory(默认okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
    • 然后通过create方法拿到接口的实现类,这里利用Java的Proxy类完成动态代理的相关代理
    • 在invoke方法内部,拿到我们所声明的注解以及实参等,构造ServiceMethod,ServiceMethod中解析了大量的信息,最痛可以通过toRequest构造出okhttp3.Request对象。有了okhttp3.Request对象就可以很自然的构建出okhttp3.call,最后calladapter对Call进行装饰返回。
    • 拿到Call就可以执行enqueue或者execute方法了

    ok,了解这么多足以。

    下面呢,有几个地方需要注意,一方面是一些特殊的细节;另一方面就是Converter

    五、retrofit中的各类细节

    (1)上传文件中使用的奇怪value值

    第一个问题涉及到文件上传,还记得我们在单文件上传那里所说的吗?有种类似于hack的写法,上传文件是这么做的?

    public interface ApiInterface {
            @Multipart
            @POST ("/api/Accounts/editaccount")
            Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username);
        }

    首先我们一点明确,因为这里使用了@ Multipart,那么我们认为@Part应当支持普通的key-value,以及文件。

    对于普通的key-value是没问题的,只需要这样@Part("username") String username

    那么对于文件,为什么需要这样呢?@Part("file_key\"; filename=\"pp.png")

    这个value设置的值不用看就会觉得特别奇怪,然而却可以正常执行,原因是什么呢?

    原因是这样的:

    当上传key-value的时候,实际上对应这样的代码:

    builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
                            RequestBody.create(null, params.get(key)));

    也就是说,我们的@Part转化为了

    Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")

    这么一看,很随意,只要把key放进去就可以了。

    但是,retrofit2并没有对文件做特殊处理,文件的对应的字符串应该是这样的

     Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");

    与键值对对应的字符串相比,多了个;filename="filename.png,就因为retrofit没有做特殊处理,所以你现在看这些hack的做法

    @Part("file_key\"; filename=\"pp.png")
    拼接:==>
    Content-Disposition", "form-data; name=\"" + key + "\"
    结果:==>
    Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"

    ok,到这里我相信你已经理解了,为什么要这么做,而且为什么这么做可以成功!

    恩,值得一提的事,因为这种方式文件名写死了,我们上文使用的的是@Part MultipartBody.Part file,可以满足文件名动态设置,这个方式貌似也是2.0.1的时候支持的。

    上述相关的源码:

    #ServiceMethod
    if (annotation instanceof Part) {
        if (!isMultipart) {
          throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
        }
        Part part = (Part) annotation;
        gotPart = true;
    
        String partName = part.value();
    
        Headers headers =
              Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"",
                  "Content-Transfer-Encoding", part.encoding());
    }

    可以看到呢,并没有对文件做特殊处理,估计下个版本说不定@Part会多个isFile=true|false属性,甚至修改对应形参,然后在这里做简单的处理。

    ok,最后来到关键的ConverterFactory了~

    六、自定义Converter.Factory

    (1)responseBodyConverter

    关于Converter.Factory,肯定是通过addConverterFactory设置的

    Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
            .build();

    该方法接受的是一个Converter.Factory factory对象

    该对象呢,是一个抽象类,内部包含3个方法:

    abstract class Factory {
    
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
            Retrofit retrofit) {
          return null;
        }
    
    
        public Converter<?, RequestBody> requestBodyConverter(Type type,
            Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
          return null;
        }
    
    
        public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
            Retrofit retrofit) {
          return null;
        }
      }

    可以看到呢,3个方法都是空方法而不是抽象的方法,也就表明了我们可以选择去实现其中的1个或多个方法,一般只需要关注requestBodyConverterresponseBodyConverter就可以了。

    ok,我们先看如何自定义,最后再看GsonConverterFactory.create的源码。

    先来个简单的,实现responseBodyConverter方法,看这个名字很好理解,就是将responseBody进行转化就可以了。

    ok,这里呢,我们先看一下上述中我们使用的接口:

    package com.zhy.retrofittest.userBiz;
    
    public interface IUserBiz
    {
        @GET("users")
        Call<List<User>> getUsers();
    
        @POST("users")
        Call<List<User>> getUsersBySort(@Query("sort") String sort);
    
        @GET("{username}")
        Call<User> getUser(@Path("username") String username);
    
        @POST("add")
        Call<List<User>> addUser(@Body User user);
    
        @POST("login")
        @FormUrlEncoded
        Call<User> login(@Field("username") String username, @Field("password") String password);
    
        @Multipart
        @POST("register")
        Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password);
    
        @Multipart
        @POST("register")
        Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
    
        @GET("download")
        Call<ResponseBody> downloadTest();
    
    }
    

    不知不觉,方法还蛮多的,假设哈,我们这里去掉retrofit构造时的GsonConverterFactory.create,自己实现一个Converter.Factory来做数据的转化工作。

    首先我们解决responseBodyConverter,那么代码很简单,我们可以这么写:

    public class UserConverterFactory extends Converter.Factory
    {
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
        {
            //根据type判断是否是自己能处理的类型,不能的话,return null ,交给后面的Converter.Factory
            return new UserConverter(type);
        }
    
    }
    
    public class UserResponseConverter<T> implements Converter<ResponseBody, T>
    {
        private Type type;
        Gson gson = new Gson();
    
        public UserResponseConverter(Type type)
        {
            this.type = type;
        }
    
        @Override
        public T convert(ResponseBody responseBody) throws IOException
        {
            String result = responseBody.string();
            T users = gson.fromJson(result, type);
            return users;
        }
    }
    

    使用的时候呢,可以

     Retrofit retrofit = new Retrofit.Builder()
    .callFactory(new OkHttpClient())               .baseUrl("http://example/springmvc_users/user/")
    //.addConverterFactory(GsonConverterFactory.create())
    .addConverterFactory(new UserConverterFactory())
                .build();

    ok,这样的话,就可以完成我们的ReponseBodyList<User>或者User的转化了。

    可以看出,我们这里用的依然是Gson,那么有些同学肯定不希望使用Gson就能实现,如果不使用Gson的话,一般需要针对具体的返回类型,比如我们针对返回List<User>或者User

    你可以这么写:

    package com.zhy.retrofittest.converter;
    /**
     * Created by zhy on 16/4/30.
     */
    public class UserResponseConverter<T> implements Converter<ResponseBody, T>
    {
        private Type type;
        Gson gson = new Gson();
    
        public UserResponseConverter(Type type)
        {
            this.type = type;
        }
    
        @Override
        public T convert(ResponseBody responseBody) throws IOException
        {
            String result = responseBody.string();
    
            if (result.startsWith("["))
            {
                return (T) parseUsers(result);
            } else
            {
                return (T) parseUser(result);
            }
        }
    
        private User parseUser(String result)
        {
            JSONObject jsonObject = null;
            try
            {
                jsonObject = new JSONObject(result);
                User u = new User();
                u.setUsername(jsonObject.getString("username"));
                return u;
            } catch (JSONException e)
            {
                e.printStackTrace();
            }
            return null;
        }
    
        private List<User> parseUsers(String result)
        {
            List<User> users = new ArrayList<>();
            try
            {
                JSONArray jsonArray = new JSONArray(result);
                User u = null;
                for (int i = 0; i < jsonArray.length(); i++)
                {
                    JSONObject jsonObject = jsonArray.getJSONObject(i);
                    u = new User();
                    u.setUsername(jsonObject.getString("username"));
                    users.add(u);
                }
            } catch (JSONException e)
            {
                e.printStackTrace();
            }
            return users;
        }
    }
    

    这里简单读取了一个属性,大家肯定能看懂,这样就能满足返回值是Call<List<User>>或者Call<User>.

    这里郑重提醒:如果你针对特定的类型去写Converter,一定要在UserConverterFactory#responseBodyConverter中对类型进行检查,发现不能处理的类型return null,这样的话,可以交给后面的Converter.Factory处理,比如本例我们可以按照下列方式检查:

    public class UserConverterFactory extends Converter.Factory
    {
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
        {
            //根据type判断是否是自己能处理的类型,不能的话,return null ,交给后面的Converter.Factory
            if (type == User.class)//支持返回值是User
            {
                return new UserResponseConverter(type);
            }
    
            if (type instanceof ParameterizedType)//支持返回值是List<User>
            {
                Type rawType = ((ParameterizedType) type).getRawType();
                Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
                if (rawType == List.class && actualType == User.class)
                {
                    return new UserResponseConverter(type);
                }
            }
            return null;
        }
    
    }

    好了,到这呢responseBodyConverter方法告一段落了,谨记就是将reponseBody->返回值返回中的实际类型,例如Call<User>中的User;还有对于该converter不能处理的类型一定要返回null。

    (2)requestBodyConverter

    ok,上面接口一大串方法呢,使用了我们的Converter之后,有个方法我们现在还是不支持的。

    @POST("add")
    Call<List<User>> addUser(@Body User user);

    ok,这个@Body需要用到这个方法,叫做requestBodyConverter,根据参数转化为RequestBody,下面看下我们如何提供支持。

    public class UserRequestBodyConverter<T> implements Converter<T, RequestBody>
    {
        private Gson mGson = new Gson();
        @Override
        public RequestBody convert(T value) throws IOException
        {
            String string = mGson.toJson(value);
            return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string);
        }
    }
    

    然后在UserConverterFactory中复写requestBodyConverter方法,返回即可:

    public class UserConverterFactory extends Converter.Factory
    {
    
        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
        {
            return new UserRequestBodyConverter<>();
        }
    }

    这里偷了个懒,使用Gson将对象转化为json字符串了,如果你不喜欢使用框架,你可以选择拼接字符串,或者反射写一个支持任何对象的,反正就是对象->json字符串的转化。最后构造一个RequestBody返回即可。

    ok,到这里,我相信如果你看的细致,自定义Converter.Factory是干嘛的,但是我还是要总结下:

    • responseBodyConverter 主要是对应@Body注解,完成ResponseBody到实际的返回类型的转化,这个类型对应Call<XXX>里面的泛型XXX,其实@Part等注解也会需要responseBodyConverter,只不过我们的参数类型都是RequestBody,由默认的converter处理了。
    • requestBodyConverter 完成对象到RequestBody的构造。
    • 一定要注意,检查type如果不是自己能处理的类型,记得return null (因为可以添加多个,你不能处理return null ,还会去遍历后面的converter).

    七、值得学习的API

    其实一般情况下看源码呢,可以让我们更好的去使用这个库,当然在看的过程中如果发现了一些比较好的处理方式呢,是非常值得记录的。如果每次看别人的源码都能吸取一定的精华,比你单纯的去理解会好很多,因为你的记忆力再好,源码解析你也是会忘的,而你记录下来并能够使用的优越的代码,可能用久了就成为你的代码了。

    我举个例子:比如retrofit2中判断当前运行的环境代码如下,如果下次你有这样的需求,你也可以这么写,甚至源码中根据不同的运行环境还提供了不同的Executor都很值得记录:

    class Platform {
      private static final Platform PLATFORM = findPlatform();
    
      static Platform get() {
        return PLATFORM;
      }
    
      private static Platform findPlatform() {
        try {
          Class.forName("android.os.Build");
          if (Build.VERSION.SDK_INT != 0) {
            return new Android();
          }
        } catch (ClassNotFoundException ignored) {
        }
        try {
          Class.forName("java.util.Optional");
          return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
        try {
          Class.forName("org.robovm.apple.foundation.NSObject");
          return new IOS();
        } catch (ClassNotFoundException ignored) {
        }
        return new Platform();
      }
    

    ok,文章结束,最后打个广告,个人微信公众号目前关注大概9000人左右,无奈个人博文更新频率无法做到很好的为这么多人服务,所以本公众号欢迎大家投稿,如果你希望你的文章可以被更多人看到,直接将md、doc等格式的文章发我邮箱即可(623565791@qq.com),也可以加我好友,需要注明(投稿),谢谢。

    文章要求:原创+你觉得通过文章你能学到一些有价值的东西。

    福利:您的文章还可以发到csdn,简书等其他平台,但不能投稿至其他微信公众号了;更多人能够发现你的文章(好文我会帮你发送至我的各大群)+您的署名+您的原文链接,如果后期有打赏收益均归投稿人所有。


    欢迎关注我的微博:
    http://weibo.com/u/3165018720


    群号:535687182,欢迎入群

    微信公众号:hongyangAndroid
    (欢迎关注,第一时间推送博文信息)
    1422600516_2905.jpg

    展开全文
  • Android Retrofit的学习

    千次阅读 2018-11-01 21:42:37
    Retrofit 在使用时其实就充当了一个适配器(Adapter)的角色,主要是将一个 Java 接口翻译成一个 HTTP 请求对象,然后用 OkHttp 去发送这个请求 核心思想:动态代理—通俗来讲,就是你要执行某个操作的前后需要增加...

    一、简介

    一个基于 OkHttp 的 RESTful API 请求工具

    Retrofit 在使用时其实就充当了一个适配器(Adapter)的角色,主要是将一个 Java 接口翻译成一个 HTTP 请求对象,然后用 OkHttp 去发送这个请求

    核心思想:动态代理—通俗来讲,就是你要执行某个操作的前后需要增加一些操作,比如查看用户个人信息前需要判断用户是否登录,用户访问数据库后想清除用户的访问记录等操作

    特别注意:

    • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
    • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

    • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
    • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

    Retrofit 主要定义了 4 个接口:

    • Callback<T>:请求数据的返回;
    • Converter<F, T>:对返回数据进行解析,一般用 GSON ;
    • Call<T>:发送请求,Retrofit 默认的实现是 OkHttpCall<T>,也可以依需自定义 Call<T>;
    • CallAdapter<T>:将 Call 对象转换成其他对象,如转换成支持 RxJava 的 Observable对象

    二、使用介绍

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
      }

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>

    步骤2:创建 接收服务器返回数据 的类

    Reception.java

    public class Reception {
        ...
        // 根据返回数据的格式和数据解析方式(Json、XML等)定义
        // 下面会在实例进行说明
            }

    步骤3:创建 用于描述网络请求 的接口

    • Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数
    •       用 动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,最后再执行 Http 请求
    •       注:接口中的每个方法的参数都需要使用注解标注,否则会报错

    GetRequest_Interface.java

    public interface GetRequest_Interface {
    
        @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
        Call<Translation>  getCall();
        // @GET注解的作用:采用Get方法发送网络请求
     
        // getCall() = 接收网络请求数据的方法
        // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
        // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
    }
    

    下面详细介绍Retrofit 网络请求接口 的注解类型。

    注解类型

     

    注解说明

    第一类:网络请求方法

    详细说明:
    a. @GET、@POST、@PUT、@DELETE、@HEAD
    以上方法分别对应 HTTP中的网络请求方式

    public interface GetRequest_Interface {
    
        @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
        Call<Translation>  getCall();
        // @GET注解的作用:采用Get方法发送网络请求
        // getCall() = 接收网络请求数据的方法
        // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
    }
    

    此处特意说明URL的组成:Retrofit把 网络请求的URL 分成了两部分设置:

    // 第1部分:在网络请求接口的注解设置
     @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    
    // 第2部分:在创建Retrofit实例时通过.baseUrl()设置
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
                    .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
                    .build();
    
    // 从上面看出:一个请求的URL可以通过 替换块 和 请求方法的参数 来进行动态的URL更新。
    // 替换块是由 被{}包裹起来的字符串构成
    // 即:Retrofit支持动态改变网络请求根目录
    
    
    • 网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置(下面称 “path“ )
    • 具体整合的规则如下:

    建议采用第三种方式来配置,并尽量使用同一种路径形式。

    b. @HTTP

    • 作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展
    • 具体使用:通过属性method、path、hasBody进行设置
    public interface GetRequest_Interface {
        /**
         * method:网络请求的方法(区分大小写)
         * path:网络请求地址路径
         * hasBody:是否有请求体
         */
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getCall(@Path("id") int id);
        // {id} 表示是一个变量
        // method 的值 retrofit 不会做处理,所以要自行保证准确
    }
    

    第二类:标记

    a. @FormUrlEncoded

    • 作用:表示发送form-encoded的数据

    每个键值对需要用@Filed来注解键名,随后的对象需要提供值。

    b. @Multipart

    • 作用:表示发送form-encoded的数据(适用于 有文件 上传的场景)

    每个键值对需要用@Part来注解键名,随后的对象需要提供值。

    具体使用如下:
    GetRequest_Interface

    public interface GetRequest_Interface {
            /**
             *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
             * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
             
            /**
             * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
             * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
    
    }
    
    // 具体使用
           GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
            // @FormUrlEncoded 
            Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
            
            //  @Multipart
            RequestBody name = RequestBody.create(textType, "Carson");
            RequestBody age = RequestBody.create(textType, "24");
    
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
            Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
    

    第三类:网络请求参数

    详细说明

    a. @Header & @Headers

    • 作用:添加请求头 &添加不固定的请求头
    • 具体使用如下:
    // @Header
    @GET("user")
    Call<User> getUser(@Header("Authorization") String authorization)
    
    // @Headers
    @Headers("Authorization: authorization")
    @GET("user")
    Call<User> getUser()
    
    // 以上的效果是一致的。
    // 区别在于使用场景和使用方式
    // 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
    // 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
    
    

    b. @Body

    • 作用:以 Post方式 传递 自定义数据类型 给服务器
    • 特别注意:如果提交的是一个Map,那么作用相当于 @Field

    不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:

    FormBody.Builder builder = new FormBody.Builder();
    builder.add("key","value");

    c. @Field & @FieldMap

    • 作用:发送 Post请求 时提交请求的表单字段
    • 具体使用:与 @FormUrlEncoded 注解配合使用
    public interface GetRequest_Interface {
            /**
             *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
             * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
    
    /**
             * Map的key作为表单的键
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
    
    }
    
    // 具体使用
             // @Field
            Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
    
            // @FieldMap
            // 实现的效果与上面相同,但要传入Map
            Map<String, Object> map = new HashMap<>();
            map.put("username", "Carson");
            map.put("age", 24);
            Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
    

    d. @Part & @PartMap

    • 作用:发送 Post请求 时提交请求的表单字段

    与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景

    • 具体使用:与 @Multipart 注解配合使用
    public interface GetRequest_Interface {
    
              /**
             * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
             * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
    
            /**
             * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
             * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
             * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
    
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
    }
    
    // 具体使用
     MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "Carson");
            RequestBody age = RequestBody.create(textType, "24");
            RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
    
            // @Part
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
            Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
            ResponseBodyPrinter.printResponseBody(call3);
    
            // @PartMap
            // 实现和上面同样的效果
            Map<String, RequestBody> fileUpload2Args = new HashMap<>();
            fileUpload2Args.put("name", name);
            fileUpload2Args.put("age", age);
            //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
            //fileUpload2Args.put("file", file);
            Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
            ResponseBodyPrinter.printResponseBody(call4);
    }
    

    e. @Query和@QueryMap

    • 作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)

    如:url = http://www.println.net/?cate=android,其中,Query = cate

    • 具体使用:配置时只需要在接口方法中增加一个参数即可:
    @GET("/")    
       Call<String> cate(@Query("cate") String cate);
    }
    
    // 其使用方式同 @Field与@FieldMap,这里不作过多描述

    f. @Path

    • 作用:URL地址的缺省值
    • 具体使用:
    public interface GetRequest_Interface {
    
            @GET("users/{user}/repos")
            Call<ResponseBody>  getBlog(@Path("user") String user );
            // 访问的API是:https://api.github.com/users/{user}/repos
            // 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
        }
    
    

    g. @Url

    • 作用:直接传入一个请求的 URL变量 用于URL设置
    • 具体使用:
    public interface GetRequest_Interface {
    
            @GET
            Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
           // 当有URL注解时,@GET传入的URL就可以省略
           // 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
    
    }
    

    汇总

     

    步骤4:创建 Retrofit 实例

    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址
                    .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台
                    .build();
    

    a. 关于数据解析器(Converter)

    • Retrofit支持多种数据解析方式
    • 使用时需要在Gradle添加依赖
    数据解析器 Gradle依赖
    Gson com.squareup.retrofit2:converter-gson:2.0.2
    Jackson com.squareup.retrofit2:converter-jackson:2.0.2
    Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
    Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
    Moshi com.squareup.retrofit2:converter-moshi:2.0.2
    Wire com.squareup.retrofit2:converter-wire:2.0.2
    Scalars com.squareup.retrofit2:converter-scalars:2.0.2

    b. 关于网络请求适配器(CallAdapter)

    • Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava

    使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加
    Retrofit 提供的 CallAdapter

    • 使用时需要在Gradle添加依赖:
    网络请求适配器 Gradle依赖
    guava com.squareup.retrofit2:adapter-guava:2.0.2
    Java8 com.squareup.retrofit2:adapter-java8:2.0.2
    rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

    步骤5:创建 网络请求接口实例

    // 创建 网络请求接口 的实例
            GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
    
            //对 发送请求 进行封装
            Call<Reception> call = request.getCall();
    

    步骤6:发送网络请求(异步 / 同步)

    封装了 数据转换、线程切换的操作

    //发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    //请求处理,输出结果
                    response.body().show();
                }
    
                //请求失败时候的回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
    
    // 发送网络请求(同步)
    Response<Reception> response = call.execute();
    
    

    步骤7:处理返回数据

    通过response类的 body()对返回的数据进行处理

    //发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    // 对返回数据进行处理
                    response.body().show();
                }
    
                //请求失败时候的回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
    
    // 发送网络请求(同步)
      Response<Reception> response = call.execute();
      // 对返回数据进行处理
      response.body().show();
    
    

     

    三、实例 基于以前的hello项目

    接下来,我将用两个实例分别对 Retrofit GET方式 和 POST方式进行 网络请求 讲解。

    • 实现功能:将中文翻译成英文
    • 实现方案:采用Get方法对 金山词霸API 发送网络请求

    采用 Gson 进行数据解析

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
      }

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>

    步骤2:创建 接收服务器返回数据 的类

    • 金山词霸API 的数据格式说明如下:
    // URL模板
    http://fy.iciba.com/ajax.php
    
    // URL实例
    http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world
    
    // 参数说明:
    // a:固定值 fy
    // f:原文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
    // t:译文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
    // w:查询内容
    

    • 根据 金山词霸API 的数据格式,创建 接收服务器返回数据 的类:

    Translation.java

    public class Translation {
            private int status;
    
        private content content;
        private static class content {
            private String from;
            private String to;
            private String vendor;
            private String out;
            private int errNo;
        }
    
        //定义 输出返回数据 的方法
        public void show() {
            System.out.println(status);
            
            System.out.println(content.from);
            System.out.println(content.to);
            System.out.println(content.vendor);
            System.out.println(content.out);
            System.out.println(content.errNo);
        }
    }
    
    

    步骤3:创建 用于描述网络请求 的接口

    采用 **注解 ** 描述 网络请求参数。
    GetRequest_Interface.java

    import retrofit2.Call;
    import retrofit2.http.GET;
    
    public interface GetRequest_Interface {
    
        @GET("ajax.php?a=fy&f=auto&t=auto&w=follow%20system")
        Call<Translation> getCall();
        // 注解里传入 网络请求 的部分URL地址
        // Retrofit把网络请求的URL分成了两部分:一部分放在Retrofit对象里,另一部分放在网络请求接口里
        // 如果接口里的url是一个完整的网址,那么放在Retrofit对象里的URL可以忽略
        // getCall()是接受网络请求数据的方法
    }

    接下来的步骤均在GetRequest.java内实现(看注释)

    步骤4:创建Retrofit对象
    步骤5:创建 网络请求接口 的实例
    步骤6:发送网络请求

    以最常用的 异步请求 为例

    步骤7:处理返回数据

    public class GetRequest extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            request();
            // 使用Retrofit封装的方法
        }
        public void request() {
    
            //步骤4:创建Retrofit对象
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
                    .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                    .build();
    
            // 步骤5:创建 网络请求接口 的实例
            GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
    
            //对 发送请求 进行封装
            Call<Translation> call = request.getCall();
    
            //步骤6:发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    // 步骤7:处理返回的数据结果
                    response.body().show();
                }
    
                //请求失败时回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
        }
    }
    

     由于此处采用了 Gson 解析,所以需要在 Gradle加入依赖
    build.gradle

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'

    AndroidManifest.xml中主程序改为

    <activity android:name=".GetRequest" ></activity>

     在主页面增加一个命令按钮,进行测试。

     

     

    四、实例2

    • 实现的功能:将 英文 翻译成 中文
    • 实现方法:采用Post方法对 有道API 发送网络请求

    采用 Gson 进行数据解析

     

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
      }

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>

    步骤2:创建 接收服务器返回数据 的类

    • API 的数据格式说明如下:
    // URL
    http://fanyi.youdao.com/translate
    
    // URL实例
    http://fanyi.youdao.com/translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=
    
    
    // 参数说明
    // doctype:json 或 xml
    // jsonversion:如果 doctype 值是 xml,则去除该值,若 doctype 值是 json,该值为空即可
    // xmlVersion:如果 doctype 值是 json,则去除该值,若 doctype 值是 xml,该值为空即可
    // type:语言自动检测时为 null,为 null 时可为空。英译中为 EN2ZH_CN,中译英为 ZH_CN2EN,日译中为 JA2ZH_CN,中译日为 ZH_CN2JA,韩译中为 KR2ZH_CN,中译韩为 ZH_CN2KR,中译法为 ZH_CN2FR,法译中为 FR2ZH_CN
    // keyform:mdict. + 版本号 + .手机平台。可为空
    // model:手机型号。可为空
    // mid:平台版本。可为空
    // imei:???。可为空
    // vendor:应用下载平台。可为空
    // screen:屏幕宽高。可为空
    // ssid:用户名。可为空
    // abtest:???。可为空
    
    // 请求方式说明
    // 请求方式:POST
    // 请求体:i
    // 请求格式:x-www-form-urlencoded
    

    • 根据 有道API 的数据格式,创建 接收服务器返回数据 的类:

    Translationyd.java

    public class Translationyd {
        private String type;
        private int errorCode;
        private int elapsedTime;
        private List<List<TranslateResultBean>> translateResult;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public int getErrorCode() {
            return errorCode;
        }
    
        public void setErrorCode(int errorCode) {
            this.errorCode = errorCode;
        }
    
        public int getElapsedTime() {
            return elapsedTime;
        }
    
        public void setElapsedTime(int elapsedTime) {
            this.elapsedTime = elapsedTime;
        }
    
        public List<List<TranslateResultBean>> getTranslateResult() {
            return translateResult;
        }
    
        public void setTranslateResult(List<List<TranslateResultBean>> translateResult) {
            this.translateResult = translateResult;
        }
    
        public static class TranslateResultBean {
            /**
             * src : merry me
             * tgt : 我快乐
             */
    
            public String src;
            public String tgt;
    
            public String getSrc() {
                return src;
            }
    
            public void setSrc(String src) {
                this.src = src;
            }
    
            public String getTgt() {
                return tgt;
            }
    
            public void setTgt(String tgt) {
                this.tgt = tgt;
            }
        }
    
    }
    

    步骤3:创建 用于描述网络请求 的接口

    采用 注解 描述 网络请求参数。

    PostRequest_Interface.java

    ublic interface PostRequest_Interface {
    
    
        @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
        @FormUrlEncoded
        Call<Translationyd> getCall(@Field("i") String targetSentence);
        //采用@Post表示Post方法进行请求(传入部分url地址)
        // 采用@FormUrlEncoded注解的原因:API规定采用请求格式x-www-form-urlencoded,即表单形式
        // 需要配合@Field使用
    }

    接下来的步骤均在PostRequest.java内实现(看注释)

    步骤4:创建Retrofit对象
    步骤5:创建 网络请求接口 的实例
    步骤6:发送网络请求

    以最常用的 异步请求 为例

    步骤7:处理返回数据

    PostRequest.java

    
    public class PostRequest extends AppCompatActivity {
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            request();
        }
        public void request() {
    
            //步骤4:创建Retrofit对象
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") // 设置 网络请求 Url
                    .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                    .build();
    
            // 步骤5:创建 网络请求接口 的实例
            PostRequest_Interface request = retrofit.create(PostRequest_Interface.class);
    
            //对 发送请求 进行封装(设置需要翻译的内容)
            Call<Translationyd> call = request.getCall("I love you");
    
            //步骤6:发送网络请求(异步)
            call.enqueue(new Callback<Translationyd>() {
    
                //请求成功时回调
                @Override
                public void onResponse(Call<Translationyd> call, Response<Translationyd> response) {
                    // 请求处理,输出结果
                    // 输出翻译的内容
                    System.out.println("翻译是:"+ response.body().getTranslateResult().get(0).get(0).getTgt());
                }
    
                //请求失败时回调
                @Override
                public void onFailure(Call<Translationyd> call, Throwable throwable) {
                    System.out.println("请求失败");
                    System.out.println(throwable.getMessage());
                }
            });
        }
    
    
    }

    由于此处采用了 Gson 解析,所以需要在 Gradle 加入依赖
    build.gradle

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'

     

     

     

    Retrofit 的拓展使用

    • Retrofit的使用场景非常丰富,如支持RxJavaPrototocobuff
    • 具体设置也非常简单 & 方便:
    <-- 主要在创建Retrofit对象中设置 -->
    Retrofit retrofit = new Retrofit.Builder()
      .baseUrl(""http://fanyi.youdao.com/"")
      .addConverterFactory(ProtoConverterFactory.create()) // 支持Prototocobuff解析
      .addConverterFactory(GsonConverterFactory.create()) // 支持Gson解析
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava
      .build();
    
    

     

    展开全文
  • Retrofit2详解

    万次阅读 多人点赞 2020-04-03 14:08:59
    Retrofit框架: 它是Square公司开发的现在非常流行的网络框架 retrofit2.0它依赖于OkHttp,在这里我们也不需要显示的导入okHttp,在retrofit中已经导入okhttp3 性能好,处理快,使用简单,Retrofit 是安卓上最...

    Retrofit介绍

    Retrofit框架: 它是Square公司开发的现在非常流行的网络框架

    retrofit2.0它依赖于OkHttp,在这里我们也不需要显示的导入okHttp,在retrofit中已经导入okhttp3
    性能好,处理快,使用简单,Retrofit 是安卓上最流行的HTTP Client库之一

    github地址

    使用步骤:
    1、定义一个接口(封装URL地址和数据请求)
    2、实例化Retrofit
    3、通过Retrofit实例创建接口服务对象
    4、接口服务对象调用接口中方法,获得Call对象
    5、Call对象执行请求(异步、同步请求)

    依赖:

         //retrofit2
        implementation 'com.squareup.retrofit2:retrofit:2.4.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
        implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    

    入门案例:

    测试Url:
    https://api.github.com/users/basil2style

      /**
         * 1.get无参请求
         * https://api.github.com/users/basil2style
         */
        @GET("basil2style")
        Call<ResponseBody> getbasil2style();
    
    public class Main2Activity extends AppCompatActivity {
    
        public String BASE_URL = "https://api.github.com/users/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main10);
    
            //1.创建Retrofit对象
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .build();
    
            //2.通过Retrofit实例创建接口服务对象
            ApiService apiService = retrofit.create(ApiService.class);
    
            //3.接口服务对象调用接口中方法,获得Call对象
            Call<ResponseBody> call = apiService.getbasil2style();
    
            //同步请求
            //Response<ResponseBody> bodyResponse = call.execute();
    
            //4.Call对象执行请求(异步、同步请求)
            call.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                    //onResponse方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui
                    if (response.isSuccessful()) {
                        try {
                            String string = response.body().string();
                            Log.e("xyh", "onResponse: " + string);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
    
                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
                    Log.e("xyh", "onFailure: " + t.getMessage());
                }
            });
    
            //call.cancel(); //取消
    
        }
    }
    

    请求结果:

    06-04 09:37:40.604 30233-30233/com.xiaoyehai.retrofit2 E/xyh: onResponse: {"login":"basil2style","id":1285344,"node_id":"MDQ6VXNlcjEyODUzNDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1285344?v=4","gravatar_id":"","url":"https://api.github.com/users/basil2style","html_url":"https://github.com/basil2style","followers_url":"https://api.github.com/users/basil2style/followers","following_url":"https://api.github.com/users/basil2style/following{/other_user}","gists_url":"https://api.github.com/users/basil2style/gists{/gist_id}","starred_url":"https://api.github.com/users/basil2style/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/basil2style/subscriptions","organizations_url":"https://api.github.com/users/basil2style/orgs","repos_url":"https://api.github.com/users/basil2style/repos","events_url":"https://api.github.com/users/basil2style/events{/privacy}","received_events_url":"https://api.github.com/users/basil2style/received_events","type":"User","site_admin":false,"name":"Basil Alias","company":"https://www.ribitt.io/","blog":"https://www.linkedin.com/in/basilalias92/","location":"Toronto,Canada","email":null,"hireable":true,"bio":"Developer | Marketer | Reader | Cinephile | Entrepreneur","public_repos":90,"public_gists":9,"followers":76,"following":193,"created_at":"2011-12-26T00:17:22Z","updated_at":"2018-05-25T16:30:14Z"}
    

    注解

    retrofit通过使用注解来简化请求,大体分为以下几类:
    1.用于标注请求方式的注解
    2.用于标记请求头的注解
    3.用于标记请求参数的注解
    4.用于标记请求和响应格式的注解

    请求方法注解:
    这里写图片描述
    这里写图片描述

    这里写图片描述

    定义一个接口(封装URL地址和数据请求)

    package com.xiaoyehai.retrofit2.api;
    
    import android.app.Activity;
    
    import com.xiaoyehai.retrofit2.bean.BaseEntity;
    import com.xiaoyehai.retrofit2.bean.GameInfo;
    import com.xiaoyehai.retrofit2.bean.NewsInfo;
    
    import java.util.List;
    import java.util.Map;
    
    import okhttp3.MultipartBody;
    import okhttp3.RequestBody;
    import okhttp3.ResponseBody;
    import retrofit2.Call;
    import retrofit2.http.Body;
    import retrofit2.http.Field;
    import retrofit2.http.FieldMap;
    import retrofit2.http.FormUrlEncoded;
    import retrofit2.http.GET;
    import retrofit2.http.HTTP;
    import retrofit2.http.Header;
    import retrofit2.http.Headers;
    import retrofit2.http.Multipart;
    import retrofit2.http.POST;
    import retrofit2.http.Part;
    import retrofit2.http.PartMap;
    import retrofit2.http.Path;
    import retrofit2.http.Query;
    import retrofit2.http.QueryMap;
    import retrofit2.http.Streaming;
    import retrofit2.http.Url;
    import rx.Observable;
    
    /**
     * Created by xiaoyehai on 2018/4/4 0004.
     * 定义一个接口(封装URL地址和数据请求)
     * <p>
     * 1. @FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
     * 2. @Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于 有文件 上传的场景
     * 3. @Streaming:表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用
     * 4. @HTTP:通用注解,可以替换以上所有的注解,其拥有三个属性:method,path,hasBody
     * <p>
     * 5. @Query	用于Get中指定参数
     * 6. @QueryMap	和Query使用类似
     * 7. @Path	用于url中的占位符
     * 8. @Url	指定请求路径
     * <p>
     * 9. @Filed	多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
     * 10. @FiledMap	和@Filed作用一致,用于不确定表单参数
     * 11. @Body	多用于post请求发送非表单数据,比如想要以post方式传递json格式数据
     * <p>
     * 12.@Part	用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
     * 13.@PartMap	用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传
     * <p>
     * 14 @Headers	用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
     * 15 @Header	作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头
     */
    
    
    public interface ApiService {
    
        /**
         * 1.get无参请求
         * https://api.github.com/users/basil2style
         */
        @GET("basil2style")
        Call<ResponseBody> getbasil2style();
    
        //Call<String> getbasil2style2();
    
        /**
         * 2.get有参请求
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @GET("news/php/varcache_getnews.php")
        Call<ResponseBody> getNewsInfo(@Query("id") String id,
                                       @Query("page") String page,
                                       @Query("plat") String plat,
                                       @Query("version") String version);
    
        /**
         * 3.gson转换器
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
    
        @GET("news/php/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfo2(@Query("id") String id,
                                                      @Query("page") String page,
                                                      @Query("plat") String plat,
                                                      @Query("version") String version);
    
        /**
         * 4.@Path:用于url中的占位符,所有在网址中的参数(URL的问号前面)
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @GET("news/{php}/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfoPath(@Path("php") String php,
                                                         @Query("id") String id,
                                                         @Query("page") String page,
                                                         @Query("plat") String plat,
                                                         @Query("version") String version);
    
        /**
         * 5.@QueryMap:参数太多时可以用@QueryMap封装参数,相当于多个@Query
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
    
        @GET("news/php/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfo3(@QueryMap Map<String, String> map);
    
    
        /**
         * 6.post请求;
         * url:http://zhushou.72g.com/app/game/game_list/
         * params:platform=2&page=1
         * FieldMap:多个参数时可以使用,类型@QueryMap
         * FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @FormUrlEncoded
        @POST("app/game/game_list/")
        Call<GameInfo> getGameInfo(@Field("platform") String platform,
                                   @Field("page") String page);
    
        /**
         * 7.body注解:上传json格式的数据
         *
         * @param url
         * @param Body
         * @return
         */
        @POST()
        Call<ResponseBody> getNewsInfoByBody(@Url String url, @Body RequestBody Body);
    
        /**
         * 直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
         *
         * @param url
         * @param newsInfo
         * @return
         */
        @POST("api/{url}/newsList")
        Call<BaseEntity<NewsInfo>> login(@Path("url") String url, @Body NewsInfo newsInfo);
    
    	//上传json格式的数据,也可以使用Map集合,加上body注解
    	 @POST("users/update")
        Observable<BaseResp<UserInfo>> updateUserInfo(@Header("token") String token, @Body Map<String, Object> map);
    
        /**
         * 8.若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。如
         *
         * @param url
         * @param map
         * @return
         */
        @GET
        Call<List<Activity>> getActivityList(@Url String url, @QueryMap Map<String, String> map);
    
        /**
         * 9.使用@Headers添加多个请求头
         * 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
         *
         * @param url
         * @param map
         * @return
         */
        @Headers({
                "User-Agent:android",
                "apikey:123456789",
                "Content-Type:application/json",
        })
        @POST()
        Call<BaseEntity<NewsInfo>> post(@Url String url, @QueryMap Map<String, String> map);
    
    
        /**
         * 10.@Header注解:
         * 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头
         *
         * @param token
         * @param activeId
         * @return
         */
        @GET("mobile/active")
        Call<BaseEntity<NewsInfo>> get(@Header("token") String token, @Query("id") int activeId);
    
    
        /**
         * 11.@HTTP注解:
         * method 表示请求的方法,区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getBlog(@Path("id") int id);
    
        /**
         * 12.Streaming注解:表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用
         */
        @Streaming
        @GET
        Call<BaseEntity<NewsInfo>> downloadPicture(@Url String fileUrl);
    
    
        ///上传单张图片//
    
        /**
         * Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于 有文件 上传的场景
         * Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
         * PartMap:用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传
         * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;
         *
         * @param file 服务器指定的上传图片的key值
         * @return
         */
    
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload1(@Part("file" + "\";filename=\"" + "test.png") RequestBody file);
    
        @Multipart
        @POST("xxxxx")
        Call<BaseEntity<NewsInfo>> upload2(@Part MultipartBody.Part file);
    
        //上传多张图片/
    
        /**
         * @param map
         * @return
         */
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload3(@PartMap Map<String, RequestBody> map);
    
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload4(@PartMap Map<String, MultipartBody.Part> map);
    
    
        //图文混传/
    
        /**
         * @param params
         * @param files
         * @return
         */
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload5(@FieldMap() Map<String, String> params,
                                           @PartMap() Map<String, RequestBody> files);
    
        /**
         * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;
         *
         * @param userName
         * @param passWord
         * @param file
         * @return
         */
        @Multipart
        @POST("xxxxx")
        Call<BaseEntity<NewsInfo>> upload6(@Part("username") RequestBody userName,
                                           @Part("password") RequestBody passWord,
                                           @Part MultipartBody.Part file);
    
    
        /**
         * 15.rxjava
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
    
        @GET("news/php/varcache_getnews.php")
        Observable<BaseEntity<List<NewsInfo>>> getNewsInfoByRxJava(@Query("id") String id,
                                                                   @Query("page") String page,
                                                                   @Query("plat") String plat,
                                                                   @Query("version") String version);
    
    }
    
    

    @GET :表明这是get请求
    @Query 用于Get中指定参数
    @QueryMap 和Query使用类似,用于不确定表单参数

    get有参请求案例:

      /**
         * 2.get有参请求
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @GET("news/php/varcache_getnews.php")
        Call<ResponseBody> getNewsInfo(@Query("id") String id,
                                       @Query("page") String page,
                                       @Query("plat") String plat,
                                       @Query("version") String version);
    
    /**
     * get有参请求
     * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
     */
    public class Main3Activity extends AppCompatActivity {
    
        public String BASE_URL = "http://qt.qq.com/php_cgi/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main3);
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .build();
    
            retrofit.create(ApiService.class)
                    .getNewsInfo("12", "0", "android", "9724")
                    .enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                            if (response.isSuccessful()) {
                                try {
                                    String string = response.body().string();
                                    Log.e("xyh", "onResponse: " + string);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
    
                        @Override
                        public void onFailure(Call<ResponseBody> call, Throwable t) {
                            Log.e("xyh", "onFailure: " + t.getMessage());
                        }
                    });
        }
    }
    

    gosn转换器:直接返回对象

    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
    
    /**
     * gson转换器:直接返回对象
     */
    public class Main4Activity extends AppCompatActivity {
    
        public String BASE_URL = "http://qt.qq.com/php_cgi/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main4);
    
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            retrofit.create(ApiService.class)
                    .getNewsInfo2("12", "0", "android", "9724")
                    .enqueue(new Callback<BaseEntity<List<NewsInfo>>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<List<NewsInfo>>> call, Response<BaseEntity<List<NewsInfo>>> response) {
                            BaseEntity<List<NewsInfo>> body = response.body();
                            List<NewsInfo> list = body.getList();
                            Log.e("xyh", "onResponse: " + list.size());
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<List<NewsInfo>>> call, Throwable t) {
    
                        }
                    });
        }
    }
    

    使用@QueryMap注解:

    
        /**
         * 5.@QueryMap:参数太多时可以用@QueryMap封装参数,相当于多个@Query
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
    
        @GET("news/php/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfo3(@QueryMap Map<String, String> map);
    
    /**
     * QueryMap注解:参数太多时可以用@QueryMap封装参数,相当于多个@Query
     */
    public class Main6Activity extends AppCompatActivity {
    
        public String BASE_URL = "http://qt.qq.com/php_cgi/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main6);
    
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            Map<String, String> map = new HashMap<>();
            map.put("id", "12");
            map.put("page", "0");
            map.put("plat", "android");
            map.put("version", "9724");
    
            retrofit.create(ApiService.class)
                    .getNewsInfo3(map)
                    .enqueue(new Callback<BaseEntity<List<NewsInfo>>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<List<NewsInfo>>> call, Response<BaseEntity<List<NewsInfo>>> response) {
                            BaseEntity<List<NewsInfo>> body = response.body();
                            List<NewsInfo> list = body.getList();
                            Log.e("xyh", "onResponse: " + list.size());
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<List<NewsInfo>>> call, Throwable t) {
    
                        }
                    });
        }
    }
    
    

    @Path :用于url中的占位符,所有在网址中的参数(URL的问号前面)

      /**
         * 4.@Path:用于url中的占位符,所有在网址中的参数(URL的问号前面)
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @GET("news/{php}/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfoPath(@Path("php") String php,
                                                         @Query("id") String id,
                                                         @Query("page") String page,
                                                         @Query("plat") String plat,
                                                         @Query("version") String version);
    
    /**
     * @Path注解:用于url中的占位符
     */
    public class Main5Activity extends AppCompatActivity {
    
        public String BASE_URL = "http://qt.qq.com/php_cgi/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main5);
    
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            retrofit.create(ApiService.class)
                    .getNewsInfoPath("php", "12", "0", "android", "9724")
                    .enqueue(new Callback<BaseEntity<List<NewsInfo>>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<List<NewsInfo>>> call, Response<BaseEntity<List<NewsInfo>>> response) {
                            BaseEntity<List<NewsInfo>> body = response.body();
                            List<NewsInfo> list = body.getList();
                            Log.e("xyh", "onResponse: " + list.size());
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<List<NewsInfo>>> call, Throwable t) {
    
                        }
                    });
        }
    }
    

    @Url:指定请求路径

        /**
         * 8.若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。如
         *
         * @param url
         * @param map
         * @return
         */
        @GET
        Call<List<Activity>> getActivityList(@Url String url, @QueryMap Map<String, String> map);
    

    @Filed: 多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
    @FiledMap :和@Filed作用一致,用于不确定表单参数
    @FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
    @Body:多用于post请求发送非表单数据,比如想要以post方式传递json格式数据

     /**
         * 6.post请求;
         * <p>
         * FieldMap:多个参数时可以使用,类型@QueryMap
         * FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
        @FormUrlEncoded
        @POST("news/php/varcache_getnews.php")
        Call<BaseEntity<List<NewsInfo>>> getNewsInfoByPost(@Field("id") String id,
                                                           @Field("page") String page,
                                                           @Field("plat") String plat,
                                                           @Field("version") String version);
    

    body注解:上传json数据

     /**
         * 7.body注解:上传json格式的数据
         *
         * @param url
         * @param Body
         * @return
         */
        @POST()
        Call<ResponseBody> getNewsInfoByBody(@Url String url, @Body RequestBody Body);
    
      Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            String json = "";
            RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
            retrofit.create(ApiService.class)
                    .getNewsInfoByBody("", body)
                    .enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<ResponseBody> call, Throwable t) {
    
                        }
                    });
    
    
     /**
         * 直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
         *
         * @param url
         * @param newsInfo
         * @return
         */
        @POST("api/{url}/newsList")
        Call<BaseEntity<NewsInfo>> login(@Path("url") String url, @Body NewsInfo newsInfo);
    

    @Headers:添加请求头,用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在。

     /**
         * 9.使用@Headers添加多个请求头
         * 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
         *
         * @param url
         * @param map
         * @return
         */
        @Headers({
                "User-Agent:android",
                "apikey:123456789",
                "Content-Type:application/json",
        })
        @POST()
        Call<BaseEntity<NewsInfo>> post(@Url String url, @QueryMap Map<String, String> map);
    

    @Header注解:作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头。

     /**
         * 10.@Header注解:
         * 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头
         *
         * @param token
         * @param activeId
         * @return
         */
        @GET("mobile/active")
        Call<BaseEntity<NewsInfo>> get(@Header("token") String token, @Query("id") int activeId);
    

    @HTTP注解:

     /**
         * 11.@HTTP注解:
         * method 表示请求的方法,区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getBlog(@Path("id") int id);
    

    Streaming注解:表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用

     /**
         * 12.Streaming注解:表示响应体的数据用流的方式返回,适用于返回的数据比较大,该注解在在下载大文件的特别有用
         */
        @Streaming
        @GET
        Call<BaseEntity<NewsInfo>> downloadPicture(@Url String fileUrl);
    

    上传单张图片案例:

     ///上传单张图片//
    
        /**
         * Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于 有文件 上传的场景
         * Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
         * PartMap:用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传
         * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;
         *
         * @param file 服务器指定的上传图片的key值
         * @return
         */
    
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload1(@Part("file" + "\";filename=\"" + "test.png") RequestBody file);
    
        @Multipart
        @POST("xxxxx")
        Call<BaseEntity<NewsInfo>> upload2(@Part MultipartBody.Part file);
    
    
     private void upLoadImage1() {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            File file = new File("");
            RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
    
            retrofit.create(ApiService.class)
                    .upload1(requestBody)
                    .enqueue(new Callback<BaseEntity<NewsInfo>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
    
                        }
                    });
        }
    
        private void upLoadImage2() {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            File file = new File("");
            RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
            MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", file.getName(), photoRequestBody);
    
            retrofit.create(ApiService.class)
                    .upload2(photo)
                    .enqueue(new Callback<BaseEntity<NewsInfo>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
    
                        }
                    });
        }
    

    上传多张图片

      //上传多张图片/
    
        /**
         * @param map
         * @return
         */
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload3(@PartMap Map<String, RequestBody> map);
    
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload4(@PartMap Map<String, MultipartBody.Part> map);
    
      private void upLoadImage3() {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            //图片集合
            List<File> files = new ArrayList<>();
    
            Map<String, RequestBody> map = new HashMap<>();
            for (int i = 0; i < files.size(); i++) {
                RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), files.get(i));
                map.put("file" + i + "\";filename=\"" + files.get(i).getName(), requestBody);
            }
    
            retrofit.create(ApiService.class)
                    .upload3(map)
                    .enqueue(new Callback<BaseEntity<NewsInfo>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
    
                        }
                    });
        }
    
        private void upLoadImage4() {
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            Map<String, MultipartBody.Part> map = new HashMap<>();
    
            File file1 = new File("");
            RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file1);
            MultipartBody.Part photo1 = MultipartBody.Part.createFormData("上传的key1", file1.getName(), photoRequestBody);
            map.put("上传的key1", photo1);
    
            File file2 = new File("");
            RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
            MultipartBody.Part photo2 = MultipartBody.Part.createFormData("上传的key2", file2.getName(), photoRequestBody2);
            map.put("上传的key2", photo2);
    
            retrofit.create(ApiService.class)
                    .upload4(map)
                    .enqueue(new Callback<BaseEntity<NewsInfo>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
    
                        }
                    });
        }
    

    图文混传

    
       //图文混传/
    
        /**
         * @param params
         * @param files
         * @return
         */
        @Multipart
        @POST("upload/upload")
        Call<BaseEntity<NewsInfo>> upload5(@FieldMap() Map<String, String> params,
                                           @PartMap() Map<String, RequestBody> files);
    
        /**
         * Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;
         *
         * @param userName
         * @param passWord
         * @param file
         * @return
         */
        @Multipart
        @POST("xxxxx")
        Call<BaseEntity<NewsInfo>> upload6(@Part("username") RequestBody userName,
                                           @Part("password") RequestBody passWord,
                                           @Part MultipartBody.Part file);
    
      private void upload6() {
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("")
                    .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                    .build();
    
            //RequestBody startTowerId = RequestBody.create(MediaType.parse("multipart/form-data"), "xx");
    
            MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "二傻子");
            RequestBody pass = RequestBody.create(textType, "123456");
    
            File file = new File("");
            RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
            MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", file.getName(), photoRequestBody);
    
            //multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
            //        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
            //        MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
            //
            //        String descriptionString = "hello, 这是文件描述";
            //        RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
    
    
            retrofit.create(ApiService.class)
                    .upload6(name, pass, photo)
                    .enqueue(new Callback<BaseEntity<NewsInfo>>() {
                        @Override
                        public void onResponse(Call<BaseEntity<NewsInfo>> call, Response<BaseEntity<NewsInfo>> response) {
    
                        }
    
                        @Override
                        public void onFailure(Call<BaseEntity<NewsInfo>> call, Throwable t) {
    
                        }
                    });
        }
    
       /**
         * 15.rxjava
         * http://qt.qq.com/php_cgi/news/php/varcache_getnews.php?id=12&page=0&plat=android&version=9724
         */
    
        @GET("news/php/varcache_getnews.php")
        Observable<BaseEntity<List<NewsInfo>>> getNewsInfoByRxJava(@Query("id") String id,
                                                                   @Query("page") String page,
                                                                   @Query("plat") String plat,
                                                                   @Query("version") String version);
    

    创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。

    Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。

    有些特殊情况可以不以/结尾,比如 其实这个 URL https://www.baidu.com?key=value用来作为baseUrl其实是可行的,因为这个URL隐含的路径就是 /(斜线,代表根目录) ,而后面的?key=value在拼装请求时会被丢掉所以写上也没用。之所以 Retrofit 2 在文档上要求必须以 /(斜线) 结尾的要求想必是要消除歧义以及简化规则。

    自定义一个转换器,把请求到的数据转换成字符串

       @GET("news/php/varcache_getnews.php")
        Call<String> getNewsInfoStr(@Query("id") String id,
                                       @Query("page") String page,
                                       @Query("plat") String plat,
                                       @Query("version") String version);
    
    /**
     * 自定义返回数据转换器
     */
    public class Main11Activity extends AppCompatActivity {
    
        public String BASE_URL = "http://qt.qq.com/php_cgi/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main11);
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(new ToStringConverterFactory())
                    .build();
    
            ApiService apiService = retrofit.create(ApiService.class);
    
            apiService.getNewsInfoStr("12", "0", "android", "9724")
                    .enqueue(new Callback<String>() {
                        @Override
                        public void onResponse(Call<String> call, Response<String> response) {
                            String body = response.body();
                        }
    
                        @Override
                        public void onFailure(Call<String> call, Throwable t) {
    
                        }
                    });
        }
    
        /**
         * 定义一个转换器,把请求到的数据转换成字符串
         */
        private class ToStringConverterFactory extends Converter.Factory {
    
            private final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
    
            @Override
            public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
                if (String.class.equals(type)) {
                    return new Converter<ResponseBody, String>() {
                        @Override
                        public String convert(ResponseBody value) throws IOException {
                            return value.string();
                        }
                    };
                }
                return null;
            }
    
            @Override
            public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations,
                                                                  Annotation[] methodAnnotations, Retrofit retrofit) {
                if (String.class.equals(type)) {
                    return new Converter<String, RequestBody>() {
                        @Override
                        public RequestBody convert(String value) throws IOException {
                            return RequestBody.create(MEDIA_TYPE, value);
                        }
                    };
                }
                return null;
            }
        }
    }
    

    在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,
    这也是为什么我在前面的例子接口的返回值都是 Call,
    但如果响应体只是支持转换为ResponseBody的话何必要引入泛型呢,
    返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,
    而Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型.

    Retrofit封装

    /**
     * Created by : xiaoyehai
     * description :Retrofit的封装
     */
    public class RetrofitManager {
    
        private static RetrofitManager mInstance;
    
        private final Retrofit mRetrofit;
    
        private RetrofitManager() {
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(ConstantUrls.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(getOkhttpClient())
                    .build();
    
        }
    
        public static RetrofitManager getInstance() {
            if (mInstance == null) {
                synchronized (RetrofitManager.class) {
                    if (mInstance == null) {
                        mInstance = new RetrofitManager();
                    }
                }
            }
            return mInstance;
        }
    
        private OkHttpClient getOkhttpClient() {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            if (BuildConfig.DEBUG) {
                builder.addInterceptor(getHttpLoggingInterceptor()); //日志拦截器
            }
            return builder
                    // .addInterceptor(getInterceptor()) //通用拦截器
                    .connectTimeout(20, TimeUnit.SECONDS) //设置连接超时时间
                    .readTimeout(20, TimeUnit.SECONDS) //设置读取超时时间
                    .retryOnConnectionFailure(true)
                    .build();
    
        }
    
    
        /**
         * 日志拦截器
         *
         * @return
         */
        private Interceptor getHttpLoggingInterceptor() {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            return interceptor;
        }
    
        /**
         * 通用拦截器
         * 根据自己项目的需求添加公共的请求头和请求参数
         *
         * @return
         */
        private Interceptor getInterceptor() {
            Interceptor interceptor = new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request()
                            .newBuilder()
                            .addHeader("token", "xxx")
                            .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
                            .addHeader("Accept-Encoding", "gzip, deflate")
                            .addHeader("Connection", "keep-alive")
                            .addHeader("Accept", "*/*")
                            .addHeader("Cookie", "add cookies here")
                            .build();
                    return chain.proceed(request);
                }
            };
            return interceptor;
        }
    
        public <T> T create(Class<T> clazz) {
            return mRetrofit.create(clazz);
        }
    }
    
    

    使用retrofit做为网络请求时,解决多个BaseURL切换的问题

    项目中使用Retrofit进行请求时,后台接口的域名有多个:

     public static final String BASE_URL_APP = "https://app.tjinzhu.com/";
        public static final String BASE_URL_H5 = "https://res.tjinzhu.com/";
        public static final String BASE_URL_MAT = "https://mat.tjinzhu.com/";
        public static final String BASE_URL_LOGIN = "https://login.tjinzhu.com/";
        public static final String BASE_URL_HOME_MGI = "https://mgi.sitezt.cn/";
        public static final String BASE_URL_HOME_MGAPP = "https://mgapp.sitezt.cn/";
    
    

    在service代码中添加@Headers():

    //默认baseurl
    @GET("api/tjz/v1/tao/assets")
    Observable<BaseResp<UserAssets>> getUserAssets(@Header("token") String token);
    
     @Headers({"baseurl:mat"})
    @GET("api/tjzadmin/v1/appver/new")
    Observable<BaseResp<AppVersionResp>> getAppVersionInfo();
    
     @Headers({"baseurl:homeapp"})
    @GET("api/info/item/getdetailpics")
    Observable<List<GoodsDetailPicInfo>> getDetailpics(@Query("itemId") String itemId);
    

    添加okhttpclient拦截器,捕获添加的Headers,然后修改baseURL

    /**
     * Cerated by xiaoyehai
     * Create date : 2020/4/3 11:17
     * description : okhttpclient拦截器,捕获添加的Headers,然后修改baseURL
     */
    public class BaseUrlInterceptor implements Interceptor {
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            //获取request
            Request request = chain.request();
            //从request中获取原有的HttpUrl实例oldHttpUrl
            HttpUrl oldHttpUrl = request.url();
            //获取request的创建者builder
            Request.Builder builder = request.newBuilder();
            //从request中获取headers,通过给定的键url_name
            List<String> headerValues = request.headers("baseurl");
            if (headerValues != null && headerValues.size() > 0) {
                //如果有这个header,先将配置的header删除,因此header仅用作app和okhttp之间使用
                builder.removeHeader("baseurl");
                //匹配获得新的BaseUrl
                String headerValue = headerValues.get(0);
                HttpUrl newBaseUrl = null;
                if ("game".equals(headerValue)) {
                    newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_LOGIN);
                } else if ("homeapp".equals(headerValue)) {
                    newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_HOME_MGAPP);
                } else if ("homeagi".equals(headerValue)) {
                    newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_HOME_MGI);
                } else if ("mat".equals(headerValue)) {
                    newBaseUrl = HttpUrl.parse(ConstantUrls.BASE_URL_MAT);
                } else {
                    newBaseUrl = oldHttpUrl;
                }
                //重建新的HttpUrl,修改需要修改的url部分
                HttpUrl newFullUrl = oldHttpUrl
                        .newBuilder()
                        .scheme("https")//更换网络协议
                        .host(newBaseUrl.host())//更换主机名
                        .port(newBaseUrl.port())//更换端口
                        //.removePathSegment(0)//移除第一个参数(根据baseurl移除相关参数)
                        //.removePathSegment(1)//移除第二个参数(根据baseurl移除相关参数)
                        //.removePathSegment(2)//移除第三个参数(根据baseurl移除相关参数)
                        .build();
    
    
                //重建这个request,通过builder.url(newFullUrl).build();
                // 然后返回一个response至此结束修改
                Log.e("xyh1", "intercept: " + newFullUrl.toString());
                return chain.proceed(builder.url(newFullUrl).build());
            }
            return chain.proceed(request);
        }
    
    }
    
    

    在okhttpclient中设置

     private OkHttpClient getOkhttpClient() {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            if (BuildConfig.DEBUG) {
                builder.addInterceptor(getHttpLoggingInterceptor()); //日志拦截器
            }
            return builder
                   // .addInterceptor(getInterceptor()) //通用拦截器
                    .addInterceptor(new BaseUrlInterceptor()) //多个baseurl动态切换
                    .connectTimeout(20, TimeUnit.SECONDS) //设置连接超时时间
                    .readTimeout(20, TimeUnit.SECONDS) //设置读取超时时间
                    .retryOnConnectionFailure(true)
                    .build();
    
        }
    

    使用okhttp拦截器添加公共的参数

    /**
     * 公共参数的封装
     */
    
    public class CommonParamsInterceptor implements Interceptor {
    
        public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    
    
        private Gson mGson;
        private Context mContext;
    
        public CommonParamsInterceptor(Context context, Gson gson) {
            this.mContext = context;
            this.mGson = gson;
    
        }
    
    
        @Override
        public Response intercept(Chain chain) throws IOException {
    
            //拦截请求,获取到原始请求的request
            Request request = chain.request(); // 112.124.22.238:8081/course_api/cniaoplay/featured?p={'page':0}
    
            try {
                String method = request.method();
    
                //公共参数
                HashMap<String, Object> commomParamsMap = new HashMap<>();
                //commomParamsMap.put(Constants.IMEI, DeviceUtils.getIMEI(mContext));
                commomParamsMap.put(Constants.IMEI, "23b12e30ec8a2f17");
                commomParamsMap.put(Constants.MODEL, DeviceUtils.getModel());
                commomParamsMap.put(Constants.LANGUAGE, DeviceUtils.getLanguage());
                commomParamsMap.put(Constants.os, DeviceUtils.getBuildVersionIncremental());
                commomParamsMap.put(Constants.RESOLUTION, DensityUtil.getScreenW(mContext) + "*" + DensityUtil.getScreenH(mContext));
                commomParamsMap.put(Constants.SDK, DeviceUtils.getBuildVersionSDK() + "");
                commomParamsMap.put(Constants.DENSITY_SCALE_FACTOR, mContext.getResources().getDisplayMetrics().density + "");
    
                //token一般写在header
                String token = ACache.get(mContext).getAsString(Constants.TOKEN);
                commomParamsMap.put(Constants.TOKEN, token == null ? "" : token);
    
                if (method.equals("GET")) {
    
                    HttpUrl httpUrl = request.url();
    
                    HashMap<String, Object> rootMap = new HashMap<>();
    
                    Set<String> paramNames = httpUrl.queryParameterNames();
    
                    for (String key : paramNames) {
    
                        if (Constants.PARAM.equals(key)) {
    
                            //{'page':0}
                            String oldParamJson = httpUrl.queryParameter(Constants.PARAM);
                            if (oldParamJson != null) {
                                HashMap<String, Object> p = mGson.fromJson(oldParamJson, HashMap.class); // 原始参数
    
                                if (p != null) {
                                    for (Map.Entry<String, Object> entry : p.entrySet()) {
    
                                        rootMap.put(entry.getKey(), entry.getValue());
                                    }
                                }
                            }
                        } else {
                            rootMap.put(key, httpUrl.queryParameter(key));
                        }
                    }
    
                    //{"publicParams":{"resolution":"1080*1776","sdk":"23","la":"zh","densityScaleFactor":"3.0","imei":"A0000059953B34","os":"C92B437","model":"KIW-CL00"}}
    
                    rootMap.put("publicParams", commomParamsMap); // 重新组装
                    String newJsonParams = mGson.toJson(rootMap); // {"page":0,"publicParams":{"imei":'xxxxx',"sdk":14,.....}}
    
    
                    String url = httpUrl.toString();
    
                    int index = url.indexOf("?");
                    if (index > 0) {
                        url = url.substring(0, index);
                    }
    
                    url = url + "?" + Constants.PARAM + "=" + newJsonParams; //  http://112.124.22.238:8081/course_api/cniaoplay/featured?p= {"page":0,"publicParams":{"imei":'xxxxx',"sdk":14,.....}}
    
                    Log.e("xyh", "intercept: " + url);
    
                    request = request.newBuilder().url(url).build();
    
                } else if (method.equals("POST")) {
    
                    RequestBody body = request.body();
    
                    HashMap<String, Object> rootMap = new HashMap<>();
                    if (body instanceof FormBody) { // form 表单
    
                        for (int i = 0; i < ((FormBody) body).size(); i++) {
    
                            rootMap.put(((FormBody) body).encodedName(i), ((FormBody) body).encodedValue(i));
                        }
    
                    } else {  //提交json
    
                        Buffer buffer = new Buffer();
    
                        body.writeTo(buffer);
    
                        String oldJsonParams = buffer.readUtf8();
    
                        if (!TextUtils.isEmpty(oldJsonParams)) {
    
                            rootMap = mGson.fromJson(oldJsonParams, HashMap.class); // 原始参数
    
                            if (rootMap != null) {
                                rootMap.put("publicParams", commomParamsMap); // 重新组装
                                String newJsonParams = mGson.toJson(rootMap); // {"page":0,"publicParams":{"imei":'xxxxx',"sdk":14,.....}}
                                Log.e("xyh", "newJsonParams: " + newJsonParams);
                                request = request.newBuilder().post(RequestBody.create(JSON, newJsonParams)).build();
                            }
                        }
    
                    }
    
                }
            } catch (JsonSyntaxException e) {
                e.printStackTrace();
            }
    
            return chain.proceed(request);
        }
    }
    
    
    展开全文
  • 前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求库中,Retrofit是当下... 如果对Retrofit v2.0的源码感兴趣,可看文章:Android:手把手带你深入剖析 Retrofit 2.0 源码 目录![目录](http://upload-

    前言

    • Andrroid开发中,网络请求十分常用
    • 而在Android网络请求库中,Retrofit是当下最热的一个网络请求库

    Github截图

    • 今天,我将献上一份非常详细Retrofit v2.0的使用教程,希望你们会喜欢。

    如果对Retrofit v2.0的源码感兴趣,可看文章:Android:手把手带你深入剖析 Retrofit 2.0 源码


    目录

    目录


    1. 简介

    Retrofit简介

    特别注意:

    • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
    • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

    本质过程

    • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
    • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

    2. 与其他开源请求库对比

    除了Retrofit,如今Android中主流的网络请求框架有:

    • Android-Async-Http
    • Volley
    • OkHttp

    下面是简单介绍:

    网络请求加载 - 介绍

    一图让你了解全部的网络请求库和他们之间的区别!

    网络请求库 - 对比


    附:各个主流网络请求库的Github地址


    3. 使用介绍

    使用 Retrofit 的步骤共有7个:

    步骤1:添加Retrofit库的依赖
    步骤2:创建 接收服务器返回数据 的类
    步骤3:创建 用于描述网络请求 的接口
    步骤4:创建 Retrofit 实例
    步骤5:创建 网络请求接口实例 并 配置网络请求参数
    步骤6:发送网络请求(异步 / 同步)

    封装了 数据转换、线程切换的操作

    步骤7:处理服务器返回的数据

    接下来,我们一步步进行讲解。

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
        compile 'com.squareup.okhttp3:okhttp:3.1.2'
        // Okhttp库
      }
    

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>
    

    步骤2:创建 接收服务器返回数据 的类

    Reception.java

    public class Reception {
        ...
        // 根据返回数据的格式和数据解析方式(Json、XML等)定义
        // 下面会在实例进行说明
            }
    

    步骤3:创建 用于描述网络请求 的接口

    • Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数
    1. 用 动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,最后再执行 Http 请求
    2. 注:接口中的每个方法的参数都需要使用注解标注,否则会报错

    GetRequest_Interface.interface

    public interface GetRequest_Interface {
    
        @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
        Call<Translation>  getCall();
        // @GET注解的作用:采用Get方法发送网络请求
     
        // getCall() = 接收网络请求数据的方法
        // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
        // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
    }
    

    下面详细介绍Retrofit 网络请求接口 的注解类型。

    注解类型

    注解类型

    注解说明

    第一类:网络请求方法

    网络请求方法注解

    详细说明:
    a. @GET、@POST、@PUT、@DELETE、@HEAD
    以上方法分别对应 HTTP中的网络请求方式

    public interface GetRequest_Interface {
    
        @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
        Call<Translation>  getCall();
        // @GET注解的作用:采用Get方法发送网络请求
        // getCall() = 接收网络请求数据的方法
        // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
    }
    

    此处特意说明URL的组成:Retrofit把 网络请求的URL 分成了两部分设置:

    // 第1部分:在网络请求接口的注解设置
     @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    
    // 第2部分:在创建Retrofit实例时通过.baseUrl()设置
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
                    .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
                    .build();
    
    // 从上面看出:一个请求的URL可以通过 替换块 和 请求方法的参数 来进行动态的URL更新。
    // 替换块是由 被{}包裹起来的字符串构成
    // 即:Retrofit支持动态改变网络请求根目录
    
    • 网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置(下面称 “path“ )
    • 具体整合的规则如下:

    URL整合规则

    建议采用第三种方式来配置,并尽量使用同一种路径形式。

    b. @HTTP

    • 作用:替换**@GET、@POST、@PUT、@DELETE、@HEAD**注解的作用 及 更多功能拓展
    • 具体使用:通过属性method、path、hasBody进行设置
    public interface GetRequest_Interface {
        /**
         * method:网络请求的方法(区分大小写)
         * path:网络请求地址路径
         * hasBody:是否有请求体
         */
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getCall(@Path("id") int id);
        // {id} 表示是一个变量
        // method 的值 retrofit 不会做处理,所以要自行保证准确
    }
    

    第二类:标记

    标记类注解

    a. @FormUrlEncoded

    • 作用:表示发送form-encoded的数据

    每个键值对需要用@Filed来注解键名,随后的对象需要提供值。

    b. @Multipart

    • 作用:表示发送form-encoded的数据(适用于 有文件 上传的场景)

    每个键值对需要用@Part来注解键名,随后的对象需要提供值。

    具体使用如下:
    GetRequest_Interface

    public interface GetRequest_Interface {
            /**
             *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
             * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
             
            /**
             * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
             * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
    
    }
    
    // 具体使用
           GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
            // @FormUrlEncoded 
            Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
            
            //  @Multipart
            RequestBody name = RequestBody.create(textType, "Carson");
            RequestBody age = RequestBody.create(textType, "24");
    
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
            Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
    

    第三类:网络请求参数

    网络请求参数注解

    详细说明

    a. @Header & @Headers

    • 作用:添加请求头 &添加不固定的请求头
    • 具体使用如下:
    // @Header
    @GET("user")
    Call<User> getUser(@Header("Authorization") String authorization)
    
    // @Headers
    @Headers("Authorization: authorization")
    @GET("user")
    Call<User> getUser()
    
    // 以上的效果是一致的。
    // 区别在于使用场景和使用方式
    // 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
    // 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
    

    b. @Body

    • 作用:以 Post方式 传递 自定义数据类型 给服务器
    • 特别注意:如果提交的是一个Map,那么作用相当于 @Field

    不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:

    FormBody.Builder builder = new FormBody.Builder();
    builder.add("key","value");
    
    

    c. @Field & @FieldMap

    • 作用:发送 Post请求 时提交请求的表单字段
    • 具体使用:与 @FormUrlEncoded 注解配合使用
    public interface GetRequest_Interface {
            /**
             *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
             * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
    
    /**
             * Map的key作为表单的键
             */
            @POST("/form")
            @FormUrlEncoded
            Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
    
    }
    
    // 具体使用
             // @Field
            Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
    
            // @FieldMap
            // 实现的效果与上面相同,但要传入Map
            Map<String, Object> map = new HashMap<>();
            map.put("username", "Carson");
            map.put("age", 24);
            Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
    

    d. @Part & @PartMap

    • 作用:发送 Post请求 时提交请求的表单字段

    与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景

    • 具体使用:与 @Multipart 注解配合使用
    public interface GetRequest_Interface {
    
              /**
             * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
             * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
    
            /**
             * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
             * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
             * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
             */
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
    
            @POST("/form")
            @Multipart
            Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
    }
    
    // 具体使用
     MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "Carson");
            RequestBody age = RequestBody.create(textType, "24");
            RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
    
            // @Part
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
            Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
            ResponseBodyPrinter.printResponseBody(call3);
    
            // @PartMap
            // 实现和上面同样的效果
            Map<String, RequestBody> fileUpload2Args = new HashMap<>();
            fileUpload2Args.put("name", name);
            fileUpload2Args.put("age", age);
            //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
            //fileUpload2Args.put("file", file);
            Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
            ResponseBodyPrinter.printResponseBody(call4);
    }
    
    

    e. @Query和@QueryMap

    • 作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)

    如:url = http://www.println.net/?cate=android,其中,Query = cate

    • 具体使用:配置时只需要在接口方法中增加一个参数即可:
       @GET("/")    
       Call<String> cate(@Query("cate") String cate);
    }
    
    // 其使用方式同 @Field与@FieldMap,这里不作过多描述
    
    

    f. @Path

    • 作用:URL地址的缺省值
    • 具体使用:
    public interface GetRequest_Interface {
    
            @GET("users/{user}/repos")
            Call<ResponseBody>  getBlog(@Path("user") String user );
            // 访问的API是:https://api.github.com/users/{user}/repos
            // 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
        }
    

    g. @Url

    • 作用:直接传入一个请求的 URL变量 用于URL设置
    • 具体使用:
    public interface GetRequest_Interface {
    
            @GET
            Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
           // 当有URL注解时,@GET传入的URL就可以省略
           // 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
    
    }
    

    汇总

    汇总

    步骤4:创建 Retrofit 实例

     Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址
                    .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台
                    .build();
    

    a. 关于数据解析器(Converter)

    • Retrofit支持多种数据解析方式
    • 使用时需要在Gradle添加依赖
    数据解析器 Gradle依赖
    Gson com.squareup.retrofit2:converter-gson:2.0.2
    Jackson com.squareup.retrofit2:converter-jackson:2.0.2
    Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
    Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
    Moshi com.squareup.retrofit2:converter-moshi:2.0.2
    Wire com.squareup.retrofit2:converter-wire:2.0.2
    Scalars com.squareup.retrofit2:converter-scalars:2.0.2

    b. 关于网络请求适配器(CallAdapter)

    • Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava

    使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加
    Retrofit 提供的 CallAdapter

    • 使用时需要在Gradle添加依赖:
    网络请求适配器 Gradle依赖
    guava com.squareup.retrofit2:adapter-guava:2.0.2
    Java8 com.squareup.retrofit2:adapter-java8:2.0.2
    rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

    步骤5:创建 网络请求接口实例

            // 创建 网络请求接口 的实例
            GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
    
            //对 发送请求 进行封装
            Call<Reception> call = request.getCall();
    

    步骤6:发送网络请求(异步 / 同步)

    封装了 数据转换、线程切换的操作

    //发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    //请求处理,输出结果
                    response.body().show();
                }
    
                //请求失败时候的回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
    
    // 发送网络请求(同步)
    Response<Reception> response = call.execute();
    
    

    步骤7:处理返回数据

    通过response类的 body()对返回的数据进行处理

    //发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    // 对返回数据进行处理
                    response.body().show();
                }
    
                //请求失败时候的回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
    
    // 发送网络请求(同步)
      Response<Reception> response = call.execute();
      // 对返回数据进行处理
      response.body().show();
    
    

    4. 实例讲解

    接下来,我将用两个实例分别对 Retrofit GET方式 和 POST方式进行 网络请求 讲解。

    4.1 实例1

    • 实现功能:将中文翻译成英文
    • 实现方案:采用Get方法对 金山词霸API 发送网络请求

    采用 Gson 进行数据解析

    金山词典

    • 步骤说明

    步骤1:添加Retrofit库的依赖
    步骤2:创建 接收服务器返回数据 的类
    步骤3:创建 用于描述网络请求 的接口
    步骤4:创建 Retrofit 实例
    步骤5:创建 网络请求接口实例 并 配置网络请求参数
    步骤6:发送网络请求(采用最常用的异步方式)

    封装了 数据转换、线程切换的操作

    步骤7:处理服务器返回的数据

    接下来,我们一步步进行讲解。

    • 具体使用

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
        compile 'com.squareup.okhttp3:okhttp:3.1.2'
        // Okhttp库
      }
    

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>
    

    步骤2:创建 接收服务器返回数据 的类

    • 金山词霸API 的数据格式说明如下:
    // URL模板
    http://fy.iciba.com/ajax.php
    
    // URL实例
    http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world
    
    // 参数说明:
    // a:固定值 fy
    // f:原文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
    // t:译文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
    // w:查询内容
    

    API格式说明

    • 根据 金山词霸API 的数据格式,创建 接收服务器返回数据 的类:

    Translation.java

    public class Translation {
            private int status;
    
        private content content;
        private static class content {
            private String from;
            private String to;
            private String vendor;
            private String out;
            private int errNo;
        }
    
        //定义 输出返回数据 的方法
        public void show() {
            System.out.println(status);
            
            System.out.println(content.from);
            System.out.println(content.to);
            System.out.println(content.vendor);
            System.out.println(content.out);
            System.out.println(content.errNo);
        }
    }
        
    

    步骤3:创建 用于描述网络请求 的接口

    采用 注解 描述 网络请求参数。
    GetRequest_Interface.java

    public interface GetRequest_Interface {
        
     @GET("ajax.php?a=fy&f=auto&t=auto&w=hello%20world")
        Call<Translation> getCall();
        // 注解里传入 网络请求 的部分URL地址
        // Retrofit把网络请求的URL分成了两部分:一部分放在Retrofit对象里,另一部分放在网络请求接口里
        // 如果接口里的url是一个完整的网址,那么放在Retrofit对象里的URL可以忽略
        // getCall()是接受网络请求数据的方法
    }
    

    接下来的步骤均在GetRequest.java内实现(看注释)

    步骤4:创建Retrofit对象
    步骤5:创建 网络请求接口 的实例
    步骤6:发送网络请求

    以最常用的 异步请求 为例

    步骤7:处理返回数据

    GetRequest.java

    public class GetRequest extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            request();
            // 使用Retrofit封装的方法
        }
        public void request() {
    
            //步骤4:创建Retrofit对象
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
                    .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                    .build();
    
            // 步骤5:创建 网络请求接口 的实例
            GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
    
            //对 发送请求 进行封装
            Call<Translation> call = request.getCall();
    
            //步骤6:发送网络请求(异步)
            call.enqueue(new Callback<Translation>() {
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation> call, Response<Translation> response) {
                    // 步骤7:处理返回的数据结果
                    response.body().show();
                }
    
                //请求失败时回调
                @Override
                public void onFailure(Call<Translation> call, Throwable throwable) {
                    System.out.println("连接失败");
                }
            });
        }
    }
    

    由于此处采用了 Gson 解析,所以需要在 Gradle加入依赖
    build.gradle

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    

    运行结果

    运行结果

    Demo地址

    Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


    4.2 实例2

    • 实现的功能:将 英文 翻译成 中文
    • 实现方法:采用Post方法对 有道API 发送网络请求

    采用 Gson 进行数据解析

    有道翻译

    • 使用步骤

    步骤1:添加Retrofit库的依赖
    步骤2:创建 接收服务器返回数据 的类
    步骤3:创建 用于描述网络请求 的接口
    步骤4:创建 Retrofit 实例
    步骤5:创建 网络请求接口实例 并 配置网络请求参数
    步骤6:发送网络请求(采用最常用的异步方式)

    封装了 数据转换、线程切换的操作

    步骤7:处理服务器返回的数据

    接下来,我们一步步进行Retrofit的使用。

    • 具体使用

    步骤1:添加Retrofit库的依赖

    1. 在 Gradle加入Retrofit库的依赖

    由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

    build.gradle

    dependencies {
        compile 'com.squareup.retrofit2:retrofit:2.0.2'
        // Retrofit库
        compile 'com.squareup.okhttp3:okhttp:3.1.2'
        // Okhttp库
      }
    

    2. 添加 网络权限
    AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET"/>
    

    步骤2:创建 接收服务器返回数据 的类

    • API 的数据格式说明如下:
    // URL
    http://fanyi.youdao.com/translate
    
    // URL实例
    http://fanyi.youdao.com/translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=
    
    
    // 参数说明
    // doctype:json 或 xml
    // jsonversion:如果 doctype 值是 xml,则去除该值,若 doctype 值是 json,该值为空即可
    // xmlVersion:如果 doctype 值是 json,则去除该值,若 doctype 值是 xml,该值为空即可
    // type:语言自动检测时为 null,为 null 时可为空。英译中为 EN2ZH_CN,中译英为 ZH_CN2EN,日译中为 JA2ZH_CN,中译日为 ZH_CN2JA,韩译中为 KR2ZH_CN,中译韩为 ZH_CN2KR,中译法为 ZH_CN2FR,法译中为 FR2ZH_CN
    // keyform:mdict. + 版本号 + .手机平台。可为空
    // model:手机型号。可为空
    // mid:平台版本。可为空
    // imei:???。可为空
    // vendor:应用下载平台。可为空
    // screen:屏幕宽高。可为空
    // ssid:用户名。可为空
    // abtest:???。可为空
    
    // 请求方式说明
    // 请求方式:POST
    // 请求体:i
    // 请求格式:x-www-form-urlencoded
    

    数据格式说明

    • 根据 有道API 的数据格式,创建 接收服务器返回数据 的类:

    Translation.java

    public class Translation1 {
    
        private String type;
        private int errorCode;
        private int elapsedTime;
        private List<List<TranslateResultBean>> translateResult;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public int getErrorCode() {
            return errorCode;
        }
    
        public void setErrorCode(int errorCode) {
            this.errorCode = errorCode;
        }
    
        public int getElapsedTime() {
            return elapsedTime;
        }
    
        public void setElapsedTime(int elapsedTime) {
            this.elapsedTime = elapsedTime;
        }
    
        public List<List<TranslateResultBean>> getTranslateResult() {
            return translateResult;
        }
    
        public void setTranslateResult(List<List<TranslateResultBean>> translateResult) {
            this.translateResult = translateResult;
        }
    
        public static class TranslateResultBean {
            /**
             * src : merry me
             * tgt : 我快乐
             */
    
            public String src;
            public String tgt;
    
            public String getSrc() {
                return src;
            }
    
            public void setSrc(String src) {
                this.src = src;
            }
    
            public String getTgt() {
                return tgt;
            }
    
            public void setTgt(String tgt) {
                this.tgt = tgt;
            }
        }
    
    }
    

    步骤3:创建 用于描述网络请求 的接口

    采用 注解 描述 网络请求参数。

    PostRequest_Interface.java

    public interface PostRequest_Interface {
    
        @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
        @FormUrlEncoded
        Call<Translation1> getCall(@Field("i") String targetSentence);
        //采用@Post表示Post方法进行请求(传入部分url地址)
        // 采用@FormUrlEncoded注解的原因:API规定采用请求格式x-www-form-urlencoded,即表单形式
        // 需要配合@Field 向服务器提交需要的字段
    }
    

    接下来的步骤均在PostRequest.java内实现(看注释)

    步骤4:创建Retrofit对象
    步骤5:创建 网络请求接口 的实例
    步骤6:发送网络请求

    以最常用的 异步请求 为例

    步骤7:处理返回数据

    PostRequest.java

    public class PostRequest extends AppCompatActivity {
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            request();
        }
        public void request() {
    
            //步骤4:创建Retrofit对象
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fanyi.youdao.com/") // 设置 网络请求 Url
                    .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                    .build();
    
            // 步骤5:创建 网络请求接口 的实例
            PostRequest_Interface request = retrofit.create(PostRequest_Interface.class);
    
            //对 发送请求 进行封装(设置需要翻译的内容)
            Call<Translation1> call = request.getCall("I love you");
    
            //步骤6:发送网络请求(异步)
            call.enqueue(new Callback<Translation1>() {
    
                //请求成功时回调
                @Override
                public void onResponse(Call<Translation1> call, Response<Translation1> response) {
                    // 步骤7:处理返回的数据结果:输出翻译的内容
                    System.out.println(response.body().getTranslateResult().get(0).get(0).getTgt());
                }
    
                //请求失败时回调
                @Override
                public void onFailure(Call<Translation1> call, Throwable throwable) {
                    System.out.println("请求失败");
                    System.out.println(throwable.getMessage());
                }
            });
        }
    
    
    }
    

    由于此处采用了 Gson 解析,所以需要在 Gradle 加入依赖
    build.gradle

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    

    运行结果

    运行结果

    Demo地址

    Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


    5. Retrofit 的拓展使用

    • Retrofit的使用场景非常丰富,如支持RxJavaPrototocobuff
    • 具体设置也非常简单 & 方便:
    <-- 主要在创建Retrofit对象中设置 -->
    Retrofit retrofit = new Retrofit.Builder()
      .baseUrl(""http://fanyi.youdao.com/"")
      .addConverterFactory(ProtoConverterFactory.create()) // 支持Prototocobuff解析
      .addConverterFactory(GsonConverterFactory.create()) // 支持Gson解析
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava
      .build();
    

    具体关于 RxJava的使用这里就不展开,请期待下篇关于 Rxjava的文章。


    6. 总结


    帮顶或评论点赞!因为你的鼓励是我写作的最大动力!


    欢迎关注carson_ho的微信公众号

    示意图

    示意图

    展开全文
  • Retrofit

    千次阅读 2019-08-14 17:34:50
    Retrofit到底是什么 Retrofit主要是对Android网络请求的框架的封装,它遵循Restful设计风格,底层基于OkHttp。 换句说,网络请求的工作本质上是OkHttp完成,而Retrofit仅负责网络请求接口的封装。 主要是使用Retrofit...
  • Retrofit使用

    千次阅读 2019-06-14 06:41:21
    Retrofit是什么? Retrofit就是一个Http请求库,和其它Http库最大区别在于通过大范围使用注解简化Http请求。目前Retrofit 2.0底层是依赖OkHttp实现的,也就是说Retrofit本质上就是对OkHttp的更进一步封装。那么我们...
  • Retrofit 基本使用

    2019-04-09 12:07:56
    implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' 2、基本使用 2.1、定义请求接口 import hq.demo.net.model.WeatherBeans; impo...
  • Retrofit简介

    2018-06-28 14:42:57
    Retrofit干什么的?Retrofit让你便捷地封装出一个类来给其它地方调用和服务器进行HTTP API调用。比如,一般来说,不用retrofit的时候,你需要填写http参数,然后get数据下来,然后解析,然后。。。。用了retrofit...
  • Retrofit·特点性能最好,处理最快使用REST API时非常方便;传输层默认就使用OkHttp;支持NIO;拥有出色的API文档和社区支持速度上比volley更快;如果你的应用程序中集成了OKHttp,Retrofit默认会使用OKHttp处理其他...
  • Retrofit 传递数组参数

    万次阅读 2016-06-18 10:36:38
    接触Retrofit一段时间后,感觉到Retrofit越来越强大,方便快捷。传数组@GET("v1/enterprise/find") Call<ResponseBody> getData(@Query("id") String id, @Query("linked[]") String... linked); 代码调用:String ...
  • Retrofit 2.2 传递数组参数

    万次阅读 2017-09-01 14:24:27
    Android 使用 Retrofit2 传递数组参数,POST请求参数 list
  • 最近刚开始接触Retrofit,碰到一个问题: 服务器要求所有POST方式接口都带上TOKEN等字段,如果每个接口都单独写明显不符合程序员的气质,那么问题来了:Retrofit里如何在请求的时候统一给请求加上这些公共的参数? ...
  • Retrofit 2 动态URL

    万次阅读 2017-04-20 16:55:11
    问题使用Retrofit2一般都是针对于一baseURL,其它接口都是拼接不同的参数如get/photo,search?name=xiaohong&&sex=female,这样的形式。但是一些请求此时又要访问不同的url只能重新生成一个Retrofit2实例,实质上还有...
  • Retrofit2+Rxjava2如何主动取消网络请求

    万次阅读 热门讨论 2018-03-01 11:52:22
    近期在新项目中使用了Retrofit2+Rxjava2+Mvp的框架,在搭建的过程中想到一个问题,困扰了我两天。Retrofit2+Rxjava2如何主动去取消网络请求。 在单独使用Retrofit2中,我们可以通过调用Call的cancel();方法 主动去...
  • Retrofit提供了两个两种定义HTTP请求头字段的方法即静态和动态。静态头不能改变为不同的请求,头的键和值是固定的且不可改变的,随着程序的打开便已固定。动态添加@Header@GET("/") Call<ResponseBody> query(@...
  • Retrofit添加header参数的几种方法。

    万次阅读 2016-10-28 15:30:41
    (1)使用注解的方式 添加一个Header参数 public interface UserService { @Headers("Cache-Control: max-age=640000") @GET("/tasks") Call> getTasks(); } ...添加多个Header参数 ...public interface UserService...
  • No Retrofit annotation found

    万次阅读 2016-06-06 16:47:37
    今天在使用Square的android 网络封装框架Retrofit 时出现了这个问题,报错:No Retrofit annotation found,焦灼了很久在解决问题,所以拿出来和大家分享一下下,希望大家遇到同样问题时可以快速解决。  我直接贴出...
  • android retrofit设置网络请求超时时间

    千次阅读 2018-02-01 17:41:15
    今天开发的时候遇到一个网络请求超时的问题,后台处理是成功的,但是移动端返回的总是提示请求超时,在设置了retrofit请求超时的时间延长以后,就可以请求成功了,下面是配置的方法: private static final ...
  • Retrofit 设置 超时时间

    万次阅读 2016-12-09 21:49:13
    现象在代码中,通过retrofit 框架发送请求去调用其他系统中的 REST 接口时,正常情况下,都没有啥问题。 但是今天碰到一个问题是,在发送一个请求时,过了10秒钟之后,系统就报SocketTimeout这个异常。每次报这个...
1 2 3 4 5 ... 20
收藏数 29,679
精华内容 11,871
关键字:

retrofit