Retrofit2个人使用总结

该文章讲述Retrofit中的一些基本使用总结,不涉及源码的介绍,是个人在使用中总结出来对Retrofit的用法。

我们知道Retrofit是当今安卓开发中热门的网络请求框架,内部封装了okhttp,并且能与RxJava结合一同使用。三者结合使网络请求变得更加简洁,轻松和强大。

首先导入Retrofit2


implementation'com.squareup.retrofit2:retrofit:2.3.0'

1. 基本用法

    interface Demo {
        @GET("feeds")
        Call<ResponseBody> getData();
    }

写一个接口和获取数据方法,其中@GET表示使用GET方法请求,括号中的feeds代表追加地址。Call<ResponseBody>为Retrofit默认的数据返回类型。

当然这里仅仅是写了一个接口,并没有真正做网络请求。上面说到feeds代表追加地址,那么它肯定还要有一个基本请求地址。

    //构建Retrofit
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/") //基础地址
        .build();

可以看到我们在构建Retrofit的过程中添加了基础地址(baseUrl),这时它的完整链接就是基础地址加上追加地址
这里为:https://api.github.com/feeds
文章到传递表单字段前都用 https://api.github.com/ 来作为基本的请求地址

一次完整的网络请求(记得添加网络权限)

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //构建Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/") //基础地址
                .build();

        Demo demo = retrofit.create(Demo.class);
        Call<ResponseBody> data = demo.getData(); //调用接口方法并返回
        //网络请求
        data.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
                try {
                    Log.i("TAG", "请求成功:" + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
                Log.i("TAG", "请求失败: " + t);
            }
        });
    }

    interface Demo {
        @GET("feeds")
        Call<ResponseBody> getData();
    }
}

2. 占位符:

/**
 * 使用占位符进行请求
 *
 * @param appendUrl 动态url
 * @return 一次请求的结果
 */
 @GET("{url}")
 Call<ResponseBody> getData2(@Path("url") String appendUrl);

可以看到我们之前的具体链接feeds替换成{url},并且用注解@Path("url")声明该替换的url是动态路径。
Call<ResponseBody> data = demo.getData2("feeds");
我们只需要在方法调用时输入动态的追加地址即可。

3. 查询参数

网络请求中常常需要用到一些查询的参数来确定具体数据,Retrofit为我们提供了@Query和@QueryMap注解。
@Query
本次请求地址:https://api.github.com/search/repositories?q=tetris
其中q=tetris就是查询参数

   @GET("search/repositories")
   Call<ResponseBody> getData3(@Query("q") String queryUrl);

方法调用
Call<ResponseBody> data = demo.getData3("tetris");

@QueryMap
多个查询参数时用到
本次请求地址:https://api.github.com/search/repositories?q=tetris&sort=stars

  //接口
  interface Demo {
        @GET("search/repositories")
        Call<ResponseBody> getData4(@QueryMap Map<String, String> map);
    }
 //使用
   Demo demo = retrofit.create(Demo.class);
   Map<String, String> map = new HashMap<>();
   map.put("q", "tetris");
   map.put("sort", "stars");
   Call<ResponseBody> data = demo.getData4(map);

4. FromUrlEncode传递表单字段

被该注解标记的方法表示请求体是Form表单,结合@Field@FieldMap可以通过@Post方法传递表单字段到服务器。这里我搭建一个最简单的测试后台,结合注解做一个简单的登录操作。

项目结构.png

在Eclipse创建Dynamic Web Project,结构如上图。
登录逻辑也很简单,判断密码是否正确,并返回给移动端。

/**
 * 登录服务(Login.java)
 */
@WebServlet("/login")
public class Login extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Login() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 设置编码格式,防止数据传到Android端乱码
        response.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        PrintWriter pw = response.getWriter();
        if ("123".equals(username) && "123".equals(password)) {
            System.out.println("登录成功");
            pw.print("登录成功!");
        } else {
            System.out.println("登录失败");
            pw.print("登录失败!");
        }
        pw.flush();
        pw.close();
    }
}

然后在Android端使用Retrofit注解做登录请求
首先有FormUrlEncoded注解的方法不能使用@Get请求,否则会抛出异常

 Caused by: java.lang.IllegalArgumentException: FormUrlEncoded can only be specified on HTTP methods with request body (e.g., @POST).

接口写法:

    interface Demo {
        /**
         * 传递表单字段需要被FormUrlEncoded注解
         * @param username 用户名,对应服务端参数
         * @param password 密码,对应服务端参数
         * @return 一次请求结果
         */
        @FormUrlEncoded
        @POST("TestDemo/login")
        Call<ResponseBody> doLogin(@Field("username") String username, @Field("password") String password);
    }

完整请求:

        //构建Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.1.122:8080/") //基础地址,tomcat地址和端口
                .build();
        Demo demo = retrofit.create(Demo.class);
        Call<ResponseBody> call = demo.doLogin("123", "123");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    Log.i("TAG", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

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

            }
        });
    }

Android端Log:

I/TAG: 登录成功!                                                               
 [ 12-27 04:41:51.171 13335:13372 D/         ]
  SurfaceInterface::setAsyncMode: set async mode 1

@FieldMap则通过Map来传递集合

//接口
@FormUrlEncoded
       @POST("TestDemo/login")
       Call<ResponseBody> doLogin2(@FieldMap Map<String,String> map);
//使用
Map<String,String> map=new HashMap<>();
      map.put("username","123");
      map.put("password","123");
      Call<ResponseBody> call = demo.doLogin2(map);

5. 文件上传

单文件上传@Multipart
结合@Part MultipartBody.Part使用上传文件

 interface Demo {
        @Multipart
        @POST("TestDemo/uploadFile")
        Call<ResponseBody> uploadFile(@Part MultipartBody.Part file);
    }

完整请求(记得添加读写权限)

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //构建Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.1.122:8080/") //基础地址,tomcat地址和端口
                .build();
        Demo demo = retrofit.create(Demo.class);
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/gxImg/test.jpg");
        //将File转换成二进制数据传到服务器的request
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        //添加到MultipartBody.Part中等待上传,参数一:服务器接收的key,参数二:文件名
        MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
        //上传文件
        Call<ResponseBody> call = demo.uploadFile(body);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    Log.i("TAG", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("TAG", "onFailure: " + t);
            }
        });
    }

服务端:

/**
 * 上传服务
 */
@WebServlet("/uploadFile")
public class UpLoadFile extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public UpLoadFile() {
        super();
        // TODO Auto-generated constructor stub
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        // 创建文件项目工厂对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 设置文件上传路径
        String upload = "d:";
        // 设置缓冲区大小为 5M
        factory.setSizeThreshold(1024 * 1024 * 5);
        // 用工厂实例化上传组件, 用来解析文件上传请求
        ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
        // 解析结果放在List中
        try {
            List<FileItem> list = servletFileUpload.parseRequest(request);
            for (FileItem item : list) {
                String name = item.getFieldName();
                InputStream is = item.getInputStream();
                System.out.println("当前文件名字 " + name);
                if (name.contains("file")) {
                    try {
                        inputStream2File(is, upload + "\\" + item.getName());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    String key = item.getName();
                    String value = item.getString();
                    System.out.println(key + "---" + value);
                }
            }
            out.write("上传成功");
        } catch (FileUploadException e) {
            e.printStackTrace();
            out.write("上传失败");
        }
        out.flush();
        out.close();
    }

    // 流转化成字符串
    public static String inputStream2String(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int i = -1;
        while ((i = is.read()) != -1) {
            baos.write(i);
        }
        return baos.toString();
    }

    // 流转化成文件
    public static void inputStream2File(InputStream is, String savePath) throws Exception {
        System.out.println("文件路径:" + savePath);
        File file = new File(savePath);
        InputStream inputSteam = is;
        BufferedInputStream fis = new BufferedInputStream(inputSteam);
        FileOutputStream fos = new FileOutputStream(file);
        int f;
        while ((f = fis.read()) != -1) {
            fos.write(f);
        }
        fos.flush();
        fos.close();
        fis.close();
        inputSteam.close();
    }
}

多文件上传@PartMap

//依旧使用上面服务端
@Multipart
    @POST("TestDemo/uploadFile")
    Call<ResponseBody> uploadMultiFiles(@PartMap Map<String, RequestBody> map);
   File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/gxImg/test.jpg");
   Map<String, RequestBody> map = new HashMap<>();
   RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
   //file是传到服务器的键,filename是传到服务器后使用的文件名
   map.put("file\";filename=\"icon.png" , requestFile);//文件1
   map.put("file1\";filename=\"icon1.png" , requestFile);//文件2,这里为了方便使用同一个文件
   Call<ResponseBody> call = demo.uploadMultiFiles(map);

6. 统一请求头@Headers

@Headers("Accept: application/json, text/plain, */*")
    @FormUrlEncoded
    @POST("TestDemo/login")
    Call<ResponseBody> doLogin(@FieldMap Map<String, String> paramsMap);

7. 向服务端传Json数据@Body

   @POST("TestDemo/uploadJson")
   Call<ResponseBody> uploadJson(@Body UserInfo info);

说到向服务器传递Json数据那就要提到Retrofit的转换器

转换器种类.png

转换器有什么用?我们想一下,我们现在要提交Json数据给服务器,但是@Body中传递的是一个UserInfo的Bean类,直接发送肯定不行,这时就要通过转换器,将Bean类转换成Json数据给服务器接收。这里既然传递Json数据,我们就是用Gson转换器。

    //添加依赖
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

在构建Retrofit的时候添加转换器

   //构建Retrofit
   Retrofit retrofit = new Retrofit.Builder()
          .addConverterFactory(GsonConverterFactory.create())//添加转换器
          .baseUrl("http://192.168.1.122:8080/")
          .build();

发送数据到服务器

 Demo demo = retrofit.create(Demo.class);
 Call<ResponseBody> call = demo.uploadJson(new UserInfo("Fan", 10));

服务器代码:

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        InputStream is = request.getInputStream();
        byte[] buf = new byte[1024];
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int len;
        if ((len = is.read(buf)) != -1) {
            bos.write(buf, 0, len);
        }
        System.out.println("收到数据:" + bos.toString());
        bos.close();
    }

既然发送时可以转换成Json数据,那么从服务器获取数据是否可以解析Json传到Bean类呢?
答案是肯定的。

interface Demo {
        @POST("TestDemo/uploadJson")
        Call<UserInfo> uploadJson();
    }

可以看到我们将默认的ResponseBody换成想要转换的bean类即可。
当然如果传来的Json数据与bean类属性不符合则会出错。

      Demo demo = retrofit.create(Demo.class);
        Call<UserInfo> call = demo.uploadJson();
        call.enqueue(new Callback<UserInfo>() {
            @Override
            public void onResponse(Call<UserInfo> call, Response<UserInfo> response) {
                //解析成UserInfo的数据使用
                Log.i("TAG", response.body().getName());
                Log.i("TAG", String.valueOf(response.body().getAge()));
            }

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

            }
        });

这样,在成功的回调onResponse中就可以使用UserInfo的属性了。

8. 结合RxJava

为什么Retrofit会成为热门网络请求框架,除了请求简单和强大外,可以与目前热门框架RxJava结合使用也是原因之一。
结合RxJava首先要添加以下依赖

  //Retrofit中RxJava的适配器
  implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
  //RxJava
  implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
  //RxAndroid,有安卓特有的一些方法
  implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

这里改造一下上面用到的Login.java,模拟登录成功后返回Json数据

    if ("123".equals(username) && "123".equals(password)) {
            System.out.println("登录成功");
            //仅仅返回一个success属性
            pw.write("{\"success\":\"登录成功\"}");
        } else {
            System.out.println("登录失败");
            pw.write("登录失败!");
        }
   //接口编写
   interface Demo {
         /**
         * 结合RxJava和Gson适配器使用
         * SuccessResponse里只有一个属性
         * 就是服务器返回的success
         * @param username 用户名
         * @param password 密码
         * @return 请求结果
         */
        @FormUrlEncoded
        @POST("TestDemo/login")
        Observable<SuccessResponse> login(@Field("username") String username, @Field("password") String password);
 }

添加RxJava适配器

  Retrofit retrofit = new Retrofit.Builder()
         .addConverterFactory(GsonConverterFactory.create())//添加转换器
         .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //添加RxJava适配器
         .baseUrl("http://192.168.1.122:8080/")
         .build();

使用

    Demo demo = retrofit.create(Demo.class);
        Observable<SuccessResponse> observable = demo.login("123","123");
        observable.subscribeOn(Schedulers.io()) //网络请求时切换io线程
                .observeOn(AndroidSchedulers.mainThread()) //请求成功后切换主线程显示
                .subscribe(new Consumer<SuccessResponse>() {
                    @Override
                    public void accept(SuccessResponse successResponse) throws Exception {
                        //主线程,显示数据
                        Log.i("TAG", "返回数据: " + successResponse.getSuccess());
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.i("TAG", "登录失败:"+throwable);
                    }
                });

9. 添加okhttpClient

为Retrofit添加okhttpClient,可以使用OkHttp的一些属性(例如,设置连接超时)。
因为一开始说过,Retrofit内部封装了OkHttp,所以能直接使用,这里我们再为Okhttp添加拦截器(可以拦截请求中的请求头和请求体等数据),需要添加依赖。

    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
       //创建拦截器
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Log.i("TAG", "请求中发送的请求头请求体等数据拦截在这里: " + message);
            }
        });
        //设置拦截等级
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //创建OkHttpClient并设置属性
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//设置超时
                .readTimeout(10, TimeUnit.SECONDS)
                .addInterceptor(interceptor)//添加拦截器
                .build();
        //构建Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())//添加转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //添加RxJava适配器
                .baseUrl("http://192.168.1.122:8080/")
                .client(client) //添加OkHttp
                .build();

以上就是个人总结的一些Retrofit的基本用法,当然还有没有提到的,例如@Url,@Streaming,@Header等一些注解要自行查找文档。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,376评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李头阅读 15,022评论 4 39
  • 前段时间看了RxJava,发现跟他一起用的Retrofit,今天就把认识的他们俩个来总结梳理一下 一、什么是RxJ...
    毹毹阅读 663评论 0 5
  • 冷 慕雨 一. 当冬天悄然而至,这座城市变得一片雪白,连蛐蛐也开始沉默,一切都归于平静,这是死一般的寂静,然而一声...
    故城里的故人阅读 474评论 0 0