阳光沙滩
让学习编程变得简单
Android网络编程Retrofit
发表于 2019-12-11    阅读次数 2293

Android网络编程Retrofit

本文为Android开发网络编程系列课程内容,更多内容请查看课程

Android开发之网络和多媒体

我们先是学习了http协议,然后使用java的api去请求数据,接着我们觉得麻烦,又学了一个okhttp,只管请求参数跟请求结果,不用处理线程,也不用处理请求头等复杂的内容。

前辈们感觉还不够简单,于是又整了一个Retrofit。学过后台的同学就知道了,以前配置很复杂。有了注解驱动开发以后,很多配置通过注解的方式就可以完成了。简单了很多。

而Retrofit是一款基于注解把http api转成接口,使用起来更加简单。

接下来,我们就看看怎么使用吧!

权限

权限问题自行处理,些系列里前面的课程里介绍高版本如何配置http的安全访问配置以及网络权限。

服务器接口

SOBAndroidMiniWeb

或者使用网站的商城API

商城API文档

文档和代码

文档地址:

retrofit文档

github地址

retrofit API文档

添加依赖

版本

版本在release里看就好了

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

目前最新的版本是:parent-2.6.1

所以我这里的依赖就用

implementation 'com.squareup.retrofit2:retrofit:parent-2.6.1 

另外,我们要把结果转成对象,这个时候需要加一些转换器

    implementation 'com.squareup.retrofit2:converter-gson:2.6.2'

当然啦,有多种。在前面的官方文档中有提到

图片描述

可以转xml的数据,可以转Json的数据。我们的数据格式是json,可以用Gson,也可以用Jackson

版本地址可以到中央仓库去搜索一下

converter-gson

入门代码

  1. 先定义一个接口

/**
 * 阳光沙滩AndroidWeb后台的接口
 */
public interface SobMiniWebInterface {

    @GET("/get/text")
    Call<JsonResult> getJson();

}

  1. 定义结果bean类

bean类代码比较多,我就不帖出来了。同学们可以使用gsonFormat自动生成,而结果的json数据样例可以在前面的接口文档里看到。

  1. 创建retrofit
 public void getJson(View view) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http:10.0.2.2:9102")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        SobMiniWebInterface sobMiniWebInterface = retrofit.create(SobMiniWebInterface.class);
        Call task = sobMiniWebInterface.getJson();
        task.enqueue(new Callback() {
            @Override
            public void onResponse(Call call,Response response) {
                Log.d(TAG,"response -- > " + response.body());
            }

            @Override
            public void onFailure(Call call,Throwable t) {
                Log.d(TAG,"exception -- > " + t.toString());
            }
        });
    }
  1. 执行结果:

图片描述

okay啦,到这里就能体验到跟使用okhttp请求一样的功能。更加简单了。定义一个接口完事。而数据回来,就是一个bean类实体对象了。需要使用的地方,可以直接使用。

初步封装

如果我们每次使用,都要去创建Retrofit,那不是很麻烦吗?

所以我们可以把这些相关的配置,整合起来。把Retrofit的创建和配置定义成一个单例,这样子,我们要去发起请求的时候,直接使用单例获取就完事了。不用再重复去编写baseURL呀,转换器呀,连接时的相关属性,等等...

于是我们创建一个单例

package com.sunofbeaches.retrofitdemo;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitCreator {

    public static final String BASE_URL = "https://10.0.2.2:9102";
    public static final int CONNECT_TIME_OUT = 10000;//毫秒
    private Retrofit mRetrofit;


    private RetrofitCreator() {
        createRetrofit();
    }

    private void createRetrofit() {
        //设置一下okHttp的参数
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS)
                .build();
        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)//设置BaseUrl
                .client(okHttpClient)//设置请求的client
                .addConverterFactory(GsonConverterFactory.create())//设置转换器
                .build();
    }

    private static RetrofitCreator sRetrofitCreator = null;

    public static RetrofitCreator getInstance() {
        if(sRetrofitCreator == null) {
            synchronized(RetrofitCreator.class) {
                if(sRetrofitCreator == null) {
                    sRetrofitCreator = new RetrofitCreator();
                }
            }
        }
        return sRetrofitCreator;
    }


    public Retrofit getRetrofit() {
        return mRetrofit;
    }


}

那使用变成怎么样了呢?

 public void getJson(View view) {
        SobMiniWebInterface sobMiniWebInterface = RetrofitCreator.getInstance().getRetrofit().create(SobMiniWebInterface.class);
        Call task = sobMiniWebInterface.getJson();
        task.enqueue(new Callback() {
            @Override
            public void onResponse(Call call,Response response) {
                Log.d(TAG,"response -- > " + response.body());
            }

            @Override
            public void onFailure(Call call,Throwable t) {
                Log.d(TAG,"exception -- > " + t.toString());
            }
        });
    }

如果多个地方使用呢?重复的代码是不是变少啦!

接口定义

前面我们定义了一个接口


/**
 * 阳光沙滩AndroidWeb后台的接口
 */
public interface SobMiniWebInterface {

    @GET("/get/text")
    Call<JsonResult> getJson();

}

这里我们可以看到有一个注解@Get

@Documented @Target(value=METHOD) @Retention(value=RUNTIME) public @interface GET Make a GET request.

从官方的API文档里我们可以看到这个注解是作用于方法,是一个运行时起作用的注解。创建一个get request 之前我们创建Request是new出来了。Retrofit则直接使用注解

知道这个,那就好办了!

那我post请求呢?是不也可以来一个@Post呢?

是的!Restrofit,rest就是Restful的意思,按这个套路,肯定有get、post、delete、put。当然还有其他的。

不知道restful风格api的同学可以去了解一下

那咱们列一个思维导图吧

图片描述

get请求

/**
 * 阳光沙滩AndroidWeb后台的接口
 */
public interface SobMiniWebInterface {

    @GET("/get/text")
    Call<JsonResult> getJson();

}

带参数的咋整呢?

比如说这个接口:

/get/param

参数:

  • keyword 关键字
  • page 页码
  • order 0 顺序 1 逆序

接口定义:两种写法都可以

    @GET("/get/param")
    Call<GetParamsResult> getWithParams(@Query("keyword") String keyword,@Query("page") int page,@Query("order") String order);

    @GET("/get/param")
    Call<GetParamsResult> getWithParams(@QueryMap Map<String,Object> params);

调用:

        Call<GetParamsResult> call = mSobMiniWebInterface.getWithParams("关键字测试",1,"1");
        call.enqueue(new Callback<GetParamsResult>() {
            @Override
            public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) {
                GetParamsResult result = response.body();
                Log.d(TAG,"result -- > " + result);
                if(result.getCode() == 10000) {
                    Log.d(TAG,"请求成功");
                    Log.d(TAG,"结果信息 --> " + result.getMessage());
                }
            }

            @Override
            public void onFailure(Call<GetParamsResult> call,Throwable t) {
                Log.d(TAG,"请求失败...onFailure");
            }
        });

        Map<String,Object> params = new HashMap<>();
        params.put("keyword","map 参数测试关键字");
        params.put("page",1);
        params.put("order","0");
        Call<GetParamsResult> mapTask = mSobMiniWebInterface.getWithParams(params);
        mapTask.enqueue(new Callback<GetParamsResult>() {
            @Override
            public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) {
                Log.d(TAG,"onResponse ==> " + response.body());
            }

            @Override
            public void onFailure(Call<GetParamsResult> call,Throwable t) {
                Log.d(TAG,"onFailure == > " + t.toString());
            }
        });

结果:

D/MainActivity: getWithParams..
D/MainActivity: result -- > GetParamsResult{success=true, code=10000, message='get带参数请求成功.', data=DataBean{page='1', keyword='关键字测试', order='逆序
D/MainActivity: 请求成功
D/MainActivity: 结果信息 --> get带参数请求成功.

到这里我们就学习了

@Qeury注解,@QueryMap注解

post请求,body携带字符串内容(json)

这个参数是body的内容

图片描述

接口文档看前面呀。

定义接口

    @POST("/post/comment")
    Call<PostWithBodyResult> postWithBodyContent(@Body CommentItem commentItem);

调用代码

public void postWithBody(View view) {
        CommentItem commentItem = new CommentItem();
        commentItem.setArticleId("1251412341");
        commentItem.setCommentContent("这是我提交的测试评论内容...");
        Call<PostWithBodyResult> task = mSobMiniWebInterface.postWithBodyContent(commentItem);
        task.enqueue(new Callback<PostWithBodyResult>() {
            @Override
            public void onResponse(Call<PostWithBodyResult> call,Response<PostWithBodyResult> response) {
                Log.d(TAG,"response -- > " + response.body());
            }

            @Override
            public void onFailure(Call<PostWithBodyResult> call,Throwable t) {
                Log.d(TAG,"post with body...onFailure");
            }
        });
    }

结果:

D/MainActivity: response -- > PostWithBodyResult{success=true, code=10000, message='评论成功:这是我提交的测试评论内容...', data=null}

到这里我们就学习了@Body注解

post请求Url携带参数

接口:

图片描述

接口定义:


    @POST("/post/string")
    Call<PostWithUrlParamsResult> postWithUrlParams(@Query("string") String text);

调用


    public void postWithUrlParams(View view) {
        Call<PostWithUrlParamsResult> task = mSobMiniWebInterface.postWithUrlParams("这是我提交的参数内容");
        task.enqueue(new Callback<PostWithUrlParamsResult>() {
            @Override
            public void onResponse(Call<PostWithUrlParamsResult> call,Response<PostWithUrlParamsResult> response) {
                Log.d(TAG,"onResponse result -- > " + response.body());
            }

            @Override
            public void onFailure(Call<PostWithUrlParamsResult> call,Throwable t) {
                Log.d(TAG,"onFailure -- > " + t.toString());
            }
        });
    }

单文件上传

图片描述

权限自行处理

@Part注解,要跟@Multipart注解一起使用。

而参数类型有三种:

  • MultipartBody.Part
  • RequestBody
  • Other object

具体可以去看看文档

/**
   * The name of the part. Required for all parameter types except
   * {@link okhttp3.MultipartBody.Part}.
   */

除了MultipartBody.Part类型以外,其他的参数类型都要加上注解参数。

接口:


    @Multipart
    @POST("/file/upload")
    Call<FileUploadResult> postFile(@Part MultipartBody.Part file);

上传文件调用

public void postFile(View view) {
        File file = new File("/storage/emulated/0/Download/1.jpg");
        MediaType mediaType = MediaType.parse("image/jpg");
        RequestBody fileBody = RequestBody.create(mediaType,file);
        MultipartBody.Part part = MultipartBody.Part.createFormData("file",file.getName(),fileBody);
        Call<FileUploadResult> task = mSobMiniWebInterface.postFile(part);
        task.enqueue(new Callback<FileUploadResult>() {
            @Override
            public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
                Log.d(TAG,"onResponse -- > " + response.body());
            }

            @Override
            public void onFailure(Call<FileUploadResult> call,Throwable t) {
                Log.d(TAG,"onFailure -- > " + t.toString());
            }
        });
    }

上传结果:

图片描述

图片描述

到这里我们就学习了@Part和@Multipart注解的使用了

上传文件及附带信息

提这个方式是为了跟大家了解@PartMap的使用

接口定义:

    @Multipart
    @POST("/multiFiles/upload")
    Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);

调用:

    public void postMultiFile(View view) {
        Map<String,Object> params = new HashMap<>();
        MultipartBody.Part filePart = getPart("file","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png");
        params.put("isFree","false");
        params.put("description","我是文件的描述内容...");
        Call<FileUploadResult> task = mSobMiniWebInterface.postFileWithParams(params,filePart);
        task.enqueue(new Callback<FileUploadResult>() {
            @Override
            public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
                Log.d(TAG,"onResponse -- > " + response.body());
            }

            @Override
            public void onFailure(Call<FileUploadResult> call,Throwable t) {
                Log.d(TAG,"onFailure -- >" + t.toString());
            }
        });
    }

结果:

onResponse -- > FileUploadResult{success=true, code=10000, message='上传成功.文件路径为:E:\codes\Idear\SobNetworkCourseServer\target\classes\sobUpload\rBsADV3nxtKACoSfAAAPx8jyjF8169.png', data=your descriptions is --> "我是文件的描述内容..." isFree == > "false"}

多文件上传

后台接口:

图片描述

定义接口:

    @Multipart
    @POST("/files/upload")
    Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files);

调用代码:

   public void postFiles(View view) {
        List<MultipartBody.Part> files = new ArrayList<>();
        files.add(getPart("files","/storage/emulated/0/Download/1.jpg"));
        files.add(getPart("files","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png"));
        files.add(getPart("files","/storage/emulated/0/Download/rBsADV2rEz-AIzSoAABi-6nfiqs456.png"));

        Call<FileUploadResult> task = mSobMiniWebInterface.postFiles(files);
        task.enqueue(new Callback<FileUploadResult>() {
            @Override
            public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) {
                Log.d(TAG,"多文件上传结果" + response.body());
            }

            @Override
            public void onFailure(Call<FileUploadResult> call,Throwable t) {
                Log.d(TAG,"onFailure -- > 多文件上传失败 ---> " + t.toString());
            }
        });
    }

辅助方法

    private MultipartBody.Part getPart(String key,String filePath) {
        File file = new File(filePath);
        MediaType mediaType = MediaType.parse("image/jpg");
        RequestBody fileBody = RequestBody.create(mediaType,file);
        return MultipartBody.Part.createFormData(key,file.getName(),fileBody);
    }

多文件上传结果

FileUploadResult{success=true, code=10000, message='上传成功3个文件,路径:C:/Users/TrillGates/Desktop/SOBAndroidMiniWeb/sobUpload', data=null}

图片描述

文件下载

接口

图片描述

定义接口:

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

代码调用

ublic void downFile(View view) {
        Call<ResponseBody> task = mSobMiniWebInterface.downFile("/download/10");
        task.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
                Headers headers = response.headers();
                for(int i = 0; i < headers.size(); i++) {
                    Log.d(TAG,headers.name(i) + " == " + headers.value(i));
                }
                writeFile2Sd(response,headers);
            }

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

同学们会发现,我们收取的内容,需要进行iO操作,但是主线程不可以做文件读取呀。

所以又整了一个子线程。

 private void writeFile2Sd(final Response<ResponseBody> response,final Headers headers) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String disposition = headers.get("Content-disposition");
                if(disposition != null) {
                    int fileNameIndex = disposition.indexOf("filename=");
                    Log.d(TAG,"fileNameIndex -- > " + fileNameIndex);
                    String fileName = disposition.substring(fileNameIndex + "filename=".length());
                    Log.d(TAG,"fileName -- > " + fileName);
                    File picFilePath = MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
                    Log.d(TAG,"picFilePath --> " + picFilePath);
                    File file = new File(picFilePath + File.separator + fileName);
                    Log.d(TAG,"file -- > " + file);
                    FileOutputStream fos = null;
                    try {
                        if(!file.getParentFile().exists()) {
                            file.getParentFile().mkdirs();
                        }
                        if(!file.exists()) {
                            file.createNewFile();
                        }
                        fos = new FileOutputStream(file);
                        InputStream inputStream = response.body().byteStream();
                        byte[] buf = new byte[1024];
                        int len;
                        while((len = inputStream.read(buf,0,buf.length)) != -1) {
                            fos.write(buf,0,len);
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                    } finally {
                        if(fos != null) {
                            try {
                                fos.close();
                            } catch(IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }).start();
    }

这样子特别不方便,对吧!也不优雅,所以后面我们就要引入RxJava去解决这个问题了。

RxJava可以很方便地切换线程。

下载结果

提交表单

方法:post 接口:/login

定义接口

    @FormUrlEncoded
    @POST("/login")
    Call<ResponseBody> doLogin(@Field("userName") String userName,@Field("password") String password);

调用代码

public void login(View view) {
        Call<ResponseBody> call = mSobMiniWebInterface.doLogin("root","admin");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
                try {
                    Log.d(TAG,"onResponse -- >  " + response.body().string());
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }

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

运行结果:

{"success":true,"code":10000,"message":"这是你提交上来的数据:root - admin","data":"8920d231-20e4-46c6-9b41-9812730c4cce"}

到这里的话,我们就把@Failed搞定了,那@FailedMap呢?

相信聪明的你早就想到了,套路跟前面一样呀!

再来一个,定义接口

    @FormUrlEncoded
    @POST("/login")
    Call<ResponseBody> login(@FieldMap Map<String,Object> params);

调用代码


    public void login(View view) {
        // Call<ResponseBody> call = mSobMiniWebInterface.doLogin("root","admin");
        Map<String,Object> params = new HashMap<>();
        params.put("password","234123lkjsfa");
        params.put("userName","root");
        Call<ResponseBody> call = mSobMiniWebInterface.login(params);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {
                try {
                    Log.d(TAG,"onResponse -- >  " + response.body().string());
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }

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

运行结果

{"success":true,"code":10000,"message":"这是你提交上来的数据:root - 234123lkjsfa","data":"5af51e33-8c9c-4898-8c0e-bf44e0d2fc4f"}

Okay啦,到这里@FiledMap也搞定了

关于参数的接口

图片描述

大家再review一下吧。

header注解

到这里我们基本上把相关注解的使用学习了一遍。

图片描述

请求头注解我们还没使用上

怎么使用呢?

看码:

    @Multipart
    @POST("/file/upload")
    Call<FileUploadResult> postFile(@Part MultipartBody.Part file,@Header("token") String token);

    @Multipart
    @POST("/files/upload")
    Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files,@HeaderMap Map<String,String> headers);

    @Headers({"token:231231","version:1.0","client:android"})
    @Multipart
    @POST("/file/params/upload")
    Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);

其实我们很少这么用的,假如说,我们是为了用户验证,携带token的话,在每个方法都添加,那不是很麻烦吗?

而Okhttp是支持拦截器的,我们直接在每次请求的时候,在头部加上token就不可以了吗?

private void createRetrofit() {
        //设置一下okHttp的参数
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS)
                .addInterceptor(mHeaderInterceptor)
                .build();
        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)//设置BaseUrl
                .client(okHttpClient)//设置请求的client
                .addConverterFactory(GsonConverterFactory.create())//设置转换器
                .build();
    }

    private Interceptor mHeaderInterceptor = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request().newBuilder()
                    .addHeader("token","204391jawslejqowejqwi")
                    .addHeader("version","1.2.0")
                    .addHeader("client","android铂金版")
                    .build();
            return chain.proceed(request);
        }
    };

PS:注解里的可以覆盖掉拦截器里的内容.