美文网首页个人总结
Retrofit2个人使用总结

Retrofit2个人使用总结

作者: GrapeX | 来源:发表于2017-12-27 16:37 被阅读0次

    该文章讲述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等一些注解要自行查找文档。

    相关文章

      网友评论

        本文标题:Retrofit2个人使用总结

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