retrofit 学习笔记

作者: 前行的乌龟 | 来源:发表于2018-04-28 15:08 被阅读29次

    ps: 我写这篇这是很晚了,也是怪我自己啊,一直以来拖拖拉拉的就是改不了,retrofit 看着好几次了,看完有些日子就记不清了,也没记录下来,忘了都没地方找去。所以今天好好理一理 retrofit ,注解的使用还是很有必要记记的,谁也不敢打包票自己不忘是不,尤其是我,我对于这可是有深刻的印象啊,16年那会完全不写博客,看了好多东西,看完就忘,结果发现16年的收获惨不忍睹啊。

    另外说一点,写博客真是对知识点的理解和记忆是大大的加深啊,所以大家总是说好记性不如烂笔头呀

    retrofit 集成,简单使用


    1. 添加依赖
        // retrofit 依赖
        compile 'com.squareup.retrofit2:retrofit:2.4.0'
        compile 'com.squareup.retrofit2:converter-gson:2.4.0'
        compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    
    1. 编写远程接口
      retrofit 使用直接注解动态生成远程请求 API ,这让我们专注于接口声明,而不用去管理实现,既简单又明了
    public interface BlueService {
        @GET("book/search")
        Call<ResponseBody> getSearchBooks(@Query("q") String name,
                                          @Query("tag") String tag, @Query("start") int start,
                                          @Query("count") int count);
    }
    
    1. 原生简单请求
            String baseUrl = "https://api.douban.com/v2/";
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .build();
    
            BlueService blueService = retrofit.create(BlueService.class);
            blueService.getSearchBooks("小王子", "", 0, 3)
                    .enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                            try {
                                String message = response.body().string();
    
                                Log.d("AAA", "onResponse: " + message);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
    
                        }
    
                        @Override
                        public void onFailure(Call<ResponseBody> call, Throwable t) {
                            Log.d("AAA", "onFailure: 联网失败");
                        }
                    });
    

    注意啊:

    • baseUrl 跟路径必须以 “ / ” 结尾,现在强制要求了,不写会报错
    • response.body().string() 可以获取原始的 response 数据(字符串),我们使用的 .string() 而不是 .toString() , toString 出来的 class 名字。同时返回参数数据类型我们必须使用 ResponseBody 这个才行,这是 okhttp3 里面的,这样才能拿到原始响应体
    • 这个写法一般是获取 json 转 bean 或是测试通不通用的,正常我们不这么用
    1. 添加 json 数据变换
            String baseUrl = "https://api.douban.com/v2/";
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
    
            BlueService blueService = retrofit.create(BlueService.class);
            blueService.getSearchBooks("小王子", "", 0, 3)
                    .enqueue(new Callback<BookResponse>() {
                        @Override
                        public void onResponse(Call<BookResponse> call, Response<BookResponse> response) {
                            try {
                                BookResponse bookResponse = response.body();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
    
                        }
    
                        @Override
                        public void onFailure(Call<BookResponse> call, Throwable t) {
                            Log.d("AAA", "onFailure: 联网失败");
                        }
                    });
    

    这个我们在 retrofit.build 时添加了类型转换器,就可以让 retrofit 自动帮我们转换 json 了,然后远程接口,callback 我们都可以直接使用具体的数据类型了。我们还是 try 一下保险

    1. 添加 rxjava 支持,变换请求结果为 observable
    public interface BlueService {
        @GET("book/search")
        Observable<BookResponse> getSearchBooks(@Query("q") String name,
                                                @Query("tag") String tag, @Query("start") int start,
                                                @Query("count") int count);
    }
    
            String baseUrl = "https://api.douban.com/v2/";
    
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
    
            BlueService blueService = retrofit.create(BlueService.class);
    
            blueService.getSearchBooks("小王子", "", 0, 3)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Observer<BookResponse>() {
    
                        Disposable disposable;
    
                        @Override
                        public void onSubscribe(Disposable d) {
                            disposable = d;
                        }
    
                        @Override
                        public void onNext(BookResponse bookResponse) {
                            Log.d("AAA", "onNext: " + bookResponse.getBooks().size());
                        }
    
                        @Override
                        public void onError(Throwable e) {
    
                        }
    
                        @Override
                        public void onComplete() {
                            disposable.dispose();
                        }
                    });
        }
    

    要注意 rxjava2 的支持和1不同,rxjava2 的支持请安上面的依赖添加

    中间插一段 http 协议基础


    我也是个搬运工,http 协议我摘一点作用明显的,大家看看,然后觉得不怎么懂就去专门看下,我在下面会附上地址的

    HTTP 的请求报文分为三个部分 请求行、请求头和请求体,格式如图:


    1724103-c43900117e983241.png
    1. 请求行

    请求行(Request Line)分为三个部分:请求方法、请求地址和协议及版本,HTTP/1.1 定义的请求方法有8种:

    • GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种GET和POST
    • RestFul 规范的接口的话会用到 GET、POST、DELETE、PUT,分别对应数据库的增删改查操作:
      • PUT -> 增
      • DELETE -> 删
      • POST -> 改
      • GET -> 查

    在了解请求地址之前,先了解一下URL的构成:


    1724103-95c263da5671d6fa.png

    所以我们在 retrofit 的 baseUrl 添加跟地址的行为就是添加 http 协议和主机地址

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
    

    剩下的 http 基础内容还有很多,我就不复制了,大家看这里:你应该知道的HTTP基础知识,一定要看哦,要不后面你会晕,有地方搞不明白的

    1. 请求头
      其中有一点我们一定要弄清楚的是 http 请求体类型,也就是我们会看到的 Content-Type
    • Content-Type:application/json
      表示这是提交的 json
    • Content-Type:application/x-www-form-urlencoded
      表单提交
    • Content-Type:multipart/form-data; boundary={boundary}
      文件提交,也叫模拟表单

    重头戏来啦,注解详解


    整篇文章的重点就在这啦,前面的都是浮云,初次使用的同学可能觉得有些繁琐,有些不好理解,但是对于有点水平的同学们来说就不叫事了不是,所x以注解这个才是我们需要重点学习的,看了上面的 http 协议的部分知识点,我们再去研究下 retrofit 的注解就不是那么挠头了

    1. 请求类型

    请求类型是请求头中的一个内容,写在 header 里,字段是:Content-Type,是描述我们给服务器发送的数据采用什么格式发送,下面放个例子:

    interface APIStore {
       @Headers({"Content-Type: application/json","Accept: application/json"})
       @POST ("vdyweb/ws/rest/Login")
       Call<ResponseBody>getMessage(@Body RequestBody info);   
    }
    

    大家看到在请求头中添加了 Content-Type 字段的数据,这里总结下有几种请求方式啊,这点很重要的,写不对你的远程通讯就会不同,或是服务器拿不到数据哦。retrofit 已经为我们封装好了常用的请求方式,这里结合的说一下:

    • 什么都不写
      什么都不写表示传递参数没有规范,发送过去的是二进制流,传值你得用 @Body 注解,服务器接受到的是二进制流,然后可以转成字符串按 json 解析数据,不过一般我么不推荐这么写。
    • json 提交
      json 的话我司现在就是用这种方法,适合做 post 万能请求接口,不过在 RestFul 规范的今天, 不在推荐这么写了,我们也得让后台兄弟舒服不是。所以我们在 retrofit 找不到这个注解,就是因为不推荐这种写法了, 下面是自己写的方式,这里只是接口声明,详细在后面
    interface APIStore {
       @Headers({"Content-Type: application/json","Accept: application/json"})
       @POST ("vdyweb/ws/rest/Login")
       Call<ResponseBody>getMessage(@Body  Book book);  
    }
    

    @Body 的数据对象,Retrofit2 会自动转成 Gson 字符串发送

    • get 方式
      get 方式很特殊,大家都知道 get 请求方式很特殊,把一切都拼接在 url 地址中,所以没那么复杂, Content-Type 不用设置
    • 表单提交
      表单提交,数据都以 key—value 的形式存在请求体中,服务器获取参数也是用 key—value 的形式去拿数据,作为最常使用的提交方式,retrofit 已经给我们封装好了,专门有一个字段
        @FormUrlEncoded
        Observable<BookResponse> getSearchBooks(@Query("q") String name);
    

    自己写的话就是

        @Headers("Content-Type:application/x-www-form-urlencoded")
        Observable<BookResponse> getBooks();
    
    • 模拟表单
      在表单提交的基础上可以添加文件数据,retrofit 也给我们封装好了
        @Multipart
        Observable<BookResponse> getBooks();
    

    自己写的话就是

        @Headers("Content-Type:multipart/form-data; boundary={boundary}")
        Observable<BookResponse> getBooks();
    

    boundary 部分可写可不些,我看有人不写 boundary 也没事,boundary=xxxxxxx,xxxxxx规定了请求体中的内容分隔符

    • 文档相关资料


      retrofit3.png

    2. 请求方法

    请求方法上面 http 基础应说过了,这个不难理解,需要注意的是 restFul 规范下的部分:
    * PUT -> 增
    * DELETE -> 删
    * POST -> 改
    * GET -> 查
    这里要提一下 http 这个请求方法,HTTP注解则可以代替任意一个青谷去方法,有3个属性:method,path,hasBody,下面是例子:

    public interface BlogService {
        /**
         * method 表示请的方法,不区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "get", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getFirstBlog(@Path("id") int id);
    } 
    

    3. 数据接收类型

    在 http 协议中可以指定接受服务器返回数据的类型,这个一般我们不用自己去设置,retrofit 默认就是 json 格式的,但是自己也可以设,使用的是上面可以看到的 Accept 这个字段

    @Headers({"Content-Type: application/json","Accept: application/json"})
    

    写在 head 请求头了,看到这大家可以发现,这些设置除了 get/post 之外,所有的请求设置都是写在请求头里的,请求头就是干这事的,类似于我们常用的 config ,包裹配置参数和一些通用参数。
    retrofit 还提供了 @Streaming 这个注解,我们接收的数据不再是 json 的了,而是一个二进制输入流。

        @Streaming
    

    4. 传参相关参数

    retrofit4.png

    retrofit 用来在不同请求方法中传递参数的注解都在上面了,单说没意思,结合 get / post 和请求不同的数据来说最简单明了,容易懂。

    5. get 传参详解

        @GET("xxxUrl")
        Observable<User> getUserInfo(
                @Part("xxxUrl") String userUrl,
                @Query("userid") int id,
                @QueryMap Map<String, String> options,
                @Query("list") List<String> options2);
    

    可以看到 get 可以接受4种传参注解:

    • @Part -1
      用来替换具体的 utl 路径的,一般我们都是直接就写 url 地址了,这里通过 @Part 提供一种动态传入 url 地址方式,@Part 注解是所有请求方法通用的。
    • @Part -2
      上面的 @Part 写法是整体替换具体的 URL 请求,其实要是后台同学的 restFul API 写的规范的话,我们请求,操作数据的 URL 也是可以做到动态的,不是费的一个接口写一个固定的地址给我们。这时 @Part 可以用来实现动态替换 URL 中参数的事,使用 {“xxx”} 来标记 URL 中可变的部分,下面就是一个例子:
        @GET("book/{id}")
        Observable<User> getBook(@Part("id") String id);
    
    • @Query
      传单个参数,Query("userid") 里面的字符串是 key,后面指定 value 的类型
    • @QueryMap
      可以接受多个参数,所有参数直接写在 map 里
    • Query("list")
      get 方式支持直接传入一个集合,可以看到其实和我们单个传参一样,只不过区别是接收的数据是单个对象,还是一个集合对象

    6. post 传参详解

        @POST("xxxUrl")
        Observable<User> getUserInfo(
                @Field("userid") int id,
                @FieldMap Map<String, String> options,
                @Field("list") List<String> options2);
    

    post 传参其实和 get 差不多,却别不多,一个是 query ,一个是 field

    • @Field
      传单个参数,和 @Query 一样
    • @FieldMap
      传多个数据
    • @Field("list")
      同样支持直接传递集合数据类型
    • @POST
      @POST 除了get 都能用,用来传递一个数据对象,服务器按 stream 流的方式接收数据
        @POST("name")
        Observable<User> getName(@Body Book book);
    

    7. 上传文件

    上传文件一直是个有问题的地方,不管是 Xutils,okhttp,retrofit 都有不是很明确的地方,这里就能看出网络基础是多么重要,有时候你写完之后,服务器接收不到参数,或是问你发的数据用的什么类型,我怎么接受,你都回答不上来,这个很尴尬不是,我是遇到过,还好后台的兄弟没打我脸,哈哈哈哈.......

    不看我写的,看看这些文章也是可以的:

    有一点我们要说,表单提交的时候参数的编码问题 enctype属性。enctype:规定了form表单在发送到服务器时候编码方式,他有如下的几个值:

    • application/x-www-form-urlencoded
      默认值,不写就是这个,retrofit 的表单默认也是这个,不能用来传文件,也就是二进制数据
    • multipart/form-data
      所有的数据以二进制流发送
    • application/otcet-stream
      很奇怪,我查了查是八进制.........这个恕我真的不知道为啥有二进制不用,去用八进制
    • text/plain
      纯文本格式

    在带有 file 的表单提交中:

    • file 只能用流的形式上传
      • 可以用:multipart/form-data / application/otcet-stream 这2种流上传,差别估计是后台接受数据的 API 不同。
    • 文本参数可以用流的方式上传,也可以用文本格式上传
      • 可以使用 multipart/form-data 这样后台用流接数据,getParame 拿不到数据的。
      • 最好用 text/plain 文本格式传,这样后台获取数据和原来的方式相同。

    好了看过这些,我们心里总算是对这些弯弯绕的东西有些初步了解了,再去和后台沟通也大概知道怎么说了,查资料也知道去找什么东东了

        @Multipart
        @POST("upload")
        Call<ResponseBody> uploadMultipleFiles(
                @Part("description") RequestBody description,
                @PartMap Map<String, RequestBody> options
                @Part MultipartBody.Part file1,
                @Part MultipartBody.Part file2);
    

    这是一个带其他参数的文件上传 API ,大家注意看注解如何使用,这个是固定的,大家背下来就行。期中 @Part("description") 里面的字符串是普通文本参数的 key

    我们来看看重点的 java 代码:

    • 创建文本参数
      注意这里我用的是 text/plain 发送文本参数,用 multipart/form-data 二进制的方式也可以的
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "文本参数");
    
    • 创建 file 参数
            File imageFile = new File("一张图片");
            RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
            MultipartBody.Part data = MultipartBody.Part.createFormData("file", imageFile.getName(), requestBody);
    
    • 文件数据传入 map 集合中的写法
      可能我们的接口是这样的
        @POST("NewsServlet")
        @Multipart
        Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> map);
    

    所有参数都放在这个集合中,集合参数的放置如下,注意 file 类型的参数的 key 要包含 file 的name 进去,需要自己拼接字符串的

            NewsService newsService = createRetrofit().create(NewsService.class);
             Map<String, RequestBody> fileUpload3Args = new HashMap<>();
    
            MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "txy");
            RequestBody age = RequestBody.create(textType, "18");
    
            fileUpload3Args.put("name", name);
            fileUpload3Args.put("age", age);
    
            //构建要上传的文件
            File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("application/otcet-stream"), file);
    
            fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);
    
            Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args)
    

    上传文件基本就是这些了,这块容易忘,容易混,记下来非常有必要的。

    8. 上传图片

    图片也是文件,但是为啥要单独拿出来说呢,因为图片有自己单独的数据格式 : image/png

    RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), imageFile);
    MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", "文件名.png", photoRequestBody);
    

    9. 添加请求头参数

    请求头参数我们可以在网络请求的 API 接口里写,也可以给 okhttp 添加一个拦截器进来,在请求构建完成,发送前的那一刻拦截,然后添加请求头数据,这样适合添加动态可变参数

    • @Headers 添加静态请求头参数
    public interface BlueService {
        @Headers("Cache-Control: max-age=640000")
        @Headers({
            "Accept: application/vnd.yourapi.v1.full+json",
            "User-Agent: Your-App-Name"
        })
        @GET("book/search")
        Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
                @Query("tag") String tag, @Query("start") int start, 
                @Query("count") int count);
    }
    

    注意添加一条和多条的区别

    • @Header 添加动态请求头参数
    public interface BlueService {
        @GET("book/search")
        Call<BookSearchResponse> getSearchBooks(
        @Header("Content-Range") String contentRange, 
        @Query("q") String name, @Query("tag") String tag, 
        @Query("start") int start, @Query("count") int count);
    }
    
    • 添加拦截器动态添加请求头参数
    public class HeadInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request original = chain.request();
            Request request = original.newBuilder()
                .header("User-Agent", "Your-App-Name")
                .header("Accept", "application/vnd.yourapi.v1.full+json")
                .method(original.method(), original.body())
                .build();
            return chain.proceed(request);
        }
    }
    
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(new HeadInterceptor ())
                    .build();
    
            Retrofit retrofit = new Retrofit.Builder()
                    .client(client)
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
    

    添加 header 参数 Request 提供了两个方法:

    • header(key, value)
      如果有重名的将会覆盖
    • ddHeader(key, value)
      允许相同 key 值的 header 存在

    10. 为某个请求设置完整的URL

    有个别的请求的 URL 不用用的我们基础的 baseurl 的,怎么办,我们可以使用 @Url 注解来忽略 baseurl 的

    @GET
    public Call<ResponseBody> profilePicture(@Url String url);
    
    Retrofit retrofit = Retrofit.Builder()  
        .baseUrl("https://your.api.url/");
        .build();
    
    BlueService service = retrofit.create(BlueService.class);  
    service.profilePicture("https://s3.amazon.com/profile-picture/path");
    

    json 上传的再次说明


    json 上传数据不推荐,但是有时候我们真的需要,这里多记录一下找到的东西。

    json 除了我们直接 @Body Book book 直接写具体的数据类型的做法,我们也可以使用 RequestBody 来写

    public interface PostRoute {  
       @Headers({"Content-Type: application/json","Accept: application/json"})
        @POST("api/FlyRoute/Add")  
       Call<FlyRouteBean> postFlyRoute(@Body RequestBody route)RequestBody  
    }  
    
    // 先 json 一个字符串数据出来
    Book book= new Book ();  
    Gson gson=new Gson();  
    String route = gson.toJson(book);
    
    // 我们生成一个 RequestBody 请求对象出来
    PostRoute postRoute=retrofit.create(PostRoute.class);  
    RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),route);  
    Call<Book> call=postRoute.postFlyRoute(body);  
    

    添加日志管理


    这个是 okhttp 的部分,但是呢我们不打算专门写一个 okhttp 的入了,在 retrofit 中我们已经充分使用了 okhttp 了,索性就一起写了

    retrifit 又开源的日志拦截器,要不我们就自己写拦截器打印日志,不过肯定大的不如直接诶个开源的全啊。

    添加依赖

    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    

    使用

            // 创建日志拦截器对象出来
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            //包含header、body数据
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    
            // 添加日志拦截器到 okhttp 对象中
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .build();
    
            // 把 okhttp 对象添加到 retrofit 对象中
            Retrofit retrofit = new Retrofit.Builder()
                    .client(client)
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
    

    打印样式图:


    436713-a7e0267212e9f809.png

    注意里面 --> 、<-- 箭头的方向:

    • --> 表示我们请求的内容,包括请求头参数,请求信息等,这里我们是最简单的 get 请求所以表现不出来
    • <-- 是我们接受到的内容,包括接受数据形式
    • interceptor 有几个级别,我们用 body 就行,body 会代印包括头在内的所有信息

    facebook 网络调试器 Stetho


    我还看到有一位朋友提到 facebook 开源了 Stetho 网络监测工具,试了试我没成功,有兴趣的朋友请看:使用OkHttp高效开发调试

    其他内容


    参考资料


    相关文章

      网友评论

      • NineColorDeer:这么好的文章没人看吗?写的很不错,谢谢作者分享
        前行的乌龟:@Wayne_Simon 我也不做推广,能看到的都是有缘人,很高兴能帮到你

      本文标题:retrofit 学习笔记

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