美文网首页AndroidWorldRx、Retrofit、Gson、MVP、Dagger2android
你真的会用Retrofit2吗?Retrofit2完全教程

你真的会用Retrofit2吗?Retrofit2完全教程

作者: 怪盗kidou | 来源:发表于2016-05-15 19:20 被阅读226198次

    作者: @怪盗kidou
    如需转载需在明显位置保留作者信息及原文链接
    Retrofit版本: 2.0.2

    本文注目录:

    • Retrofit入门
    • Retrofit注解详解
    • Gson与Converter
    • RxJava与CallAdapter
    • 自定义Converter
    • 自定义CallAdapter
    • 其它说明

    前言

    本文中的Retrofit均指代Retrofit2.0。
    本文涉及到的代码以及测试使用的接口可在Github上找到。
    测试接口服务器在 server 项目下,直接运行 RESTServer.main() 即可启动测试服务器,所面代码示例均使用该接口(接口地址 http://localhost:4567/ ).
    当然你也可以自己借助 json-server 或 最新开源的Parse 搭建一个REST API,不过都需要安装Node.js,有兴趣的可以去试试。

    接口列表:

    地址 请求方法 参数 说明
    /blog GET page={page},sort=asc或desc 分页获取Blog列表,每页10条
    /blog/{id} GET id 获取指定ID的Blog
    /blog POST {"author":"","title":"","content":""} 创建一个新Blog
    /blog/{id} PUT {"author":"","title":"","content":""} 中至少一个 修改Blog
    /blog/{id} DELETE id 删除一个Blog
    /form POST 任意,最终以Json Object形式返回 用于测试Form表单,支持文件上传
    /headers GET showAll=true或false,默认false 返回自定义请求头,all=true是显示全部

    注:以上的接口的{id}{page}均代表一个纯数字,/blog/{id} 可以用 /blog?id=XXX 代替,page同理。

    前面写了你应该知道的HTTP基础知识 介绍了HTTP的相关知识,不知道那些想了解Retrofit的同鞋是不是去看了Retrofit的官方教程,曾经我在你真的会用Gson吗?Gson使用指南(四) 中说当你了解了注解、反射、泛型、HTTP的内容只需要看一篇Retrofit的代码示例就可以轻松玩转Retrofit,不知道你玩转了没?
    当然注解、反射、泛型的内容还没有写,Retrofit的内容却先来了!毕竟看懂Retrofit也只需要会使就行,你准备好了吗?

    1、Retrofit入门

    Retrofit 其实相当简单,简单到源码只有37个文件,其中22个文件是注解还都和HTTP有关,真正暴露给用户的类并不多,所以我看了一遍 官方教程 大多数情景就可以无障碍使用,如果你还没有看过,可以先去看看,虽然是英文,但代码才是最好的教程不是么?当然本篇文章会介绍得详细一点,不能写一篇水文,毕竟我给它命名为《你真的会用Retrofit2吗?Retrofit2完全教程》。

    1.1、创建Retrofit实例

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://localhost:4567/")
            .build();
    

    创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。
    注1: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。
    注2: 上面的 注1 应该描述为 baseUrl 中的路径(path)必须以 / 结束, 因为有些特殊情况可以不以/结尾(感谢@liujc 提出,81楼),比如 其实这个 URL https://www.baidu.com?key=value用来作为baseUrl其实是可行的,因为这个URL隐含的路径就是 /(斜线,代表根目录) ,而后面的?key=value在拼装请求时会被丢掉所以写上也没用。之所以 Retrofit 2 在文档上要求必须以 /(斜线) 结尾的要求想必是要消除歧义以及简化规则。——2017.10.27

    1.2、接口定义.

    以获取指定id的Blog为例:

    public interface BlogService {
        @GET("blog/{id}")
        Call<ResponseBody> getBlog(@Path("id") int id);
    }
    

    注意,这里是interface不是class,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个BlogService的代理对象。

    BlogService service = retrofit.create(BlogService.class);
    

    拿到代理对象之后,就可以调用该方法啦。

    1.3、接口调用

    Call<ResponseBody> call = service.getBlog(2);
    // 用法和OkHttp的call如出一辙,
    // 不同的是如果是Android系统回调方法执行在主线程
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            try {
                System.out.println(response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            t.printStackTrace();
        }
    });
    

    打印结果:

    {"code":200,"msg":"OK","data":{"id":2,"date":"2016-04-15 03:17:50","author":"怪盗kidou","title":"Retrofit2 测试2","content":"这里是 Retrofit2 Demo 测试服务器2"},"count":0,"page":0}
    

    示例源码见 Example01.java

    2、Retrofit注解详解

    上面提到Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解我将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释。

    第一类:HTTP请求方法

    HTTP请求方法注解
    以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:methodpath,hasBody,下面是用HTTP注解实现上面 Example01.java 的例子。
    public interface BlogService {
        /**
         * method 表示请求的方法,区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getBlog(@Path("id") int id);
    }
    

    注:method 的值 retrofit 不会做处理,所以要自行保证其准确性,之前使用小写也可以是因为示例源码中的服务器不区分大小写,所以希望大家注意,感谢 @言過祺實 发现该问题。
    示例源码见 Example02.java

    第二类:标记类

    标记类注解
    示例源码见 Example03.java

    第三类:参数类

    参数类注解

    注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用QueryQueryMap 代替,保证接口定义的简洁
    注2:QueryFieldPart这三者都支持数组和实现了Iterable接口的类型,如ListSet等,方便向后台传递数组。

    Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
    //结果:ids[]=0&ids[]=1&ids[]=2
    

    Path 示例源码见 Example01.java
    Field、FieldMap、Part和PartMap 示例源码见 Example03.java
    Header和Headers 示例源码见 Example04.java
    Query、QueryMap、Url 示例源码见 Example05.java

    3、Gson与Converter

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

    public interface BlogService {
      @GET("blog/{id}")
      Call<Result<Blog>> getBlog(@Path("id") int id);
    }
    

    当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody转换我们泛型中的类型时需要使用的Converter

    引入Gson支持:

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

    通过GsonConverterFactory为Retrofit添加Gson支持:

    Gson gson = new GsonBuilder()
          //配置你的Gson
          .setDateFormat("yyyy-MM-dd hh:mm:ss")
          .create();
    
    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          //可以接收自定义的Gson,当然也可以不传
          .addConverterFactory(GsonConverterFactory.create(gson))
          .build();
    

    示例源码见 Example06.java

    这样Retrofit就会使用Gson将ResponseBody转换我们想要的类型。

    这是时候我们终于可以演示如使创建一个Blog了!

    @POST("blog")
    Call<Result<Blog>> createBlog(@Body Blog blog);
    

    @Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。

    BlogService service = retrofit.create(BlogService.class);
    Blog blog = new Blog();
    blog.content = "新建的Blog";
    blog.title = "测试";
    blog.author = "怪盗kidou";
    Call<Result<Blog>> call = service.createBlog(blog);
    

    结果:

    Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='怪盗kidou', title='测试', content='新建的Blog'}, count=0, page=0}
    

    示例源码见 Example07.java

    如果你对Gson不熟悉可以参考我写的《你真的会用Gson吗?Gson使用指南》 系列。

    4、RxJava与CallAdapter

    说到Retrofit就不得说到另一个火到不行的库RxJava,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava的例子,不过这里主要目的是介绍使用CallAdapter所带来的效果。

    第3节介绍的Converter是对于Call<T>T的转换,而CallAdapter则可以对Call转换,这样的话Call<T>中的Call也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter,这里以RxJava的为例,用Observable代替Call

    引入RxJava支持:

    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    // 针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    

    通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:

    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          .addConverterFactory(GsonConverterFactory.create())
          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
          // 针对rxjava2.x
          .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
          .build();
    

    接口设计:

    public interface BlogService {
      @POST("/blog")
      Observable<Result<List<Blog>>> getBlogs();
    }
    

    使用:

    BlogService service = retrofit.create(BlogService.class);
    service.getBlogs(1)
      .subscribeOn(Schedulers.io())
      .subscribe(new Subscriber<Result<List<Blog>>>() {
          @Override
          public void onCompleted() {
            System.out.println("onCompleted");
          }
    
          @Override
          public void onError(Throwable e) {
            System.err.println("onError");
          }
    
          @Override
          public void onNext(Result<List<Blog>> blogsResult) {
            System.out.println(blogsResult);
          }
      });
    

    结果:

    Result{code=200, msg='OK', data=[Blog{id=1, date='2016-04-15 03:17:50', author='怪盗kidou', title='Retrofit2 测试1', content='这里是 Retrofit2 Demo 测试服务器1'},.....], count=20, page=1}
    

    示例源码见 Example08.java

    「20160608补充」:像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
    1、用Observable<Response<T>> 代替 Observable<T> ,这里的Responseretrofit2.Response
    2、用Observable<Result<T>> 代替 Observable<T>,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例

    5、自定义Converter

    本节的内容是教大家实现在一简易的Converter,这里以返回格式为Call<String>为例。

    在此之前先了解一下Converter接口及其作用:

    public interface Converter<F, T> {
      // 实现从 F(rom) 到 T(o)的转换
      T convert(F value) throws IOException;
    
      // 用于向Retrofit提供相应Converter的工厂
      abstract class Factory {
        // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null
        // 主要用于对响应体的处理
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
          return null;
        }
    
        // 在这里创建 从自定类型到ResponseBody 的Converter,不能处理就返回null,
        // 主要用于对Part、PartMap、Body注解的处理
        public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
          return null;
        }
    
        // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理
        // Retrfofit对于上面的几个注解默认使用的是调用toString方法
        public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
          return null;
        }
    
      }
    }
    

    我们要想从Call<ResponseBody> 转换为 Call<String> 那么对应的F和T则分别对应ResponseBodyString,我们定义一个StringConverter并实现Converter接口。

    public static class StringConverter implements Converter<ResponseBody, String> {
    
      public static final StringConverter INSTANCE = new StringConverter();
    
      @Override
      public String convert(ResponseBody value) throws IOException {
        return value.string();
      }
    }
    

    我们需要一个Fractory来向Retrofit注册StringConverter

    public static class StringConverterFactory extends Converter.Factory {
    
      public static final StringConverterFactory INSTANCE = new StringConverterFactory();
    
      public static StringConverterFactory create() {
        return INSTANCE;
      }
    
      // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖
      @Override
      public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == String.class) {
          return StringConverter.INSTANCE;
        }
        //其它类型我们不处理,返回null就行
        return null;
      }
    }
    

    使用Retrofit.Builder.addConverterFactory向Retrofit注册我们StringConverterFactory

    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          // 我们自定义的一定要放在Gson这类的Converter前面 
          .addConverterFactory(StringConverterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .build();
    

    注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。

    只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call<String>还是Observable<String>

    有没有很简单?如果你有其它的需求处理的就自己实现吧。

    示例源码见 Example09.java

    6、自定义CallAdapter

    本节将介绍如何自定一个CallAdapter,并验证是否所有的String都会使用我们第5节中自定义的Converter。

    先看一下CallAdapter接口定义及各方法的作用:

    public interface CallAdapter<T> {
    
      // 直正数据的类型 如Call<T> 中的 T
      // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数
      // 可以参照上面的自定义Converter
      Type responseType();
    
      <R> T adapt(Call<R> call);
    
      // 用于向Retrofit提供CallAdapter的工厂类
      abstract class Factory {
        // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>`
        // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型
        // 不支持时返回null
        public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);
    
        // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody
        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
          return Utils.getParameterUpperBound(index, type);
        }
    
        // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call
        // 上面的get方法需要使用该方法。
        protected static Class<?> getRawType(Type type) {
          return Utils.getRawType(type);
        }
      }
    }
    

    了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall<String>为例。

    在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。

    public static class CustomCall<R> {
    
      public final Call<R> call;
    
      public CustomCall(Call<R> call) {
        this.call = call;
      }
    
      public R get() throws IOException {
        return call.execute().body();
      }
    }
    

    有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call<T>CustomCall<T>的转换,这里需要注意的是最后的泛型,是我们要返回的类型。

    public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {
    
      private final Type responseType;
    
      // 下面的 responseType 方法需要数据的类型
      CustomCallAdapter(Type responseType) {
        this.responseType = responseType;
      }
    
      @Override
      public Type responseType() {
        return responseType;
      }
    
      @Override
      public <R> CustomCall<R> adapt(Call<R> call) {
        // 由 CustomCall 决定如何使用
        return new CustomCall<>(call);
      }
    }
    

    提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter

    public static class CustomCallAdapterFactory extends CallAdapter.Factory {
      public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
    
      @Override
      public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        // 获取原始类型
        Class<?> rawType = getRawType(returnType);
        // 返回值必须是CustomCall并且带有泛型
        if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
          Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
          return new CustomCallAdapter(callReturnType);
        }
        return null;
      }
    }
    

    使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory

    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          .addConverterFactory(Example09.StringConverterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
          .build();
    

    注: addCallAdapterFactoryaddConverterFactory同理,也有先后顺序。

    示例源码见 Example10.java

    7、其它说明

    7.1 Retrofit.Builder

    前面用到了 Retrofit.Builder 中的baseUrladdCallAdapterFactoryaddConverterFactorybuild方法,还有callbackExecutorcallFactoryclientvalidateEagerly这四个方法没有用到,这里简单的介绍一下。

    方法 用途
    callbackExecutor(Executor) 指定Call.enqueue时使用的Executor,所以该设置只对返回值为Call的方法有效
    callFactory(Factory) 设置一个自定义的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就实现了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最终也是调用了该方法,也就是说两者不能共用
    client(OkHttpClient) 设置自定义的OkHttpClient,以前的Retrofit版本中不同的Retrofit对象共用同OkHttpClient,在2.0各对象各自持有不同的OkHttpClient实例,所以当你需要共用OkHttpClient或需要自定义时则可以使用该方法,如:处理Cookie、使用stetho 调式等
    validateEagerly(boolean) 是否在调用create(Class)时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用

    7.2 Retrofit的Url组合规则

    BaseUrl 和URL有关的注解中提供的值 最后结果
    http://localhost:4567/path/to/other/ /post http://localhost:4567/post
    http://localhost:4567/path/to/other/ post http://localhost:4567/path/to/other/post
    http://localhost:4567/path/to/other/ https://github.com/ikidou https://github.com/ikidou

    从上面不能难看出以下规则:

    • 如果你在注解中提供的url是完整的url,则url将作为请求的url。
    • 如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
    • 如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值

    7.3 Retrofit提供的Converter

    Converter Gradle依赖
    Gson com.squareup.retrofit2:converter-gson:2.0.2
    Jackson com.squareup.retrofit2:converter-jackson:2.0.2
    Moshi com.squareup.retrofit2:converter-moshi:2.0.2
    Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
    Wire com.squareup.retrofit2:converter-wire:2.0.2
    Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
    Scalars com.squareup.retrofit2:converter-scalars:2.0.2

    7.4 Retrofit提供的CallAdapter:

    CallAdapter 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

    7.5 关于源码

    看到这儿可能有小伙伴要问为什么源码没有把类拆分到单独的文件,命名也不能体现其用途,这里主要是因为方便大家看源码,而不是将注意力放在反复跳转上,另一方面也是因为同一个例子中不可避免的使用其它小节要介绍的内容,所以就直接用了ExampleXX的形式,不过在项目中千万不要使用这种方式,一定要好好命名,做到见名知意。

    结语

    其它本博客的内容早就已经完成好了,但由于当时HTTP、反射、注解的博客一篇也没有写,所以一直没有发,期间也有不少的博主写了Retrofit2的博文,不过呢没有自定义相关的内容也没有对各个注解进行详解,所以我还是决定发出来帮助一下那此对Retrofit2无从下手同鞋。

    这次Retrofit2的内容就到这里啦,下次再见。

    相关文章

      网友评论

      • 猎头eason:招聘,腾讯系 互联网体育排名 第一企业 招聘 android架构leader,有兴趣联系我
      • 8ab8f90f5349:大佬,请教个问题,Retrofit如何获取服务器返回的json串?addConverterFactory(GsonConverterFactory.create()) 直接转换了。想看一下json串没找到。
        看到上文说:用Observable<Response<T>> 代替 Observable<T> ,这里的Response指retrofit2.Response。用了这种。Response没找到json方法。
        怪盗kidou:@强子_fa37 获取不到json串
      • X2046:Converters是按照顺序处理的,对于响应回来的数据,Retrofit会按照添加的Converters顺序依次询问是否可以处理这个数据。

        因为 JSON 并没有什么继承上的约束。所以我们无法通过什么确切的条件来判断一个对象是否是 JSON 对象。以至于 JSON 的 converters 会对任何数据都回复说:我可以处理!这个一定要记住, JSON converter 一定要放在最后,不然会和你的预期不符。

        参见Jake Wharton大神的演讲19:31秒。
        -- https://academy.realm.io/cn/posts/droidcon-jake-wharton-simple-http-retrofit-2/
        怪盗kidou:@Kylin_Gu 注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持(就是大神所谓的我可以处理)同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的(直接返回Converter)
      • efbd2c84861a:厉害厉害大佬
      • Endeav0r:学习了:+1:
      • mindle君:谢谢大佬,受教了:pray:
      • Jay_Lwp:厉害厉害
      • dbe7ea5ea731:文章写得很不错,支持一下!微商中心提供各界名师大咖培训课程,各大微商团队内部培训资料,分享引流秘籍以及实战操作技巧,一次投资,永久享有收益,既学习,又可以赚钱,一举两得
      • LiuK1003:多看几遍,会有不一样的收获
      • baby_honour:好文章就应该慢慢看:grin:
        怪盗kidou:@baby_honour :smile:
      • 0c45406e8da8:文章很棒。我们侠课岛正好在找远程录制课程视频或图文教程的朋友,我们会给到课程的需求大纲,每一节课程需要你来详细展开写一些代码举例和讲解清楚,对经验积累和创新能力有一定的要求。有兴趣联系我,微信:zhimadt
      • e9c381ead931:右键Run RESTServer.main();
        错误: 找不到或无法加载主类 com.github.ikidou.RESTServer 这个是什么问题,求博主和各路大神解答
        e9c381ead931:@怪盗kidou 你github更新的readme有问题,IDEA运行配置打不开
        e9c381ead931:@怪盗kidou 我是windows as 3.0.1版本,没找到Preferences 赶快更新文档,很想学习你博客中的内容,多谢了
        怪盗kidou:@cn_wyt Android Studio 下,先Build一次,Idea下,找到 Preferences > Build,Execution,.... > Build Tools > Gradle > Runner > Delegate IDE build/run actions to gradle,待会儿我会更新到README
      • tanzhihao1qaz:up你好,有个地方不太明白,就是converter添加那里,“gson不支持判断”这里开始不明白,我看网上很多例子都是是把gsonconverter放到最后添加,也许是不注意到,但既然很多人这么写也说明gson的顺序没问题把,麻烦up再说详细一点:sweat:
        怪盗kidou:@tanzhihao1qaz 写的有点儿含糊,已经换了种说法
        tanzhihao1qaz:@怪盗kidou 这样明白了,因为看文章的注释写着“一定要放在其他converter前面”,可代码里却放在最底,这个前面我就不太理解,另外我看了gson转换器的源码,确实不判断,谢谢up
        怪盗kidou:@tanzhihao1qaz 就是gson不会判断是不是应该由它处理,它是来者不拒,所以要把它放到最后
      • 学费:啊,需要耐心地品读了。
      • Holly_dd20:请教一下:

        public interface Api {

        String baseURL="http://192.168.1.105:8888/chihuo/api/";;

        @get("login") // 这种方式 相当于 http://192.168.1.105:8888/chihuo/api/login?phone=value
        Call<LoginRespone> login(@Query("phone") String phone);

        @get("login") // 这种方式 相当于 http://192.168.1.105:8888/chihuo/api/login?phone=value&pwd=value
        Call<LoginRespone> login(@Query("phone") String phone,@Query("pwd") String pwd);

        @FormUrlEncoded
        @post("login")
        Call<LoginRespone> login_post(@Field("phone") String phone, @field("pwd") String pwd);

        @FormUrlEncoded
        @post("login")
        Call<LoginRespone> login_post(@FieldMap Map<String,String> param);

        @post("login")
        Call<LoginRespone> login_post(@Body LoginRequest request);
        }
        我上面写的get 服务器都能正确获取APP提交的数据,
        但是下面3个post都不能获取到参数, 请问问题出在哪里

        调用方法如下:
        Api api=retrofit.create(Api.class);
        // Call<LoginRespone> call=api.login("13262656236");
        // Call<LoginRespone> call=api.login_post("13262656236","123456");
        // Map<String,String> map=new HashMap<>();
        // map.put("phone","13262656236");
        // map.put("pwd","123456");
        // Call<LoginRespone> call=api.login_post(map);

        Call<LoginRespone> call=api.login_post(new LoginRequest("13262656236","123456"));
        30b1e95483cb:看你上面代码的写法,请求体会以phone=123,pwd=123的形式发送。看一下服务端是否要求以json格式({‘phone’:123,'pwd':123})上传参数呢?如果是,参数就要填写json串,而不再是@Field("phone") String phone,而是类似@Body JsonObject params。试试看
        怪盗kidou:看看是不是服务器是不是支持这种方式啊,看看你的接口是不是支持POST请求,POST请求参数的格式
        Holly_dd20:用原生okhttp试了一下 post也没有数据, 怀疑是服务器的问题 搜索了一下,可能是mac下php集成工具MAMP的配置问题
      • 晴空一垩:第二个问题:
        为啥用GSON不能转换内部的格式呢?
        即:
        ```
        Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='怪盗kidou', title='测试', content='新建的Blog'}, count=0, page=0}
        ```
        data里面的数据解析不出来
        ```
        Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).baseUrl(BASE_URL).build();
        ```

        RespParent{result=null, requestId='1517899852121454'}
        怪盗kidou:@晴空一垩 你是不知道怎么根据json生成bean?
        晴空一垩:后来才知道,是后台压根不是怎么返回的.
        想问下:如何知道后台返回的内容,是什么.
      • 晴空一垩:@get("app/init{appid}&{sign}&{deviceid}&{packagename}&{os}")
        Call<RespParent<ReqActive>> init(
        @path("appid") String appid,
        @path("sign") String sign,
        @path("deviceid") String deviceid,
        @path("packagename") String packagename,
        @path("os") String os
        );

        想问下有多个参数的时候,要怎么请求??
        我上面的写法请求不到数据是为什么?
        菜鸟一个,勿见怪.
        怪盗kidou:@晴空一垩 path只是换值,相当于string.replaceAll(),你确定你这样写是正确的么,你想要的是 app/init123&456&789&com.baidu&android这样的么?如果不是,而是 app/init?appid=123&sign=456&deviceid=789&packagename=com.baidu&os=android,这样的话建议你多读几遍,如果是后者这种写法那么该了Query而不是Path
      • Zacch:写的真详细,配合代码服用效果好
      • 670761ab7280::+1: :+1: 首先感谢并膜拜一下大神,看Retrofit系列文章受益匪浅。好期待 博主对 注解、反射、泛型相关部分的文章讲解
      • 998bd9557ae7:retrofit为何要用注解来将框架使用者的http信息传递给服务器呢?用注解有啥好处?
      • 一碗沙世:6,还要时间消化
      • tmyzh:写的好呀,对我很有帮助
      • AH120808:写得很好,如果能够提供各个注解的简单使用例子就完美了
        AH120808:@怪盗kidou :+1:
        怪盗kidou:@AH120808 demo里每个注解的用法都有
      • 叽哩叽哩鸡:其实baseUrl不用必须/结尾, 只要 baseUrl + 接口定义的url 没有错误就行.
        baseUrl结尾不加, 就接口定义的时候在前面加上就行, 别忘了就好.
      • 64004063a312:请教 :Result<Blog> ,retrofit2 我用一个base bean泛型类 result<T> , 结果就是返回的数据为null,可以用泛型吗?还是必须给定明确类型?
        怪盗kidou:@波波_1025 明确类型,retrofit本身就是封装,所以没有必要再次封装,你的Bean可以带泛型,但反回结果里,必须是明确的,比如你想要Result<Blog> ,那retrofit中的返回值就需要是 Call<Result<Blog>>
      • 如水至清:我是新手,请问这个demo要怎么运行调试,我用的是androidstudio。我已经把代码下载下来导入androidstudio了。但是不知道要怎么运行server以及上server与client通信
        如水至清:@怪盗kidou 可能我理解错了,我以为client是android app,看了下并不是。问下您,这个client要怎么运行呢
        如水至清:@怪盗kidou 您好,server端直接右键运行是在手机端上运行吗?如果是在电脑上运行,那app在手机上运行,会不会连接上不呢
        怪盗kidou:@dddd我我我 Server端就是个普通的Java程序,找到里面的main方法直接右键运行就行了,之后就不用管它了
      • wenshushu:真心不错,得好好研究一下。
      • bwzhny:非常专业
        怪盗kidou:感谢支持
      • 闲庭:不错,学习了,不过博主,你可以更新下1.1中的注意部分,现在Retrofit2 的baseUlr 并不是必须以 /(斜线) 结束,scheme://host[:port] 此种类型的baseUrl是不是以 /(斜线) 结尾都可以,不介意的话可以看下 http://www.jianshu.com/p/d6b8b6bc6209
        怪盗kidou: @liujc 嗯,从传参结果来说确实是可以不用加,但实际上这种情况是由HttpUrl这个类给你加了,这种属于缺省吧,类似于DNS的最后一个点可以省略一样,实际上是最后系统给补上了
      • ys尘笑:引入rxJava的2个库之后有了重复文件
        Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
        > More than one file was found with OS independent path 'META-INF/rxjava.properties'
        bb839216952f:@ys尘笑 请问你的是啥原因呢?
        ys尘笑:@怪盗kidou 应该不是 没加过其他东西 不过已经处理好了
        怪盗kidou:可能是你Jar包里含了RxJava的库(比如XXSDK啥的),然后你又通过Gradle引用了仓库里的RxJava,所以才有这个问题
      • 月下大象:写的好,已经收藏
      • 6d6e8cb478b4:麻烦问一下,当我请求接口时 ,比如说类似发送验证码的接口 ,后台返回的时候改手机号已经注册,但是我这边显示的是
        retrofit2.adapter.rxjava.HttpException: HTTP 400 Bad Request
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: retrofit2.adapter.rxjava.HttpException: HTTP 400 Bad Request
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$SimpleCallAdapter$1.call(RxJavaCallAdapterFactory.java:163)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$SimpleCallAdapter$1.call(RxJavaCallAdapterFactory.java:158)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:113)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:92)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at rx.Observable$2.call(Observable.java:162)
        09-18 14:13:30.069 11110-11110/com.shop.gmshop W/System.err: at rx.Observable$2.call(Observable.java:154)
        。。。。。。。。。。

        能否帮忙看一下,告知这个是什么原因造成的,该如何解决呢?
        6d6e8cb478b4:而且它走的是 onError 而不是onNext 我希望他报code为3 或者几的时候走的是onNext 这样我就可以拿到body里面的数据,进行用户提示,但是现在他走的是onError 我这边没法判断
        6d6e8cb478b4:@怪盗kidou 可以我拿不到他返回的错误信息呀 {
        "code": 3,
        "msg": "参数不正确",
        "data": "[手机号不正确]"
        } 这个是网页版返回的,而我接收到的 却是 retrofit2.adapter.rxjava.HttpException: HTTP 400 Bad Request
        怪盗kidou:@梅花弄月木子李 这没什么问题啊,400不是正常结果的返回状态码(200~300),所以就报错了啊
      • 一切从简_e156:群主,最好是多写点栗子,在介绍哪种请求的时候,多贴点栗子
      • 朋哥01:写的号
      • 箫声默:虽然没看懂,但,写得不错~
      • nul1:如何做到统一封装呢
        我有很多个接口,路径,参数都不一样,每次请求要做的事
        1. 签名
        2. 发起请求
        3. 获取同步返回结果
        4. 验证签名
        所以希望把上面的操作写到一个地方,怎么做呢?
        怪盗kidou:@nul1 统一发起请求是个啥意思?要在发请求之前 去请求另一个接口来验证签名后才能发起请求?
        nul1:@怪盗kidou 拦截器只能签名验签,没法统一发起请求
        怪盗kidou:@nul1 用okhttp的拦截器
      • 穷格万物:真是看不懂
      • Macke:大神,收下我的膝盖....
      • 5ebe398bb32d:写的非常好,第一次学习这个框架,看完后豁然开朗
      • 太白新星:大神,我的mac os上运行server程序报如下错误怎办解决吧。
        [main] INFO com.j256.ormlite.table.TableUtils - creating table 'blog'
        [main] INFO com.j256.ormlite.table.TableUtils - executed create table statement changed 0 rows: CREATE TABLE `blog` (`id` INTEGER PRIMARY KEY AUTOINCREMENT , `date` TIMESTAMP , `author` VARCHAR NOT NULL , `title` VARCHAR NOT NULL , `content` VARCHAR NOT NULL )
        [Thread-0] INFO org.eclipse.jetty.util.log - Logging initialized @1620ms
        [Thread-0] INFO spark.webserver.JettySparkServer - == Spark has ignited ...
        [Thread-0] INFO spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:4567
        [Thread-0] INFO org.eclipse.jetty.server.Server - jetty-9.3.2.v20150730
        [Thread-0] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@3834862c{HTTP/1.1,[http/1.1]}{0.0.0.0:4567}
        [Thread-0] INFO org.eclipse.jetty.server.Server - Started @2045ms
        [qtp2030218006-15] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-17] INFO spark.webserver.MatcherFilter - The requested route [/favicon.ico] has not been mapped in Spark
        [qtp2030218006-19] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-16] INFO spark.webserver.MatcherFilter - The requested route [/favicon.ico] has not been mapped in Spark
        [qtp2030218006-17] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-19] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-15] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-16] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
        [qtp2030218006-19] INFO spark.webserver.MatcherFilter - The requested route [/favicon.ico] has not been mapped in Spark
        太白新星:@怪盗kidou 哈哈,后来我就运行了您的java类就可以了
        怪盗kidou:@太白新星 没有什么问题,只是你请求的地址不对,可用的地址以及对应请求的方法我都写在表格里了
      • 1cf651476639:看了好几遍,总算是能理解的差不多了,原本以为这些都是很简单的知识,看来任何新的知识都需要自己认真去掌握,加油了!!!
        怪盗kidou:@autumc 确实也还是比较简单的,如果HTTP相关知识比较好的话,用法什么的一看就懂
      • 我是少年520:注:以上的接口的{id}和{page}均代表一个纯数字,/blog/{id} 可以用 /blog?id=XXX 代替,page同理。

        这句话什么意思,可以讲清楚点吗
        我是少年520: @怪盗kidou 嗯嗯,谢谢
        怪盗kidou:@GoFastOrGoFar 就是用你可以用一个数字去代替他,即/blog/{id} 真正的应该是 /blog/1 这样的或 /blog?id=1
      • 70a80d070472:两个字牛P 这种写作风格非常喜欢
      • 6c2f07d1f89f:我有个疑问,所有的请求都要在单独在headers注解里添加webservise信息吗,这样好麻烦,有没有封装的办法
        怪盗kidou:那你可以在Okhttp里添加拦截器来实现
      • 谦谦行者:大神,我一直有个疑问,使用Retrofit设置了请求超时时间。在请求超时后,会执行哪个回调方法呢?
        怪盗kidou:@ca43e8ada79d SocketTimeoutException ,你可以自已写个ServerSocket监听某端口,然后请求这个 http://localhost:端口/ 然后等就行了,当然你也可以把时间设置得足够短,请求没完直接就报错了。
      • 1d69fa747513:请问,我想用get来发送body 这样写行吗
        interface Service {
        @http(method = “GET” , path = “getinfo”, hasBody = true)
        Call<ResponseBody> getObject(@Body RequestBody object);
        }
      • 1d69fa747513:请问 使用 get 如和发送body,下面这样吗?
        @http(method = "GET", path = "newsinfo", hasBody = true)
        Call<ResponseBody> getInfo(@Body RequestBody body);
        怪盗kidou:@然后怎样_dc40 Okhttp不允许GET有请求体
      • 有没有口罩给我一个:如果你不加calladper就会报错,因为retrofit默认不添加会返回一个call<t>,如果你在apiserver里没有一点的话必定报错
        有没有口罩给我一个: @怪盗kidou 哈哈,谢谢
        怪盗kidou:原文中有注册啊,原话是“使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory”,下面就有代码:joy:
      • 有没有口罩给我一个:自定义converter那里有问题
      • 安浪创想:我加rxjava的依赖就不能编译运行,说有版本冲突,请问怎么搭配最新版的依赖,rxjava和rxandroid和这个retrofit2.3.0
        怪盗kidou:@安浪创想 你要看你用的rxjava 1.x还是2.x,如果是2.x的话,那你就要使用
        compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
      • 越努力越幸运阳:写的真好👍
      • Hancock1993:作者写的真好,这才是教学该有的样子,全面,细节,有条有理,看完感觉理解的特别好. 想打钱了...
        怪盗kidou:@Hancock_6344 just do it! :joy:
      • 柒冄初4:大神,你好
        现在服务器需要这样的:
        Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
        //结果:ids[1]=0&id[2]=1&ids[3]=2
        这个怎么弄?谢谢
      • 吧主:楼主,这篇文章我给你在公众号原创首发可以吗?感觉大神写的很棒。公众号:杨守乐(ysle_0313)
        吧主: @怪盗kidou 好的,谢谢你的分享精神😀
        怪盗kidou:@吧主 可以,没有问题
      • 96575c45e92f:太棒啦
      • 2bb84e08d233:@POST+@Query 请求参数是跟在url后面,@POST+@Field参数是在请求体中,对吗
        怪盗kidou:@该来的少不了 是的
      • Damon__________:@HTTP的method对应的参数要大写
        怪盗kidou:@言過祺實 :sweat_smile: ,确定如此,只是服务器的支持问题,retrofit只是原封不动的使用,我这边的测试服务器正好支持小写,感谢提出该问题 :+1:
        Damon__________:@怪盗kidou compileSdkVersion 25
        buildToolsVersion "25.0.2"
        minSdkVersion 24
        targetSdkVersion 25

        虚拟机用的7.0的 这是我测试时的情况,小写就会出问题,改成大写就没事了
        怪盗kidou:@言過祺實 大小写都一样的
      • Dinos:Demo3,上传的第二个,服务器返回;
        <h2>HTTP ERROR: 500</h2>
        <p>Problem accessing /form. Reason:
        <pre> class java.lang.NoClassDefFoundError</pre></p>
        <hr /><a href="http://eclipse.org/jetty&quot;>Powered by Jetty:// 9.3.2.v20150730</a><hr/>
        ,大神,这是正确的返回结果吗?
        怪盗kidou:@Dinos 即然都是Java程序,所以就没有必要了呀
        Dinos:所有的Demo都是java程序,如果跑在安卓中,第8个demo,需要调整一下线程=_=
        Dinos:还有第七个Demo,@body Blog那个,服务器返回:response.body()为空,?
      • 0472f74dae1d:这个支持https吗
        0472f74dae1d:@怪盗kidou 嗯嗯 多谢 刚想了下 只要配置那个okhttpclient就可以了
        怪盗kidou:@zhuzhaozZ 支持
      • iDragonfly:写得很好:+1: ,指出一个笔误:Example05的33行“(或Map的第个泛型参数不是String)”。
        怪盗kidou:@iDragonFly :+1: 看得真细
      • Anonymous_NO1:我见过写retrofit最好的文章
        怪盗kidou:@Anonymous_NO1 :scream: ,感谢土豪打赏
      • Anonymous_NO1:retrofit文章中讲的最好的 没有之一 赞一个 :+1:
        怪盗kidou:@Anonymous_NO1 :scream:
      • ZapFive69:好东西
      • 471cb334f492:Converter和Adapter的用法讲的挺好的,
        我的需求有点怪,例如:

        @get("/hello/bean")
        public Order bean();

        我希望是这样的服务调用方式,
        看了Adapter的代码是无法通过实现一个Adapter来解决的,Retrofit1是可以满足的,我想尝试在Retrofit2上这么用,不知道楼主有没有可行的方案
        471cb334f492:@怪盗kidou 多谢回复,我使用代理类解决的,在Retrofit2的代理类上再加一层代理
        怪盗kidou:@471cb334f492 这个不行, 2.x就只能返回call,然后调execute方法
      • Dreamer666:很好
      • f4b120b91f09:@body 不能和 @FormUrlEnCode 一起使用
        @body parameters cannot be used with form or multi-part encoding
        怪盗kidou: @Jumy 是不能一起用,对应的是不同请求体类型,不过你这个问题是出自我的源码或者博客么?我看你的这个情况更像是 body和 multipart一起用了呢🤔
      • 凤鸣游子:只会简单封装一下Okhttp...看不懂啊, 不知道是不是这文章不适合入门.
        怪盗kidou:@阿水哥哥 首先是HTTP的基础,然后okhttp,再后retrofit,rxjava虽然经常和他俩一起出现,但实际和网络请求没什么关系
        凤鸣游子:@怪盗kidou ; 最近在找工作, 但是关于网络封装块没有什么经验面试常栽跟头,只是会一点对OKhtttp的工具封装, 但是了解到okHttp没有对线程进行管理, 一次请求都会弄出一个线程. Rxjava+ retrofit +okHttp听说很流行也对线程进行了管理, 但是看了一些文章, 实在崩溃...说了这么多, 就是想问, 了解这三大坨东西(r+r+o), 需要从哪里开始?
        怪盗kidou:@阿水哥哥 😂说明我没讲清楚啊
      • 妙法莲花1234:不错哦,如果加上拦截器更好了,哈哈哈
        怪盗kidou: @追风917 拦截器那是属于okhttp的内容了呀
      • 653d1ebd6d6d:请教楼主,如果我的一个API返回的数据格式不统一有数据时和没有数据是返回的格式不一致,那怎么声明这个API呢?又该如何解析这两种数据呢?
        非常感谢!
      • 上官瑞杰:写的好详细,赞一个
        怪盗kidou:@上官瑞杰 :grin:
      • HenryChao:学到了,原来可以在studio部署web项目,赞.
      • b6c7a3ded5ff:大神你好,最近在看retrofit,以前用的xutils3。我们用的OAuth 2.0来做的持久化,请求的数据封装了一下,第一次请求数据的格式大概是这样的:
        https://www.xxxxx.com/xectoken? <grant_type=password&userName=189XXXxx00&password=111111>
        header KeyValue{key='Authorization', value=Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
        我现在主要的问题是提交header不成功,方括号<> 不知道怎么封装出来,希望大神指点一下,非常感谢 :smile:
        b6c7a3ded5ff:@怪盗kidou 那我的header通过那种方式提交呢?
        怪盗kidou:@dyhuang < 和 > 都是url里不支持的字符,所以需要urlencode一下,所以你把 < 用 %3c 代替,> 用 %3e 代替应该就可以了
      • bc270b8a8419:楼主,这个server项目怎么运行呀
        bc270b8a8419:@怪盗kidou 直接在AndroidStudio里面在RESTServer.java类那里点一下运行就可以了,非常感谢
        怪盗kidou:@GaoMatrix :flushed: 前言里就有说到哦,RESTServer.main()
      • from0:我的后台让我以二进制流的形式post一个文件 这种该咋做啊 用Multipart不行..后台不是表单 :sweat:
        from0:@怪盗kidou 还是不太会 大神可以写个demo吗
        from0:@怪盗kidou 好的 谢谢
        怪盗kidou:@Amaze 和POST一个json是一样的格式,只是File需要你自己实现converter
      • superoidlau:对retrofit介绍很全面的一篇文章, :+1:
        怪盗kidou:@ForeCheng :grin:
      • 庸碌无为:打算写一个 Retrofit2 学习笔记类的文章,想引入一些本文的内容,不知道可不可以

        会标明引用来源
        怪盗kidou:@庸碌无为 可以,没有问题 :smile:
      • 无为不争静:写得很好,学习啦 :smile: ,大神 :+1:
      • Neogx:强大···感谢楼主的原创!
        怪盗kidou:@Neogx :grin:
      • cyq7on:lz,我按照你的方法进行图片上传,没有成功,自己又改了一些参数,折腾了很久,还是失败了。这是后台接口http://open.weibo.com/wiki/2/statuses/upload,希望lz可以指点一二啊 :sob: !!!
        怪盗kidou:@cyq7on 你把application/octet-stream 根据图片类型换成 "image/png" 或 "image/jpg" 或 "image/jpeg" 试试,人家有要求用 jpg\gif\png,你用application/octet-stream ,他不会认为是图片的。
        cyq7on:@怪盗kidou
        @POST("statuses/update.json")
        @Multipart
        Call<StatusContent> publishImage(@PartMap Map<String, RequestBody> map
        ,@pART MultipartBody.Part file);
        Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(Constants.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
        Weibo weibo = retrofit.create(Weibo.class);
        Map<String, RequestBody> params = new HashMap<>();
        File file = new File(path);
        String filename = file.getName();
        RequestBody image = RequestBody.
        create(MediaType.parse("application/octet-stream"), file);
        MediaType textType = MediaType.parse("text/plain");
        params.put("source", RequestBody.create(textType,Constants.APP_KEY));
        params.put("access_token", RequestBody.create(textType,accessToken.getToken()));
        params.put("status", RequestBody.create(textType,content));
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("pic", filename, image);
        weibo.publishImage(params,filePart).enqueue...
        大概就是这样,有点凌乱。。。
        怪盗kidou: @cyq7on 你得给我看你怎么写的啊,我光看写这个也看不出问题在哪儿
      • 夏天的风h:感谢,很热心的作者 :heart:
        怪盗kidou:@夏天的风h :kissing_heart:
      • RidingWind2023:感谢你的辛苦付出
        怪盗kidou:@I二师兄I :smile: 希望帮助到更多人
      • 14fe2e7555e4:您好,看完了您的文章,觉得非常棒,但是有一点我通过源码和文章都没有能够很好地理解,对于返回的数据类Call<T>,Retrofit是怎么对T的类型进行还原的呢?编译的时候不是会将类型擦出掉么?为什么动态代理类里就能获取到准确的类型呢
        14fe2e7555e4:@14fe2e7555e4 又看了一遍他的代码,明白了,谢谢
        14fe2e7555e4:@怪盗kidou 恩 这个我在自己的代码里做过尝试的,拿到的还是T
        怪盗kidou:@14fe2e7555e4 对于Call<T> 虽然Call类没有保存泛型信息,但是对一个函数来说返回值是固定的,也就是那个接口中完整的保留了这个 T 的值。Method.getGenericReturnType(); 可以获得泛型。你可以看看 ServiceMethd.createCallAdapter() 的第一行。
      • 02af9b1cbd13:关于“/”的问题

        你的“/”可以在 service处定义
        如:@get("/blog/{id}")
        怪盗kidou:@flyou baseUrl中必须以 / 结束,在我的这个例子中是可以用以 / 开头,但别的地方可就不一定了,7.2有详细说明
      • ythmilk:我服务器返回的数据编码是GB2312,在这边显示是乱码。有什么地方能改编码格式吗?是先获取byte格式再进行转换?
        怪盗kidou:@ythmilk 你扔一个Reader给他不就可以设置编码么?还有你们后台就不知道用UTF-8么?
      • c1b405d8ae82:大神,我用的是post请求,完成的url = "http://mm.mfniu.com/themelist/essenceAsk&quot;

        Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://mm.mfniu.com/&quot;)
        .build();这边这么写


        接口的地方这么写的public interface BlogService {

        @post("themelist/essenceAsk")
        Call<ResponseBody> getFirstBlog(@Field("page") String PAGE,@field("pageSize") String String);
        }

        但是总是报错呢
        java.lang.IllegalArgumentException: @field parameters can only be used with form encoding. (parameter #1)

        假如我用post请求,直接请求,不添加参数应该怎么请求啊
        这个url为什么要分开写呢,不写在一起,这样感觉好麻烦
        谢谢大神
        怪盗kidou:@c1b405d8ae82
        你设计成没有参数的不就行了。
        @post("themelist/essenceAsk")
        Call<ResponseBody> getFirstBlog();
        }
        c1b405d8ae82:不加参数怎么调用, Call<ResponseBody> call = service.getFirstBlog("");

        还有service里面怎么调用@post("themelist/essenceAsk")
        Call<ResponseBody> getFirstBlog(@Field("page") String PAGE,@field("pageSize") String String);
        }

        大神能加下你的QQ吗,或者你加下我的429475006,
        怪盗kidou:@c1b405d8ae82 本意是不用让你重复写前面的URL,但我在博客中也说了,如果你是完整的URL,是不用管baseUrl是啥的。你这个错是没有加@UrlEncoded注解。不加参数你就不加呗
      • 47c1bb09ea8c:博主,非常不好意思的麻烦你,最近正好在用retrofit2做一个项目,但是一直出来日志,能麻烦您帮忙看下吗
        package com.buaa.news.newsproject.retrofit;

        import com.buaa.news.newsproject.domain.NewsMenuData;

        import java.io.IOException;

        import retrofit2.Call;
        import retrofit2.Callback;
        import retrofit2.Response;
        import retrofit2.Retrofit;
        import retrofit2.converter.gson.GsonConverterFactory;
        import retrofit2.http.GET;

        /**
        * Created by Administrator on 2016/6/16.
        */
        public class RetrofitAPI {

        public interface InternetService {
        @get("categories.json")
        Call<NewsMenuData> loadRepo();
        }

        public static void connectInternet(){
        Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:8080/zhbj/&quot;)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

        InternetService service = retrofit.create(InternetService.class);

        Call<NewsMenuData> call = service.loadRepo();

        call.enqueue(new Callback<NewsMenuData>(){
        @Override
        public void onResponse(Call<NewsMenuData> call, Response<NewsMenuData> response) {
        System.out.println(response.body().toString());
        }

        @Override
        public void onFailure(Call<NewsMenuData> call, Throwable t) {
        t.printStackTrace();
        }

        });

        }

        }

        NewsMenuData是我的javabean
        47c1bb09ea8c:@怪盗kidou 昨天尝试了打印日志发现并没有错误,只是接下来要用到handler传递数据,我没搞清楚。不过还是谢谢你 :smile:
        怪盗kidou:@47c1bb09ea8c 你遇到的什么问题,报的错是啥,做过哪些尝式
      • 柯翰辰K:我问下,这个不能打印日志,都看不了log
        柯翰辰K:@怪盗kidou //http 日志打印请求到的json字符串和查看log
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        这是使用的日志拦截器
        柯翰辰K:@怪盗kidou
        我自己想定义一个okhttpclient,
        Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(mOkHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
        这是我自己定义的okhttpclient,
        mOkHttpClient = new OkHttpClient.Builder()
        .cache(httpCache)
        .addInterceptor(interceptor)
        .addNetworkInterceptor(new StethoInterceptor())
        .retryOnConnectionFailure(true)
        .connectTimeout(15, TimeUnit.SECONDS)
        .build();
        我的需求是想打印下请求的url地址,但是我日志啥都没有,想请教下大腿 :smile:
        怪盗kidou:@柯翰辰K 没看懂你说的什么意思
      • 小范屯:在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody


        如果,我的数据返回值,也要响应头里解析怎么办?
        怪盗kidou:@小范屯 说错,其实只要是结果的地方,你都可以加一个Response,如用Response<User> 代替User,即Observer<User> 改为 Observer<Response<User>> ,这样就和Call一样了,这个时候你在使用observerable的map操作符,在那里取得Header并解析,最后才subscribe就行
        小范屯:@怪盗kidou 我用Rxjava和retrofit结合,像这样Observer<User> getUser()。不能使用call。就算使用call,也是在主线程,我想在子线程来做。
        怪盗kidou:@小范屯 如果使用的Call,那你在回调中也可以获得响应头啊
      • Champion是冠军:又是一篇好文章,谢谢博主
      • jasonkxs:赞下
      • acemurder:很棒
      • 8dea5ac34363:来给你加加人气哈,虽然看不懂,感觉NB的样子就行了


        ----沈习文
        怪盗kidou: @8dea5ac34363 哈哈,隔行如隔山啊
      • 教主张:最近也在看这个,官网上的也只是简单讲了一下。这篇真心写得不错
        怪盗kidou: @game_zr 其实官网上只是省略了很多类似的,该有的都有
      • 7709643ec01d:// 不同的是如果是Android系统回调方法执行在主线程
        这句注释,能不能用更直白的话再解释一下.
        怪盗kidou:@创迹者2014 在Android中可以直接更新UI
      • Guujii:先mark,回家慢慢研究
      • 夏天的风h:非常感谢~
      • 85291e84da24:谢谢分享,写的很好~

      本文标题:你真的会用Retrofit2吗?Retrofit2完全教程

      本文链接:https://www.haomeiwen.com/subject/onwvrttx.html