retrofit2_retrofit2.0使用详解 - CSDN
精华内容
参与话题
  • Retrofit2 的简单使用

    千次阅读 2017-10-09 23:27:46
    Retrofit 是 Square 公司开源的一个高质量高...Retrofit 以其解耦彻底、扩展灵活、使用简单等特性,在 Android 领域声名远播。Retrofit 已经出来很久了,现在最新版本是 2.3.0 ,如果还没使用过它,就真的是 low 爆了。

    iMac

    Retrofit 是 Square 公司开源的一个高质量高效率的http库,开发者是被称为 Android 之神 的 Jake Wharton。

    Retrofit 以其解耦彻底、扩展灵活、使用简单等特性,在 Android 领域声名远播。

    Retrofit 已经出来很久了,现在最新版本是 2.3.0 ,如果还没使用过它,就真的是 low 爆了。

    这里简单讲解 Retrofit 的使用以及理清楚 Retrofit 的上传。



    Retrofit 入门

    Retrofit 其实相当简单,简单到源码只有37个文件,其中22个文件是注解还都和HTTP有关,真正暴露给用户的类并不多。Retrofit 必须配合 Okhttp 来使用,并且真正的网络请求是由 Okhttp 完成的,Retrofit 是利用动态代理对数据做了封装后,给到 OKhttp ,最后由Okhttp 完成网络请求后,将数据交给 Retrofit 。


    1.创建Retrofit 实例

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://gank.io/api/")
                    .build();

    创建Retrofit实例时需要通过Retrofit.Builder ,并调用baseUrl方法设置URL,当然,baseUrl 并不是必须的,也可以在后面的接口上设置。

    注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出IllegalArgumentException: baseUrl must end in / 的异常


    2.定义 API 接口

    这里以 「干货集中营」的 API 为例,定义了一个 GET 请求的接口。

    public interface API {
        /**
         * 妹纸列表
         */
        @GET("data/福利/{num}/{page}") //里面填写全路径URl,如果设置有baseURL,也可以是相对路径
        Call<ResponseBody> getGirlPic(@Path("num") int num, @Path("page") int page);
    
        @GET() //也可以不填,然后在参数里面指定全路径的URL
        Call<ResponseBody> getGirlPic(@Url String url);
    }

    这个interface 内的方法我们是无法直接调用的,我们需要通过Retrofit 创建一个API的代理对象。

      API apiServer = retrofit.create(API.class);

    然后,通过这个代理对象来调用方法。


    3.调用接口

            API apiServer = retrofit.create(API.class);
            Call<ResponseBody> girlPic = apiServer.getGirlPic(8, 1);
            //调用后,回调方法执行在主线程
            girlPic.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    
                }
    
                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
    
                }
            });

    这样就完成了retrofit 简单的调用了。



    Retrofit 注解

    想要愉快的使用 Retrofit ,就必须要了解它的22个注解的用途。

    为更好理解,可以将这22个注解分为三类。


    HTTP 请求类型注解

    http请求类型注解

    上面 8 个注解都是在方法上使用,除了「 HTTP」 这个注解,其它都对应了HTTP 协议的 7 种请求类型,这个很好理解,只有 HTTP 这个注解有点特殊。

    HTTP 注解可以代替以上方法中的任意一个注解,有 3 个属性:methodpathhasBody

    下面是用HTTP注解实现:

    public interface API {
        /**
         * method 表示请求的方法,区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "GET", path = "data/福利/{num}/{page}", hasBody = false)
        Call<ResponseBody> getGirlPic(@Path("num") int num, @Path("page") int page);
    }


    标注类注解

    标注注解

    这 3 个注解也是作用在方法上的,前两个表单请求的注解「FormUrlEncoded」、「Multipart」用于上传,后面会详细了解。

    最后一个注解「Streaming」用于文件的下载:

        @GET
        @Streaming
        Call<ResponseBody> download(@Url String url);

    「Streaming」注解表示以流的形式返回响应体,避免了因数据过大而造成的内存溢出。



    参数类注解

    参数注解

    上面的注解都有各自的作用,多多练习才能知道每个注解的具体用法。

    GET 请求非常简单,网上有很多示例,例如:

    @GET("group/users")
    Call<ResponseBody> groupList(@Query("sort") String sort);
    //Query注解用于添加参数,可添加多个
    
    //如果参数个数不固定,可以使用QueryMap注解用于动态添加参数
    @GET("group/users")
    Call<ResponseBody> groupList(@QueryMap Map<String, String> options);


    复杂的是 POST 请求,许多文章都讲得不够详细完整,这里整理记录一下。

    关于 POST 请求上传,可以简单分为 参数上传、文件上传、混合上传



    参数上传

    参数上传非常常见,在用户登录时,一般都会用到参数上传,将用户名密码上传到服务器 做校验。

    这种表单提交需要用到 「FormUrlEncoded」注解。

    参数上传可以分为 固定参数上传、动态参数上传、自定义上传


    固定参数上传

    固定参数是指:接口上传的参数个数固定不变。

    @FormUrlEncoded
    @POST("/upload")
    Call<ResponseBody> uploadParams(
      @Field("username")String username,
      @Field("password")String password);


    动态参数上传

    动态参数上传是指:接口上传的参数动态改变。

    这种情况可以使用 「FieldMap」来添加参数。

    @FormUrlEncoded
    @POST("/upload")
    Call<ResponseBody> uploadParams(@FieldMap Map<String,String> map);


    自定义上传

    自定义上传是指:直接封装请求体。

    @POST("/upload")
    Call<ResponseBody> uploadParams(@Body RequestBody body);
    //组装请求体
    FormBody body=new FormBody.Builder()
                    .add("username","admin")
                    .add("token","psd=dmzkkshf")
                    .build();



    文件上传

    文件上传也有许多应用场景,比如图片上传。

    单文件上传

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadOneFile(@Part MultipartBody.Part body);
    
    //第一个参数表示文件的 MIME类型,第二个参数是文件
    RequestBody fileRQ = RequestBody.create(MediaType.parse("image/*"), file);
    //第一个参数表示接口定义的参数名称,第二个参数表示文件的名称,第三个参数是 RequestBody
    MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);
    
    Call<ResponseBody> uploadCall = downloadService.uploadOneFile(part);

    MediaType 指的是要传递的数据的MIME类型。

    MediaType对象包含了三种信息:type 、subtype以及charset,一般将这些信息传入parse()方法中,这样就可以解析出MediaType对象,

    比如 “text/x-markdown; charset=utf-8” ,type值是text,表示是文本这一大类;/后面的x-markdown是subtype,表示是文本这一大类下的markdown这一小类; charset=utf-8 则表示采用UTF-8编码。

    如果不知道某种类型数据的MIME类型,可以参见链接Media TypeMIME 参考手册,较详细的列出了所有数据的MIME类型。

    常见的 MIME 类型如下:

    • json : application/json
    • xml : application/xml
    • png : image/png
    • jpg : image/jpeg
    • gif : imge/gif


    多文件上传

    @PartMap
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFiles(@PartMap Map<String, RequestBody> map);
    
    
    RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
    RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
                    + file.separator + "original.png"));
    Map<String, RequestBody> map = new HashMap<>();
    //这里的key必须这么写,否则服务端无法识别
    map.put("file\"; filename=\""+ file.getName(), fileRQ);
    map.put("file\"; filename=\""+ "2.png", fileTwo);
    
    Call<ResponseBody> uploadCall = downloadService.uploadFiles(map);
    @Part
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFiles(@Part List<MultipartBody.Part> parts);
    
    
    RequestBody fileRQ = RequestBody.create(MediaType.parse("image/*"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);
    
    RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
    RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
                    + file.separator + "original.png"));
    MultipartBody.Part two=MultipartBody.Part.createFormData("one","one.png",fileTwo);
    List<MultipartBody.Part> parts=new ArrayList<>();
    parts.add(part);
    parts.add(two);
    
    Call<ResponseBody> uploadCall = downloadService.uploadFiles(parts);



    文件和参数混合上传

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFile(
      @Part("body") RequestBody body, 
      @Part MultipartBody.Part file);
    
    
    MultipartBody.Part part = MultipartBody.Part.createFormData("picture", 
                                                                file.getName(), 
                                                                fileRQ);
    RequestBody fb =RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
    Call<ResponseBody> uploadCall = downloadService.uploadFile(fb,part);



    通用上传方式

     @POST("upload")
     Call<ResponseBody> uploadFile(@Body RequestBody body);
    
    
    String path = Environment.getExternalStorageDirectory() + File.separator + "1.png";
    File file = new File(path);
    RequestBody fileRQ = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData("picture", 
                                                                file.getName(), 
                                                                fileRQ);
    
    RequestBody body=new MultipartBody.Builder()
                    .addFormDataPart("userName","lange")
                    .addFormDataPart("token","dxjdkdjkj9203kdckje0")
                    .addFormDataPart("header",file.getName(),fileRQ)
                    .build();
    Call<ResponseBody> uploadCall = downloadService.uploadFile(body);


    注意:

    @Part 在标注 RequestBody 时,必须携带参数

    在标注MultipartBody.Part 时,绝对不能携带参数,如果弄错了,就会直接报异常。

    //错误示例代码
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadOneFile(@Part RequestBody file);//报错
    //@Part 标注 RequestBody 时,需要携带参数 如: @Part("file")
    
    //错误示例代码
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadOneFile(@Part("file") MultipartBody.Part file);//报错
    //@Part 标注 MultipartBody.Part 时,不能携带参数



    参考

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

    这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)

    展开全文
  • Retrofit2详解

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

    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();
        }
    }
    
    
    展开全文
  • 带进度显示的 单个或多个文件retrofit+rxjava2 文件上传

    首先展示效果如下:
    这里写图片描述


    本demo使用的是rxjava2, rxjava1也是可以使用的;主要核心代码如下:

    1.创建个listener

    /**
     * Created by JokAr on 2017/3/6.
     */
    
    public interface UploadListener {
        void onRequestProgress(long bytesWritten, long contentLength);
    }

    2.获取上传进度

    /**
     * Created by JokAr on 2017/3/6.
     */
    
    public class CountingRequestBody extends RequestBody {
        private RequestBody mRequestBody;
        private UploadListener mUploadListener;
        private CountingSink mCountingSink;
    
        public CountingRequestBody(RequestBody requestBody, UploadListener uploadListener) {
            mRequestBody = requestBody;
            mUploadListener = uploadListener;
        }
    
        @Override
        public MediaType contentType() {
            return mRequestBody.contentType();
        }
    
        @Override
        public long contentLength() throws IOException {
            try {
                return mRequestBody.contentLength();
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }
    
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            BufferedSink bufferedSink;
    
            mCountingSink = new CountingSink(sink);
            bufferedSink = Okio.buffer(mCountingSink);
    
            mRequestBody.writeTo(bufferedSink);
            bufferedSink.flush();
        }
    
        class CountingSink extends ForwardingSink {
    
            private long bytesWritten = 0;
    
            public CountingSink(Sink delegate) {
                super(delegate);
            }
    
            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                bytesWritten += byteCount;
                mUploadListener.onRequestProgress(bytesWritten, contentLength());
            }
        }
    }
    

    3.添加拦截器

    /**
     * Created by JokAr on 2017/3/6.
     */
    
    public class UpLoadProgressInterceptor implements Interceptor {
        private UploadListener mUploadListener;
    
        public UpLoadProgressInterceptor(UploadListener uploadListener) {
            mUploadListener = uploadListener;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if(null == request.body()){
                return chain.proceed(request);
            }
    
            Request build = request.newBuilder()
                    .method(request.method(),
                            new CountingRequestBody(request.body(),
                            mUploadListener))
                    .build();
            return chain.proceed(build);
        }
    }
    

    4.使用方法

     public static Retrofit getRetrofit(UploadListener listener) {
            UpLoadProgressInterceptor interceptor = new UpLoadProgressInterceptor(listener);
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(interceptor)
                    .retryOnConnectionFailure(true)
                    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .build();
    
            return new Retrofit.Builder()
                    .baseUrl(BASE_URL + "/")
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
        }
    • 单个文件上传:
     @Override
        public void uploadFile(File file,
                               UploadListener listener,
                               @NonNull LifecycleTransformer transformer,
                               @NonNull final UploadFileCallBack callBack) {
            if (file != null && file.exists()) {
                RequestBody requestFile =
                        RequestBody.create(MediaType.parse("application/otcet-stream"), file);
    
                MultipartBody.Part body =
                        MultipartBody.Part.createFormData("file", file.getName(), requestFile);
                UploadNetWorkConfig.getRetrofit(listener)
                        .create(UploadFileService.class)
                        .uploadHead(body)
                        .compose(transformer)
                        .subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .observeOn(Schedulers.newThread())
                        .map(new HttpResultFunc())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new ResourceObserver<UploadEntities>() {
                            @Override
                            protected void onStart() {
                                super.onStart();
                                callBack.onStart();
                            }
    
                            @Override
                            public void onNext(UploadEntities data) {
                                callBack.result(data);
                            }
    
                            @Override
                            public void onError(Throwable e) {
                                callBack.requestError(e);
                            }
    
                            @Override
                            public void onComplete() {
                                callBack.complete();
                            }
                        });
            }
        }
    • 多个文件上传:
        /**
         * 上传多个文件
         *
         * @param pathList
         * @param listener
         * @param transformer
         * @param callBack
         */
        @Override
        public void uploadMultiFile(String[] pathList,
                                    UploadListener listener,
                                    @NonNull LifecycleTransformer transformer,
                                    @NonNull final UploadMultiFileCallBack callBack) {
            if (pathList != null && pathList.length > 0) {
                ArrayList<Observable> observables = new ArrayList<>();
    
                for (String path : pathList) {
                    File file = new File(path);
                    if (file.exists()) {
                        RequestBody requestFile =
                                RequestBody.create(MediaType.parse("application/otcet-stream"), file);
    
                        MultipartBody.Part body =
                                MultipartBody.Part.createFormData("file", file.getName(), requestFile);
                        Observable<String> observable = UploadNetWorkConfig.getRetrofit(listener)
                                .create(UploadFileService.class)
                                .uploadHead(body)
                                .compose(transformer)
                                .subscribeOn(Schedulers.io())
                                .unsubscribeOn(Schedulers.io())
                                .observeOn(Schedulers.newThread())
                                .map(new HttpResultFunc());
                        observables.add(observable);
                    }
                    file = null;
                }
                Observable[] observables1 = new Observable[observables.size()];
                observables1 = (Observable[]) observables.toArray();
                observables = null;
    
                Observable.zipArray(new Function<Object[], ArrayList<UploadEntities>>() {
                    /**
                     * Apply some calculation to the input value and return some other value.
                     *
                     * @param objects the input value
                     * @return the output value
                     * @throws Exception on error
                     */
                    @Override
                    public ArrayList<UploadEntities> apply(@NonNull Object[] objects) throws Exception {
                        ArrayList<UploadEntities> arrayList = new ArrayList<>();
                        for (Object object : objects) {
                            arrayList.add((UploadEntities) object);
                        }
                        return arrayList;
                    }
    
                }, true, 1, observables1)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new ResourceObserver<ArrayList<UploadEntities>>() {
                            @Override
                            protected void onStart() {
                                super.onStart();
                                callBack.onStart();
                            }
    
    
                            @Override
                            public void onNext(ArrayList<UploadEntities> data) {
                                callBack.result(data);
                            }
    
                            @Override
                            public void onError(Throwable e) {
                                callBack.requestError(e);
                            }
    
                            @Override
                            public void onComplete() {
                                callBack.complete();
                            }
                        });
    
            }
        }

    主要代码就是这些了;完整demo请看这里:
    https://github.com/MichaelJokAr/RxUpload
    如果你觉得对你有用,欢迎start

    展开全文
  • (三)Rxjava2+Retrofit之文件上传与下载

    千次阅读 热门讨论 2020-01-02 17:21:44
    上篇文章主要对Retrofit做了封装,使之使用起来更加方便。本篇文章将根据上篇文章的封装架构实现文件上传服务器的功能。文章将从以下几点实现对Retrofit上传文件的讲解 一 使用Retrofit上传文件时遇到的坑 二 实现...

    结合 第一篇文章对Retrofit的封装,本篇文章将讲述如何实现文件上传与下载。本篇文章可分为文件上传与文件下载两部分内容。

    一、使用RxJava+Retrofit上传文件

    使用Retrofit上传文件到服务器可分为单文件上传与多文件上传,实现都比较简单。不妨用两个例子来分别看下单文件和多文件上传。

    1 、实现单文件上传
    单文件上传最常见的就是更换头像,我们就以此为例来看。
    首先定义上传头像的接口方法,代码如下:

        @Multipart
        @POST("user/uploadAvatar.do")
        Observable<UploadAvatarResponse> uploadAvatar(@Part("userId") RequestBody userId,@Part MultipartBody.Part image);
    
    

    注意上面上面方法加了@Multipart的注解。对于上传文件必须要加这个注解,不然会报异常!另外方法中有两个参数,即UserId和要上传的头像文件!返回值是我我们自定义的Observable(详见上一片文章),接下来在我们注册的页面调用这个方法,如下:

     File file = new File(picPath);
     //  图片参数
    RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
            MultipartBody.Part imageBody = MultipartBody.Part.createFormData("uploadFile", file.getName(), requestFile);
     //  手机号参数
    RequestBody userIdBody = RequestBody.create(MediaType.parse("multipart/form-data"), phone);
    
    IdeaApi.getApiService()
                    .uploadAvatar(userIdBody,imageBody )
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new DefaultObserver<UploadAvatarResponse>(this, true) {
                        @Override
                        public void onSuccess(UploadAvatarResponseresponse) {
                            EventBus.getDefault().post(new RegisterSuccess("register success"));
                            showToast("头像上传成功");
                            finish();
                        }
                    });
    

    显然,上面的方法有个弊端。当接口中需要的参数较少时使用上面的方法无可厚非。如果接口中需要的参数非常多,那么上面的方法使用起来就麻烦了。因此对于参数较多的单文件上传可以使将所有参数都放入一个List集合中。同样以上传头像为例。

    先定义上传头像接口的方法:

        @Multipart
        @POST("user/register.do")
        Observable<UploadAvatarResponse> register(@Part List<MultipartBody.Part> partList);
    

    可以看到现在方法中间参数变为一个List《ltipartBody.Part》的集合。这样所有的参数我们只需要放到这个集合里边即可!接下来看注册页面如何调用这个方法:

    File file = new File(picPath);
            RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
            MultipartBody.Builder builder = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("userId", userId)
                    .addFormDataPart("uploadFile", file.getName(), imageBody);
            List<MultipartBody.Part> parts = builder.build().parts();
            
            IdeaApi.getApiService()
                    .register(parts)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new DefaultObserver<UploadAvatarResponse>(this, true) {
                        @Override
                        public void onSuccess(UploadAvatarResponse response) {
                            EventBus.getDefault().post(new RegisterSuccess("register success"));
                            showToast("注册成功,请登陆");
                            finish();
                        }
    

    这样是不是比第一种方法清爽了很多呢!

    2.实现多文件上传。
    对于多图上传其实跟单文件上传没有多大区别,只不过多了些参数而已。先看定义多文件上传接口:

    	@POST("upload/uploadPic")
        Observable<UpLoadMultiFileResponse> uploadFiles(@Part("filename") String description,
                                       @Part("pic\"; filename=\"image1.png") RequestBody imgs1,
                                       @Part("pic\"; filename=\"image2.png") RequestBody imgs2,);
    

    调用接口上传图片:

    	   File file = new File(picPath);
           RequestBody requestFile1 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
           MultipartBody.Part body = MultipartBody.Part
           .createFormData("uploadFile", file.getName(), requestFile);
            
            RequestBody requestFile2 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
           MultipartBody.Part body = MultipartBody.Part
           .createFormData("uploadFile", file.getName(), requestFile);
            
            IdeaApi.getApiService()
                    .uploadFiles("pictures",requestFile1,requestFile2 )
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new DefaultObserver<UpLoadMultiFileResponse>(this, true) {
                        @Override
                        public void onSuccess(UpLoadMultiFileResponse response) {
                            EventBus.getDefault().post(new RegisterSuccess("register success"));
                            showToast("注册成功,请登陆");
                            finish();
                        }
    

    同样,当上传图片较多时可以采用map集合来存放多个图片RequestBody参数。接口代码如下:

      @POST()
      Observable<BasicResponse> uploadFiles(
            @Part("filename") String description,
            @PartMap() Map<String, RequestBody> maps);
    

    然后调用接口实现多文件上传

    	File file = new File(picPath);
        RequestBody requestFile1 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part body = MultipartBody.Part
    		    .createFormData("uploadFile", file.getName(), requestFile);
           
        RequestBody requestFile2 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    	MultipartBody.Part body = MultipartBody.Part
    		    .createFormData("uploadFile", file.getName(), requestFile);
    	Map<String,RequestBody> map=new HashMap<>();
    	map.put("picture1",requestFile1 );
    	map.put("picture2",requestFile2 );
    
    	IdeaApi.getApiService()
                    .uploadFiles("pictures",map)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new DefaultObserver<BasicResponse<RegisterBean>>(this, true) {
                        @Override
                        public void onSuccess(BasicResponse<RegisterBean> response) {
                            EventBus.getDefault().post(new RegisterSuccess("register success"));
                            showToast("注册成功,请登陆");
                            finish();
                        }
    

    关于文件上传就这么多东西,实现起来也相当简单。那么接下来第二部分使用Retrofit实现文件下载才是比较重要的内容。

    二、使用RxJava+Retrofit下载

    使用Retrofit下载文件其实非常简单,但是对于用户来说往往都希望可以看到下载进度,然而遗憾的是Retrofit并没有为我们提供文件下载进度的接口。因此,关于下载进度就需要我们自己来通过拦截器实现。本文不讲解Retrofit下载的基本使用,而是针对Retrofit加入下载进度实现一个简单的封装。老规矩,先来看一下封装后的使用。代码如下:

    DownloadUtils downloadUtils = new DownloadUtils();
    //	开始下载
    public void download(View view) {
            btn.setClickable(false);
            downloadUtils.download(Constants.DOWNLOAD_URL, new DownloadListener() {
                @Override
                public void onProgress(int progress) {
                    LogUtils.e("--------下载进度:" + progress);
                    Log.e("onProgress", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
                    progressBar.setProgress(progress);
                    mTvPercent.setText(String.valueOf(progress) + "%");
                }
    
                @Override
                public void onSuccess(ResponseBody responseBody) {  //  运行在子线程
                    saveFile(responseBody);
                    Log.e("onSuccess", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
                }
    
                @Override
                public void onFail(String message) {
                    btn.setClickable(true);
                    ToastUtils.show("文件下载失败,失败原因:" + message);
                    Log.e("onFail", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
                }
    
                @Override
                public void onComplete() {  //  运行在主线程中
                    ToastUtils.show("文件下载成功");
                    btn.setClickable(true);
                }
            });
        }
    //	取消下载
    public void cancelDownload(View view) {
            if (downloadUtils != null) {
                downloadUtils.cancelDownload();
            }
        }
    

    上面代码中使用DownloadUtils类来实现下载文件与取消下载。并且在下载时回调出了下载进度。那么关于以上代码是如何实现的呢?接下来看详细解析。
    **1.获取下载进度。**新建一个ProgressHelper类,在该类中为Retrofit添加拦截器,核心代码如下:

    public static OkHttpClient.Builder addProgress(OkHttpClient.Builder builder){
    
            if (builder == null){
                builder = new OkHttpClient.Builder();
            }
    
            final ProgressListener progressListener = new ProgressListener() {
                //该方法在子线程中运行
                @Override
                public void onProgress(long progress, long total, boolean done) {
                    Log.d("progress:",String.format("%d%% done\n",(100 * progress) / total));
                    if (mProgressHandler == null){
                        return;
                    }
    
                    progressBean.setBytesRead(progress);
                    progressBean.setContentLength(total);
                    progressBean.setDone(done);
                    mProgressHandler.sendMessage(progressBean);
    
                }
            };
    
            //添加拦截器,自定义ResponseBody,添加下载进度
            builder.networkInterceptors().add(new Interceptor() {
                @Override
                public okhttp3.Response intercept(Chain chain) throws IOException {
                    okhttp3.Response originalResponse = chain.proceed(chain.request());
                    return originalResponse.newBuilder().body(
                            new ProgressResponseBody(originalResponse.body(), progressListener))
                            .build();
    
                }
            });
    
            return builder;
        }
    

    上述代码中在添加拦截器时通过自定义的ResponseBody将下载的进度信息回调了出来,并通过Handler不断的将下载进度发送出去。ProgressResponseBody中代码如下:

    public class ProgressResponseBody extends ResponseBody {
        private final ResponseBody responseBody;
        private final ProgressListener progressListener;
        private BufferedSource bufferedSource;
    
        public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
            this.responseBody = responseBody;
            this.progressListener = progressListener;
        }
    
        @Override
        public MediaType contentType() {
            return responseBody.contentType();
        }
    
        @Override
        public long contentLength() {
            return responseBody.contentLength();
        }
    
        @Override
        public BufferedSource source() {
            if (bufferedSource == null) {
                bufferedSource = Okio.buffer(source(responseBody.source()));
            }
            return bufferedSource;
        }
    
        private Source source(Source source) {
            return new ForwardingSource(source) {
                long totalBytesRead = 0L;
    
                @Override
                public long read(Buffer sink, long byteCount) throws IOException {
                    long bytesRead = super.read(sink, byteCount);
                    totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                    progressListener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                    return bytesRead;
                }
            };
        }
    }
    

    2.为Retrofit添加进度监听。 上面我们已经实现了下载进度的获取,那么接下来就需要为Retrofit设置下载进度监听了。来看DownloadUtils中的代码:

     public void download(@NonNull String url, DownloadListener downloadListener) {
            mDownloadListener = downloadListener;
            getApiService().download(url)
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .doOnNext(getConsumer())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getObserver());
        }
    
        private CommonService getApiService() {
            OkHttpClient.Builder httpClientBuilder = RetrofitService.getOkHttpClientBuilder();
            ProgressHelper.addProgress(httpClientBuilder);
            CommonService commonService = RetrofitService.getRetrofitBuilder(Constants.API_SERVER_URL)
                    .client(httpClientBuilder.build())
                    .build()
                    .create(CommonService.class);
            ProgressHelper.setProgressHandler(new DownloadProgressHandler() {
                @Override
                protected void onProgress(long bytesRead, long contentLength, boolean done) {
                    mDownloadListener.onProgress((int) ((100 * bytesRead) / contentLength));
                }
            });
            return commonService;
        }
    

    上面代码中将httpClientBuilder添加到ProgressHelper中,然后调用ProgressHelper中的setProgressHandler方法回调除了下载进度信息。此时通过DownloadListener类再次将下载信息回调了出来。同时DownloadListener中还有onSuccess、onFail、onComplete方法,分别在以下地方调用:

    private Observer<ResponseBody> getObserver() {
            return new Observer<ResponseBody>() {
    
                @Override
                public void onSubscribe(Disposable d) {
                    mDisposables.add(d);
                }
    
                @Override
                public void onNext(ResponseBody responseBody) {
                    mDownloadListener.onComplete();
                }
    
                @Override
                public void onError(Throwable e) {
                    mDownloadListener.onFail(e.getMessage());
                }
    
                @Override
                public void onComplete() {
                    mDownloadListener.onComplete();
                }
            };
        }
    

    3.取消下载。取消下载其实就是通过CompositeDisposable中的clear方法终止了RxJava的生命周期。

    至此关于Retrofit的上传与下载就分析完了,文末可以参看源码。

    (一)Rxjava2+Retrofit完美封装
    (二)Rxjava2+Retrofit实现Token自动刷新
    (三)Rxjava2+Retrofit实现文件上传与下载

    源码传送门

    好库推荐

    给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。通过BannerViewPager可以实现腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式。

    欢迎大家到github关注BannerViewPager

    展开全文
  • Interface 层 @Multipart @POST("/commitment/published") Observable<YiDongResponseDto<Object>> uploadPromise(@Part List<MultipartBody.Part> parts, ...
  • 使用Retrofit上传文件时遇到的坑 实现单文件上传 实现多文件上传 一、使用Retrofit上传文件时遇到的坑。项目中注册接口中有上传头像的功能,本以为上传头像是一个很简单的事情,可万万没想到使用Retrofit上传头像...
  • 从Eclipse转战AndroidStudio已经有两个月了。先夸夸Google亲儿子的强大吧,各种方便就不一一道来了。主要是现在的Android阵营已经不想前两年了。各种开源框架开源库。也正是如此,AndroidStudio导入开源的项目非常...
  • Retrofit2 详解和使用(一)

    万次阅读 多人点赞 2020-07-31 15:02:58
    retrofit是现在比较流行的网络请求框架,可以理解为okhttp的加强版,底层封装了Okhttp。准确来说,Retrofit是一个RESTful的http网络请求框架的封装。因为网络请求工作本质上是由okhttp来完成,而Retrofit负责网络...
  • 何为Retrofit? 借用官网的原话, Type-safe HTTP client for Android and Java by Square, Inc. 适用于Android 和 Java 的类型安全的HTTP客户端,由Square提供的。(敲黑板) 由此我们可以得知,Retrofit是...
  • Retrofit2深入浅出

    2020-04-21 21:40:13
    Retrofit2简单的说就是一个网络请求的适配器,它将一个基本的Java接口通过动态代理的...本文基于Retrofit2解析。(retrofit,改型) Retrofit2基本使用 先定义一个PersonalProtocol的java接口 public interface ...
  • 2.Retrofit如何使用? 2.1创建HTTP请求的API接口 2.2请求执行 3.注解详情 3.1请求方法注解 3.2标记请求数据类型 3.3注解参数 4.GSON和Converter 5.RxJava和CallAdapter 6.自定义Converter 7.自定义...
  • Retrofit2 完全解析 探索与okhttp之间的关系

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

    万次阅读 2020-01-07 16:27:44
    深度详解Retrofit2使用(一)基础入门这篇文章主要描述了Retrofit的注解,没有涉及具体Retrofit的使用。今天就以Android为平台,看看Retrofit是如何使用。 一. 准备 1.1 导入Retrofit库。 上篇文章,我们提到过...
  • 前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求库中,Retrofit是当下... 如果对Retrofit v2.0的源码感兴趣,可看文章:Android:手把手带你深入剖析 Retrofit 2.0 源码 目录![目录](http://upload-
  • Retrofit2与RxJava用法大全

    万次阅读 多人点赞 2016-07-29 16:19:35
    Retrofit2是square公司出品的一个网络请求库,网上有很多相关的介绍。我很久以前都想去研究了,但一直都有各种事情耽搁,现在就让我们一起去捋一捋,这篇主要讲解Retrofit2与RxJava的基本用法。 get请求 post请求 ...
  • Retrofit2源码解读

    万次阅读 2016-06-18 15:20:16
    Retrofit2的用法在Retrofit2.0使用详解这篇文章中已经详细介绍过了。那么在这就来看一下Retrofit2它是如何实现的。Retrofit2中它的内部网络请求是依赖于OKHttp,所以Retrofit2可以看做是对OKHttp的一次封装,那么...
  • Retrofit2+Rxjava2之优雅的封装

    万次阅读 多人点赞 2017-04-09 14:11:33
    Retrofit2+Rxjava2之优雅的封装 这篇文章主要是教大家如何封装Retrofit2+Rxjava2,所以实现原理不做过多的解释,如有不付,额(你咬我呀!), 还有就是看这篇文章的同时,你一定要对 Retrofit2 和 Rxjava2 有所...
  • Retrofit2使用(非常简洁易懂)

    万次阅读 2017-10-26 00:07:18
    1、什么是Retrofit框架?它是Square公司开发的现在非常流行的网络框架,所以我们在导入它的包的时候都可以看到这个公司的名字,目前的版本是2。特点:性能好,处理快,使用简单,Retrofit 是安卓上最流行的...
  • rxjava2+retrofit2

    千次阅读 2020-06-15 17:26:06
    Retrofit2是square公司出品的一个网络请求库,网上有很多相关的介绍。我很久以前都想去研究了,但一直都有各种事情耽搁,现在就让我们一起去捋一捋,这篇主要讲解Retrofit2与RxJava的基本用法。 get请求 ...
  • Retrofit2实现图片文字上传

    万次阅读 热门讨论 2019-12-02 21:53:49
    目录 前言 一、效果展示 二、基本配置 三、代码实战 ...3.1、创建RetrofitManager和APIService ...距离上一篇文章到现在已经有将近半年的时间了,因为换了一座城市,到现在才算是刚刚熟悉起来吧,所以这段时间一直没...
1 2 3 4 5 ... 20
收藏数 40,030
精华内容 16,012
关键字:

retrofit2