Retrofit2详解

Retrofit介绍

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

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

准确来说,网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。它的一个特点是包含了特别多注解,方便简化你的代码量。并且还支持很多的开源库(著名例子:Retrofit + RxJava)。Retrofit和OkHttp都是square公司写的.

Retrofit官网

github地址

Retrofit的优点

①超级解耦
②可以配置不同HttpClient来实现网络请求
③支持同步、异步和RxJava
④可以配置不同的反序列化工具来解析数据,如:json、xml
⑤请求速度快,使用非常方便灵活

Retrofit依赖

   implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    //gson转换器
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    //rxjava转换器
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    //string转换器
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'

Retrofit使用步骤

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

入门案例:get无参异步请求

测试Url:

https://www.wanandroid.com/banner/json

ApiService

public interface ApiService {

    /**
     * get无参请求
     * https://www.wanandroid.com/banner/json
     */

    @GET("banner/json")
    Call<ResponseBody> getBanner();
 
}

public class MainActivity extends AppCompatActivity {

     public static final String BASE_URL = "https://www.wanandroid.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        //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.getBanner();

        //4.Call对象执行请求(异步、同步请求)

        //同步请求:不常用,一般使用异步请求
        //Response<ResponseBody> execute = call.execute();

        //异步请求
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                //onResponse方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui
                if (response.isSuccessful()) {
                    try {
                        String result = response.body().string();
                        Log.e("xyh", "onResponse: " + result);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                //onFailure方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui
                Toast.makeText(MainActivity.this, t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });

        //call.cancel(); //取消
    }
}

请求结果:

2020-10-21 14:58:05.494 9204-9204/com.xyh.retrofit E/xyh: onResponse: {"data":[{"desc":"享学~","id":29,"imagePath":"https://wanandroid.com/blogimgs/08240888-1d86-4eb5-8012-b3fd6945cbb1.jpeg","isVisible":1,"order":0,"title":"【Android开发进阶教程】热修复架构方案原理详解与项目实战","type":0,"url":"https://www.bilibili.com/video/BV1f54y1k77e"},{"desc":"","id":6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"https://www.wanandroid.com/navi"},{"desc":"一起来做个App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":1,"title":"一起来做个App吧","type":1,"url":"https://www.wanandroid.com/blog/show/2"},{"desc":"","id":20,"imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png","isVisible":1,"order":2,"title":"flutter 中文社区 ","type":1,"url":"https://flutter.cn/"}],"errorCode":0,"errorMsg":""}

Retrofit注解

retrofit通过使用注解来简化请求,大体分为以下几类:

  1. 用于标注请求方式的注解
  2. 用于标记请求头的注解
  3. 用于标记请求参数的注解
  4. 用于标记请求和响应格式的注解

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

这里写图片描述

Retrofit2 的baseUlr 必须以 /(斜线) 结束

创建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有参请求

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

    /**
     * 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);
public class MainActivity extends AppCompatActivity {

    public static final String BASE_URL = "http://qt.qq.com/php_cgi/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        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());
                    }
                });
    }
}

@QueryMap注解

@QueryMap:参数太多时可以用@QueryMap封装参数,相当于多个@Query

    /**
     * @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<ResponseBody> getNewsInfo(@QueryMap Map<String, String> map);
        Map<String, String> map = new HashMap<>();
        map.put("id", "12");
        map.put("page", "0");
        map.put("plat", "android");
        map.put("version", "9724");
        
        Call<ResponseBody> call = apiService.getNewsInfo(map);

gosn转换器:直接返回对象

默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,而Converter是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型。

通过GsonConverterFactory为Retrofit添加Gson支持

implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
   /**
     * gson转换器
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/0/json")
    Call<BaseEntity<ArticleBean>> getArticleData(@Query("cid") int id);
public class MainActivity extends AppCompatActivity {

    public static final String BASE_URL = "https://www.wanandroid.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //1.创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                .build();

        ApiService apiService = retrofit.create(ApiService.class);

        Call<BaseEntity<ArticleBean>> call = apiService.getArticleData(60);

        call.enqueue(new Callback<BaseEntity<ArticleBean>>() {
            @Override
            public void onResponse(Call<BaseEntity<ArticleBean>> call, Response<BaseEntity<ArticleBean>> response) {
                BaseEntity<ArticleBean> entity = response.body();
                Log.e("xyh", "onResponse: "+entity.getData().toString() );
            }

            @Override
            public void onFailure(Call<BaseEntity<ArticleBean>> call, Throwable t) {

            }
        });

    }
}

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

   /**
     * @Path :用于url中的占位符,所有在网址中的参数(URL的问号前面的参数)
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/{page}/json")
    Call<BaseEntity<ArticleBean>> getArticleData(@Path("page") int page, @Query("cid") int id);
 Call<BaseEntity<ArticleBean>> call = apiService.getArticleData(0,60);

POST请求

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

表单提交

FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解

    /**
     * post请求
     * FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
     * 该接口是get请求,只是为了演示post请求的用法
     * 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<ResponseBody> getGameInfo(@Field("id") String id,
                               @Field("page") String page,
                               @Field("plat") String plat,
                               @Field("version") String version);

FieldMap

多个参数时可以使用,类型@QueryMap

    @FormUrlEncoded
    @POST("news/php/varcache_getnews.php")
    Call<ResponseBody> getGameInfo(@FieldMap Map<String, String> map);

body注解:上传json数据

方式1:使用RequestBody

    @POST("news/php/varcache_getnews.php")
    Call<ResponseBody> getNewsInfoByBody(@Body RequestBody Body);

        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) {

                    }
                });

方式2:

直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。

  /**
     * 直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
     * @param bean
     * @return
     */
    @POST("news/php/varcache_getnews.php")
    Call<ResponseBody> getNewsInfoByBody(@Body ParmasBean bean);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                .build();

        ApiService apiService = retrofit.create(ApiService.class);

        ParmasBean parmasBean=new ParmasBean();
        Call<ResponseBody> call = apiService.getNewsInfoByBody(parmasBean);

方式3:使用Map集合

上传json格式的数据,也可以使用Map集合,加上body注解。

    @POST("news/php/varcache_getnews.php")
    Call<ResponseBody> getNewsInfoByBody(@Body Map<String, Object> map);
        Map<String, Object> parmas = new HashMap<>();
        parmas.put("alipay_account", "xx");
        parmas.put("real_name", "xx");
        Call<ResponseBody> call = apiService.getNewsInfoByBody(parmas);

@Url:指定请求路径

若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。

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

@Headers注解

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

 /**
     * 使用@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,该注解会更新已有的请求头。

 /**
     * @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} 、任意类型;

 ///上传单张图片//

    /**
     * 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) {

                    }
                });
    }

和rxjava结合使用

 implementation "io.reactivex.rxjava2:rxjava:2.1.1"
 implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    /**
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/{page}/json")
    Observable<BaseEntity<ArticleBean>> getArticleData(@Path("page") int page, @Query("cid") int id);
public class MainActivity extends AppCompatActivity {

    public static final String BASE_URL = "https://www.wanandroid.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava
                .build();


        ApiService apiService = retrofit.create(ApiService.class);
        apiService
                .getArticleData(0, 60)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<BaseEntity<ArticleBean>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull BaseEntity<ArticleBean> articleBeanBaseEntity) {
                        Log.e("xyh", "onNext: " + articleBeanBaseEntity.getData().toString());
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });


    }
}

Retrofit请求直接返回string

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

string转换器:

implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
    /**
     * 不加转化器类型必须是ResponseBody
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/{page}/json")
    Call<ResponseBody> getArticleData1(@Path("page") int page, @Query("cid") int id);

    /**
     * gson转换器:GsonConverterFactory.create()
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/{page}/json")
    Call<BaseEntity<ArticleBean>> getArticleData2(@Path("page") int page, @Query("cid") int id);


    /**
     * 字符串转换器:ScalarsConverterFactory.create()
     * https://www.wanandroid.com/article/list/0/json?cid=60
     */
    @GET("article/list/{page}/json")
    Call<String> getArticleData3(@Path("page") int page, @Query("cid") int id);
public class MainActivity extends AppCompatActivity {

    public static final String BASE_URL = "https://www.wanandroid.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(ScalarsConverterFactory.create())  //字符串转换器,这个要写在gson转换器前面,不然不起作用
                .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava
                .build();


        ApiService apiService = retrofit.create(ApiService.class);
        apiService.getArticleData1(0, 60).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    Log.e("xyh", "onResponse1: " + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.e("xyh", "onFailure1: "+t.getMessage() );
            }
        });


        apiService.getArticleData2(0,60).enqueue(new Callback<BaseEntity<ArticleBean>>() {
            @Override
            public void onResponse(Call<BaseEntity<ArticleBean>> call, Response<BaseEntity<ArticleBean>> response) {
                Log.e("xyh", "onResponse2: " + response.body().getData().toString());
            }

            @Override
            public void onFailure(Call<BaseEntity<ArticleBean>> call, Throwable t) {
                Log.e("xyh", "onFailure2: "+t.getMessage() );
            }
        });


        apiService.getArticleData3(0, 60).enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                Log.e("xyh", "onResponse3: " + response.body());
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {
                Log.e("xyh", "onFailure3: "+t.getMessage() );
            }
        });

		}
    }

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

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(new ToStringConverterFactory())  //字符串转换器,这个要写在gson转换器前面,不然不起作用
                .addConverterFactory(GsonConverterFactory.create()) //gson转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //RxJava
                .build();
      /**
     * 定义一个转换器,把请求到的数据转换成字符串
     */
    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数据转换器(Converter)

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

转换器就是把服务器返回的json格式数据,多做了一步处理,转换成你希望的类型.

转换器可以被添加到支持其他类型。提供了方便适应流行的串行化库, Retrofit 提供了六兄弟模块如下:

Gson: com.squareup.retrofit:converter-gson
Jackson: com.squareup.retrofit:converter-jackson
Moshi: com.squareup.retrofit:converter-moshi
Protobuf: com.squareup.retrofit:converter-protobuf
Wire: com.squareup.retrofit:converter-wire
Simple XML: com.squareup.retrofit:converter-simplexml

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 PublicHeaderInterceptor implements Interceptor {

    private static final String TAG = "PublicHeaderInterceptor";

    private static final String TIMES_TAMP = "timestamp";
    private static final String TOUCH_ID = "touchid";
    private static final String DEVICE = "device";
    private static final String VERSION = "version";
    private static final String TOKEN = "token";
    private static final String SIGN = "sign";
    private static final String KEY = "key";
    private static final String KEY_VALUE = "base64:qC93ZPHeTNxh2SwB/DeOSb0zUwhHWHWHiU61ZDAPvdnOjkOYE=";

    private Gson gson;
    private String imeiMd5;
    private String androidId;

    public PublicHeaderInterceptor() {
        gson = createGsonObj();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        // 单位秒 10位
        long timeMillis = DateUtils.getSystemTimestamp();
        long time = timeMillis / 1000L;
        String device = "android";
        String touchId = UTDevice.getUtdid(Utils.getContext());
        String token = StringUtils.null2Length0(UserManager.getDefault().getLoginToken());
        HttpUrl url = request.url();
        // Log.e(TAG, "intercept: " + url);
        String sign = "";
        Request.Builder builder = request.newBuilder();

        // 获取参数
        Map<String, String> params = new HashMap<>(10);

        // 获取 url 参数
        List<String> urlKeys = new ArrayList<>(url.queryParameterNames());
        String urlKey = "";
        for (int i = 0; i < urlKeys.size(); i++) {
            urlKey = urlKeys.get(i);
            params.put(urlKey, StringUtils.null2Length0(url.queryParameter(urlKey)));
        }
        // 获取 Body 参数
        String bodyString = "{}";
        if (request.body() != null) {
            final Buffer buffer = new Buffer();
            request.body().writeTo(buffer);
            bodyString = buffer.readUtf8();
        }
        KLog.d(String.format("bodyString=%s", bodyString));
        if (isJsonObject(bodyString) || isJsonArray(bodyString)) {
            TreeMap<String, String> map = gson.fromJson(bodyString, new TypeToken<TreeMap<String, String>>() {
            }.getType());
            if (map != null) {
                for (String key : map.keySet()) {
                    params.put(key, map.get(key));
                }
            }
        }

        // 传入指定参数
        params.put(PublicHeaderInterceptor.TIMES_TAMP, String.valueOf(time));
        if (!TextUtils.isEmpty(touchId)) {
            params.put(PublicHeaderInterceptor.TOUCH_ID, touchId);
        }
        params.put(PublicHeaderInterceptor.DEVICE, device);
        if (!TextUtils.isEmpty(token)) {
            params.put(PublicHeaderInterceptor.TOKEN, token);
        }
        params.put(PublicHeaderInterceptor.KEY, KEY_VALUE);

        StringBuilder signStringBuilder = new StringBuilder();
        List<String> keys = new ArrayList<>(params.keySet());
        // 移除SIGN key
        keys.remove(SIGN);
        // 排序
        Collections.sort(keys);
        // 排序后 拼接
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            if (i != 0) {
                signStringBuilder.append("&");
            }
            String valueObj = params.get(key);
            String value = "";
            if (valueObj != null) {
                value = valueObj;
            }
            signStringBuilder.append(String.format("%s=%s", key, value));
        }
        String signBefore = signStringBuilder.toString().toLowerCase();
        Log.e(TAG, "signBefore: " + signBefore);
        // MD5加密
        sign = MD5Util.md5(signBefore.getBytes());
        // 在URL末尾追加签名与时间
        String newUrl = url.toString();
        if (!newUrl.contains("?")) {
            newUrl = String.format("%s?sign=%s&timestamp=%s", newUrl, sign, time);
        } else {
            newUrl = String.format("%s&sign=%s&timestamp=%s", newUrl, sign, time);
        }
        builder.url(newUrl);
        KLog.d("request", String.format("newUrl=%s \n md5_str=%s timeMillis=%s timeDiffForLocalAndService=%s",
                newUrl, signBefore, timeMillis, Constants.getTimeDiffForLocalAndService()));

        // --start 将验签参数加入header
        if (!TextUtils.isEmpty(token)) {
            builder.addHeader(TOKEN, token);
        }
        builder.addHeader(PublicHeaderInterceptor.TOUCH_ID, touchId);
        builder.addHeader(PublicHeaderInterceptor.DEVICE, device);
        builder.addHeader(PublicHeaderInterceptor.VERSION, BuildConfig.VERSION_NAME);
        // --end

        //        builder.addHeader("platform", "android");
        builder.addHeader("v_code", BuildConfig.VERSION_CODE + "");
        builder.addHeader("channel", ConfigManager.getDefault().getAppChannel());
        // samsung SM-G6200 8.1.0
        builder.addHeader("systemVersion", String.format("%s|%s|%s", Build.BRAND, Build.MODEL, Build.VERSION.RELEASE));

        if (TextUtils.isEmpty(imeiMd5)) {
            String imei = AppUtils.getImei(Utils.getContext());
            if (!TextUtils.isEmpty(imei)) {
                imeiMd5 = MD5Util.md5(imei.getBytes());
            }
        }
        if (!TextUtils.isEmpty(imeiMd5)) {
            builder.addHeader("imei", imeiMd5);
        }
        if (TextUtils.isEmpty(androidId)) {
            androidId = AppUtils.getAndroidId(Utils.getContext());
        }
        builder.addHeader("androidId", androidId);

        Request req = builder.build();
        Log.e(TAG, "url: " + req.url());
        //Log.e(TAG, "intercept3: " + req.toString());
        Headers headers = req.headers();
        Iterator<Pair<String, String>> iterator = headers.iterator();
        while (iterator.hasNext()) {
            Pair<String, String> pair = iterator.next();
            Log.e(TAG, "headers: " + pair.component1() + "==" + pair.component2());

        }

        //请求信息
        return chain.proceed(builder.build());
    }

    private boolean isJsonObject(String content) {
        return !StringUtils.isEmpty(content) && (content.trim().startsWith("{") && content.trim().endsWith("}"));
    }

    private boolean isJsonArray(String content) {
        return !StringUtils.isEmpty(content) && (content.trim().startsWith("[") && content.trim().endsWith("]"));
    }

    /**
     * Gson 自动将 int 转为 double 问题解决
     */
    private Gson createGsonObj() {
        return new GsonBuilder()
                .registerTypeAdapter(
                        new TypeToken<TreeMap<String, String>>() {
                        }.getType(),
                        new JsonDeserializer<TreeMap<String, String>>() {
                            @Override
                            public TreeMap<String, String> deserialize(
                                    JsonElement json, Type typeOfT,
                                    JsonDeserializationContext context) throws JsonParseException {

                                TreeMap<String, String> treeMap = new TreeMap<>();
                                JsonObject jsonObject = json.getAsJsonObject();
                                Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                                for (Map.Entry<String, JsonElement> entry : entrySet) {
                                    if (entry.getValue().isJsonArray()) {
                                        treeMap.put(entry.getKey(), Utils.getGson().toJson(entry.getValue()));
                                    } else {
                                        treeMap.put(entry.getKey(), entry.getValue().getAsString());
                                    }

                                }
                                return treeMap;
                            }
                        }).create();
    }
}

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值