Retrofit2 学习总结

作者: 庸碌无为 | 来源:发表于2016-08-11 11:34 被阅读8244次

    原文链接:
    http://www.jianshu.com/p/a8b88c7fe831
    http://blog.csdn.net/qq_24889075/article/details/52181133

    概述

    在学习 Retrofit2 的过程中受到了一些阻力,现 Retrofit2 学会使用了,特此写此文验证所学知识。同时也希望帮助和我一样在学习Retrofit2遇到困难的猿们。

    当我在刚开始学习 Retrofit2 的时候并不知道Retrofit2是什么东西,后来逐渐了解 “它可能是一个方便我们网络请求的库 ,可以帮我们让请求网络变得更灵活、易于维护”。然后还可以和时下比较火热的RxJava进行完美融合。

    先看看如何使用,如何进行一个简单的Get/Post请求

    Retrofit2 入门

    首先在build.gradle中添加如下代码,添加Retrofit2库

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

    有的教程里写要手动添加okhttp的库,其实是不需要的,因为retrofit2封装了okhttp,不信自己编译下看看:


    gradle External libraries

    添加完库,我们开始正文。

    我们在项目中进行网络请求时,肯定不是一个地址吧,那么这些请求地址存放在哪呢?是在哪个类里请求就在哪个类里存放,还是统一放在一个专门存地址的类中呢?
    我在学习Android期间就是哪里有请求就放哪里,后来有人告诉我要集中存放。于是后来就建立一个AppURL.java所有地址都存放这里。
    然而Retrofit2这里也可以这么理解:专门有一个‘地方’来存储链接地址(也可以创建多个‘地方’存储)。这个‘地方’不是类而是接口,在这个接口中可以设定请求地址的一些信息。就像这样:

    public interface AppURL { 
        @GET("index") 
        Call<ResponseBody> getIndex();
        ...
        ...
    //  可以存储多个连接地址
    }
    

    说明:
    接口名“AppURL ”可以随意定义,根据自己喜好。
    第一行:代表get请求,请求地址为“设定的BaseURL/index” (BaseURL设定在下面介绍如何设定)
    第二行:getIndex是方法名;Call<ResponseBody>是默认返回类型,暂且不要管能干什么。

    下面我们看下如何使用这些地址进行网络请求:

    1. 创建Retrofit对象,并设定BaseURL
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("https://www.这里是BaseURL.com/")
                    .build();
    

    需要注意的是BaseURL必须以‘/’结尾

    1. 获取“AppURL”对象(创建请求服务)
    AppURL url= retrofit.create(AppURL.class);
    
    1. 用AppURL对象得到具体请求对象(获取请求服务方法 )
    Call<ResponseBody> call = url.getIndex();
    

    后期也会在这一步中进行设置链接参数、请求头等

    1. 开始(异步)请求
    call.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                    Log.e("tag", response.body().toString());//获得数据
                }
                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
                    Log.e("tag", t.getMessage());//请求失败
                }
            });
    

    好了,现在一个简单的网络请求就写完了。不是很难吧(当时我可是觉得挺难 _

    单单会这些是远远不够的,那么我们如何来满足项目中各种各样的需求呢?请继续看

    Retrofit2 进阶

    自动解析

    其实在Retrofit2中,我们不用自己来解析数据,Retrofit2可以帮我们自动解析,怎么做呢?请看:

    1. 添加
      在Retrofit2中是用Gson解析的,所以我们要在build.gradle中添加。
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    

    有写教程说还有添加gosn库,经过测试是不需要的,converter-gson中已经封装了gson库。
    需要注意的是converter-gson和retrofit版本号应为一致,在这里我都用2.1.0

    1. 创建Bean
      创建一个JavaBean,用于解析服务器返回数据。就和我们平常自己解析创建的Bean一样,推荐用As的插件JsonFormat,挺方便的。
      我们创建一个Bean起名为MBean.java(随便起的)
    2. 为retrofit添加addConverterFactory
      添加后的代码如下:
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("https://www.这里是BaseURL.com/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
    
    1. 修改泛型
    //在AppURL 接口中修改:
    @GET("index")
        Call<MBean> getIndex();
     Call<MBean> call = url.getIndex();
    //调用服务请求时的修改
            call.enqueue(new Callback<MBean>() {
                @Override
                public void onResponse(Call<MBean> call, Response<MBean> response) {
                    Log.e("tag", response.body().toString());//解析好的数据
                }
    
                @Override
                public void onFailure(Call<MBean> call, Throwable t) {
                    Log.e("tag", t.getMessage());
                }
            });
    

    这样输出的就直接是用Gson解析好的MBean数据了。

    retrofit不仅仅只支持gson,还支持其他许多json解析库。 如:
    Jackson、Moshi、Protobuf、Wire、Simple XML、Scalars (primitives, boxed, and String) 具体请看官网

    固定地址/路径替换

    说到固定地址了,那么固定地址长什么样呢?

    http://www.BaseURL.com/index
    http://www.BaseURL.com/user
    http://www.BaseURL.com/login
    http://www.BaseURL.com/register/qq
    http://www.BaseURL.com/register/wechat
    

    那么应该如何请求呢?除了上面例子中的写法还可以这样写:

        @请求类型("{name}")
        Call<返回类型> 方法名(@path("name") String name);
    如:
    
      @GET("{name}")
        Call<MBean> get(@Path("name") String urlName);
    
    //如果想访问登录的链接,在使用时就直接url.get("login");就可以。这样请求的地址就是http://www.BaseURL.com/login 是不是很方便
    //注意@Path和{}中的参数名要一致
    

    @Path的应该作用暂且理解为 为上面的GET请求传值

    带参地址

    带参地址长这样子:

    www.BaseURL.com/login?page=1
    www.BaseURL.com/movieTop?start=1&count=5
    www.BaseURL.com/login?username=testuser&password=123456
    

    上个用的是@Path,这回用的是@Query其实和@Path一样
    直接看例子:

    @GET("movieTop")
        Call<MBean> get(@Query("start") int start, @Query("count") int count);
    //假设想查询电影排行榜的第1-5名,则使用时候是这样:
    Call<MBean> call = url.get(1, 5);
    //请求的地址是这样:www.BaseURL.com/movieTop?start=1&count=5
    

    Post带Body请求

    使用@Body来声明即可,如下:

     @POST("/aaa")
     Call<MBean> send( @Body UserInfo body);
    //使用
    Call<MBean> call=url.send();
    
    这里的UserInfo就是要发送的实体,Retrofit2 会自动转成Gson  
    

    学到这里,一般的网络请求都可以了进行,可以应付一阵子了。
    还有一些要求较高的请求,请看下节。

    Retrofit2 大成

    如果看到这里,相信对Retrofit2的基本请求会用了,那么这节就说一说其他的网络请求。

    表单(FormUrlEncoded)

    我们可以使用@FormUrlEncoded注解来发送表单数据。使用 @Field注解和参数来指定每个表单项的Key,value为参数的值。

    @FormUrlEncoded
    @POST("user/login")
    Call<User> updateUser(@Field("username") String name, @Field("password") String pass);
    

    单文件上传(Multipart)

     @Multipart
        @POST("register")
        Call<User> registerUser(
          @Part MultipartBody.Part headPhoto, 
          @Part("username") RequestBody userName, 
          @Part("password") RequestBody passWord
      );
    //使用
    MediaType textType = MediaType.parse("text/plain");
    RequestBody name = RequestBody.create(textType, "二傻子");
    RequestBody pass = RequestBody.create(textType, "123456");
    
    RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), 文件对象);
    MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", "文件名.png", photoRequestBody);
    
    Call<User> call = url.registerUser(photo,name, pass);
    

    @Part 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型;

    动手测试:username的RequestBody 换成String是否可以

    多文件上传

    @Multipart
    @POST("register")
    Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
    //使用
    RequestBody photo = RequestBody.create(MediaType.parse("image/png"), 文件对象2);
    RequestBody photo = RequestBody.create(MediaType.parse("image/png"), 文件对象2);
    Map<String,RequestBody> photos = new HashMap<>();
    photos.put("对应的key1"; filename=\"文件名1.png", photo1);
    photos.put("对应的key2"; filename=\"文件名2.png", photo2);
    photos.put("username",  RequestBody.create(null, "二傻子"));
    
    Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123456"));
    

    也可以都塞Map里上传,也可以只在Map中上传文件,随你喽~
    文章结尾有参考链接。不一样的上传方式。

    文件和参数混合上传

    比如修改用户信息:头像、用户名

        @Multipart
        @POST("地址")
        Call<User> head(@Part("key") RequestBody username,
                                  @Part("key\"; filename=\"完整的用户名") RequestBody img,
                                  @Part("key") RequestBody  sex,
                                  @Part("key") RequestBody  key
        );
    //使用
    Call<User> call =url.head(
                    RequestBody.create(MediaType.parse("text/plain"),  username),
                    RequestBody.create(MediaType.parse("image/*"),  图片对象),
                    RequestBody.create(MediaType.parse("text/plain"),  sex),
                    RequestBody.create(MediaType.parse("text/plain"),  key)));
    
    不知道还有没有其他方法 以后有时间研究下
    
    
    

    请求头

    1. 固定请求头
         @GET("地址")
         @Headers("Accept-Encoding: application/json")
         Call<返回类型> 方法名();
        // 请求结果:
        // GET 地址 HTTP/1.1
        // Accept-Encoding: application/json
    
    1. 动态请求头
         @GET("地址")
         Call<返回类型> 方法名(@Header("Location") String location);
        //使用
        url.方法名("参数");
        // 请求结果:
        // GET 地址 HTTP/1.1
        // Location: 参数
    
    1. 固定+动态
         @GET("地址")
         @Headers("Accept-Encoding: application/json")
         Call<返回类型> 方法名(@Header("Location") String location);
        //使用
        url.方法名("参数");
        // 请求结果:
        // GET 地址 HTTP/1.1
        // Accept-Encoding: application/json
        // Location: 参数
    

    其他

    1. 注意XXXMap的使用, 比如@PathMap,@FieldMap等,具体怎么使用,可以自己研究,研究不出来的可以参考结尾处的文章。

    2. 下载文件得说说,在Retrofit2中下载文件是默认存储到缓存中,也就是说不能进行大的文件下载,如果要下载大文件要用 @streaming 。但话说回来了,下载文件我们可以不用Retrofit2啊,直接用okhttp不就得啦

    3. 我们是可以添加 okhttpclient 到retrofit中去,这样可以来统一的log管理,给每个请求添加统一的header等,那么我们没有添加为什么没有报错呢? 因为在build()方法中会判断是否为空,如果我们没有添加okhttpclient 则就是空了,那么retrofit会自动给我们添加了一个new OkHttpClient();

    4. execute是同步执行 需要在子线程中执行、enqueue是异步执行。

    5. 看下我这几个图,整理一下思路吧

    HTTP请求方法

    以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path、hasBody, 这里是用HTTP注解实现的例子

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

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

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

    Retrofit2 独断万古

    首先说下如何和当前火热的RxJava进行配合使用。

    1. 引入RxJava支持 (版本号要一致)
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    
    1. 写接口
    @GET(" ^_^ ")
        Observable<MBean> get();
    
    1. 通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持
    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://www.BaseURL.com/")
          .addConverterFactory(GsonConverterFactory.create())//自动通过Gson转josn,上面有提到
          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加RxJava支持
          .build();
    
    1. 使用
    url.get()
            .subscribeOn(Schedulers.io())
            .subscribe(new Subscriber<MBean>() {
                @Override
                public void onCompleted() {
                }
                @Override
                public void onError(Throwable e) {
                }
                @Override
                public void onNext(MBean mBean) {
                }
            });
    

    根据需要添加RxAndroid,这个版本号没有要求。

    剩下的内容讲的主要是进行自定义 Converter自定义CallAdapter。还有就是源码的解析
    这里可以参考结尾处的链接,不献丑了。

    Retrofit2 + RxJava 第一次使用出现的问题

    忘记添加

    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    
    

    好的Retrofit2 + RxJava Demo参考:
    https://github.com/rengwuxian/RxJavaSamples


    参考地址

    这些是我在学习过程中收集的一些资料,个人感觉还不错。
    本文部分内容来自于下面部分文章

    鸿洋:http://blog.csdn.net/lmj623565791/article/details/51304204#t1
    图片来源(经过作者授权拿的图):http://www.jianshu.com/p/308f3c54abdd

    其他博客:
    http://blog.csdn.net/ljd2038/article/details/51046512
    http://blog.csdn.net/biezhihua/article/details/49232289
    多文件上传参考:
    http://www.chenkaihua.com/2016/04/02/retrofit2-upload-multipart-files.html
    http://www.jianshu.com/p/acfefb0a204f

    https://futurestud.io/blog/retrofit-getting-started-and-android-client

    小说看多了,起的名有些怪。。。 见谅啊 _

    相关文章

      网友评论

      • 知足青年:博主 post的时候返回com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 3 path $.
      • 潜沉:语言简练,含义清楚,逻辑通顺
      • 一只文艺猿猿猿:这是我看的最通俗易懂的文章,之前想写个demo,看别人的都不太容易理解,看完之后分分钟写个demo,soeasy! 哥们总结的很到位,语言简练不啰嗦,挺好~!!!!!
      • 三也视界:如果服务器同一个接口会根据传递的参数不一样返回不同的数据结构,这种情况怎么处理,比如参数=0返回的是人的数据,参数=1返回的是公司数据
      • Bugme:获取“AppURL”对象(创建请求服务)

        AppURL url= retrofit.create(AppURL.class);
        用AppURL对象得到具体请求对象(获取请求服务方法 )

        Call<ResponseBody> call = getIndex.getIndex();



        这里的getIndex.getIndex(); 请问是否写错了? 是否改为url.getIndex(); 本人新手,勿怪
        庸碌无为:感谢指正
      • 菜稥:你好博主,请问下怎么自动解析像这样的格式的Json { ["name":"1","value":"1" ], ["name":"2","value":"2" ], ["name":"3","value":"3" ] },格式可能写错了,就是json里面直接全是数组这情况,怎么写自动解析的Bean,,,谢谢博主了
        菜稥:@Bugme 我试了,只会生成数组里面的一组数据的bean,retrofit解析还是会报,解析是jsonarray但是发现的是jsonobject这个错误
        Bugme:在AS上面添加GsonFormat插件,然后复制进对应的代码会自动生成类哦~
        庸碌无为:写了个伪代码 希望能帮到你
        class {

        ArrayList<Data> list;

        class Data {
        String name;
        String value;
        }
        }
      • Ja_Nein:有了com.squareup.retrofit2:converter-gson:2.1.0
        就不需要 compile 'com.squareup.retrofit2:retrofit:2.1.0'了
      • 庸碌无为:你打印的Bean里首先要复写toString方法。不然打印就会是地址如:com.cqc.retrofitdemo01.IPBean@41391cd0

        如果想打印Json(或者查看服务器返回的数据)的话 可以将返回类型的泛型写成ResponseBody 这样就能看见的了比如:Call<ResponseBody> 或者Observable<ResponseBody>。
        retrofit是可以内置Gson转化的,就是人家内部转化好了 你拿Bean直接用的,不要自己解析
      • 北方的天空2000:用retrofit的话,可不可行打印出服务器发回的数据json,然后我根据json的格式再解析成javaBean?怎么获取json数据?打印出来的都是json=com.cqc.retrofitdemo01.IPBean@41391cd0,IPBean时javaBean
      • 十方天仪君:我荒天帝来慢了,道友不错
      • 5466dabd27c2:就是需要解析不符哦,因为手动解析就可以不符。。
      • 5466dabd27c2:如果解析复杂的 层层嵌套的 map 和list<Model> 那应该怎么自动解析呢
        庸碌无为:@5466dabd27c2 将Json转换为对象了,也就是上面提到的MBean,可以叫JavaBean 也可以叫mode 也可以叫pojo之类的。 本文--->Retrofit2 进阶--->自动解析这里有提到
        5466dabd27c2:@庸碌无为 你知道用retrifit2,gson 把json转化为String吗
        庸碌无为:@5466dabd27c2 兄弟请参考这里
        https://github.com/rengwuxian/RxJavaSamples

      本文标题:Retrofit2 学习总结

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