利用Retrofit进行各种网络请求

利用Retrofit进行各种网络请求

目录

  • 利用Retrofit进行各种网络请求
    • 目录
    • 前言
    • 参考
    • Retrofit的基本使用
      • 创建基本HttpClient
      • 封装接口
      • 请求示例
    • 不同的网络请求
      • 请求地址中包含变量
      • 请求地址中包含查询变量
      • 上传Json上传实体
      • Form提交提交参数
      • 上传单个文件
      • 多表单提交参数和文件混合
    • Http请求Header的设置
      • 固定Header设置
      • Header中带有变量
      • 使用OkHttp interceptor添加Header
    • 文件下载

前言

关于Retrofit的优点,原理以及基本使用网上已经有很多相关的资料,但是自己实际使用的时候发现,比如多表单提交数据,比如上传动态数目图片等一些应用场景,官方文档以及网上并没有给出很好的解释和例子,在自己摸索一番后,特地记录下来并分享。
本文所用的一切代码都是继续Retrofit2.0

参考

Retrofit官网介绍了类型的网络请求的封装
Retrofit使用手册这里介绍了各种情况下使用Retrofit

Retrofit的基本使用

创建基本HttpClient

代码如下:
    /**
     * 请求超时时长
     */
    private static final int API_BASE_URL = "http://www.hwits.top/";
    /**
     * 请求超时时长
     */
    private static final int DEFAULT_TIMEOUT = 5;
    /**
     * OkHttpClient用来添加固定的请求HEAD
     */
    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder().connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

    /**
     * 添加BaseUrl以及对Gson和Rxjava的依赖,每次创建
     */
    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create());
    /**
     * 不包含请求头的普通操作
     *
     * @param serviceClass
     * @param 
     * @return
     */
    public  S createService(Class serviceClass) {
        //创建Retrofit客户端
        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }

简单的解释一下就是httpClient是一个OkHttpClient.Builder,设置了默认的请求时长。
builder是一个Retrofit.Builder,设置了请求的基地址以及使用Gson进行Json解析,使用Rxjava作为请求返回回调。
createService函数是用来创建请求接口服务的,用来具体实现请求

封装接口

以一个最简单的Get请求为例
    /**
     * 查询法律条文接口(异步)
     */
    public interface TrafficLawService {
        @GET("dictitem/getItemListByKind.json?kind=TrafficLaw")
        Observable>> getTrafficLaw();
    }
    /**
     * 查询法律条文接口(Sync)
     */
    public interface TrafficLawService {
        @GET("dictitem/getItemListByKind.json?kind=TrafficLaw")
        HttpReply> getTrafficLaw();
    }

简单解释一下就是,异步请求回返回给你一个回调这里使用Rxjava作为回调结果所以返回的是Observable,不设置回调的话返回为Call,Observable后面跟上的泛型就是你请求的返回结果通过序列化生成的实体,当然你也可以直接使用Retrofit提供的ResponseBody作为返回结果。
下面就是个阻塞的同步网络请求了,直接返回需要的结果

请求示例

 Observable>> call =
        client.createService(TrafficLawService).getTrafficLaw();

 HttpReply> call =
        client.createService(TrafficLawService).getTrafficLaw();

上面是异步调用返回的结果,后面是要经过Rxjava中的Subscriber进行订阅处理,这里不做详细解释
下面是直接返回结果,可以用来直接使用。

不同的网络请求

这里不同情况的封装列举例子进行讲解

请求地址中包含变量

比如请求BaseUrl+ "m/uservehicle/preferenced/1815141515"
这里最后的一个地址参数是可变的,封装入下
    public interface SetUserVehiclePerferencedService {
        @GET("m/uservehicle/preferenced/{vehicleId}")
        Observable> setVehiclePerferenced(@Path("vehicleId")    String vehicleId);
    }

将变量利用{}封装,使用@path注解参数

请求地址中包含查询变量

还有一种比较常见的请求地址BaseUrl + “common/content/getarticlebyid?articleID =xxxxx”
articleId其实是查询参数,封装如下:

    public interface GetNewsInfoByIdService {
        @GET("common/content/getarticlebyid")
        Observable> getNewsInfo(@Query("articleID") String articleId);
    }
@GET("group/{id}/users")
Observable<List> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

很简单,使用@Query作为注解,@QueryMap传入多个参数

上传Json/上传实体

如果请求中需要我们上传实体反序列化的Json字符串,上传对象的,
封装如下:

    public interface UpdateUserInfoService {
        @POST("m/appuser/updat")
        Observable> updateUserInfo(@Body UserInfo userInfo);
    }

同样很简单,使用@Body注解参数即可。

Form提交,提交参数

当你需要进行表单提交提交参数的时候
封装如下:

@FormUrlEncoded
@POST("user/edit")
Call updateUser(@Field("first_name") String first, @Field("last_name") String last);

使用@Field注解添加表单提交参数,@FieldMap添加多个参数
Tips:使用@Filed时,需要添加@FormUrlEncoded注解来进行编码

上传单个文件

    /**
     * 更换用户图像
     */
    public interface UploadUserAvatarService {
        @Multipart
        @POST("m/appuser/upload/{userId}")
        Observable> upload(@Path("userId") String id, @Part MultipartBody.Part avatar);
    }

这里上传文件的话,必须将上传的文件构建成MultipartBody.Part的实体(因为Retrofit2.0实际上默认使用OkHttp作为HttpClient,所以MultipartBody.Part 实际上来自OkHttpClent),然后利用@Part来注解MultipartBody.Part ,下面向大家展示如何生成MultipartBody.Part

    /**
     * 上传用户头像
     *
     * @param subscriber
     * @param accessToken
     * @param avatar
     * @param userId
     */
    public void uploadAvatar(Subscriber> subscriber, String accessToken, File avatar, String userId) {
        MultipartBody.Part avatarP = MultipartBody.Part.createFormData("file", "avatar.jpg", RequestBody.create(MediaType.parse("image/jpeg"), avatar));
        createFileService(TrafficHttpContrains.UploadUserAvatarService.class, accessToken).upload(userId, avatarP)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }

如上所示,RequestBody.create创建File的RequestBody(来自okHttp),第一个参数用来设置文件类型,第二个参数为上传文件
利用MultipartBody.Part.createFormData函数再次封装RequestBody作为Retrofit请求参数,第一个参数”file“表示提交给后台的参数,相当与前文中@Field(”file“)就行修饰;
第二个参数就是你上传文件自定义的文件名了。

综上,文件,文件名,文件类型,对应参数,这么就直接构成了单个文件上传。

多表单提交(参数和文件混合)

使用MutilForm,多表单提交时,当我想要提交多个文件的时候,同时还要上传多个参数的时候,也就是多表单提交既包括关键字,又包括文件的时候,怎么办?
这里其实是困扰了我许久的地方,解救方案就是将所有参数封装成RequestBody,接口封装如下:

    /**
     * 上传证据
     */
    public interface UploadProofsService {
        @Multipart
        @POST("m/accident/upload")
        Observable> uploadProofs(@Part List fileMap, @PartMap HashMap map);
    }

以@Part作为注解所有的文件都封装在一个MultipartBody.Part的List中,以@PartMap作为注解所有的其他参数封装到关键字–>RequestBody的实体中。(这里其实都源于okHttp的使用)
如何将参数进行封装,实现如下:

    /**
     * 证据上传
     */
    public void upLoadProofs(Subscriber> subscriber, String accessToken, String accidentId, HashMap photoMap, List proofTemplateList, File recorderFile, String accidentType, String timestamp, String vehicleCount, String position, String roadName, String regionCode, String regionName, String longitude, String latitude, String weather, String accidentSource) {
        //添加FileMap
        List.Part> list = new ArrayList<>();
        //添加图片证据
        for (Map.Entry entry : photoMap.entrySet()) {
            MultipartBody.Part proof = MultipartBody.Part.createFormData("file", proofTemplateList.get(entry.getKey()).getName() + ".jpg", RequestBody.create(MediaType.parse("image/jpeg"), new File(entry.getValue())));
            list.add(proof);
        }
        //添加录音证据
        if (null != recorderFile && recorderFile.exists()) {
            MultipartBody.Part record = MultipartBody.Part.createFormData("file", AccidentProof.ProofTypeEn[AccidentProof.ProofTypeEn.length - 1] + ".amr", RequestBody.create(MediaType.parse("application/octet-stream"), recorderFile));
            list.add(record);
        }

        //添加其他的参数Body
        RequestBody accidentTypeRb = RequestBody.create(MediaType.parse("text/plain"), accidentType);
        RequestBody timeStampRb = RequestBody.create(MediaType.parse("text/plain"), timestamp);
        RequestBody vehicleCountRb = RequestBody.create(MediaType.parse("text/plain"), vehicleCount);
        RequestBody positionRb = RequestBody.create(MediaType.parse("text/plain"), position);
        RequestBody roadNameRb = RequestBody.create(MediaType.parse("text/plain"), roadName);
        RequestBody regionCodeRb = RequestBody.create(MediaType.parse("text/plain"), regionCode);
        RequestBody regionNameRb = RequestBody.create(MediaType.parse("text/plain"), regionName);
        RequestBody longitudeRb = RequestBody.create(MediaType.parse("text/plain"), longitude);
        RequestBody latitudeRb = RequestBody.create(MediaType.parse("text/plain"), latitude);
        RequestBody weatherRb = RequestBody.create(MediaType.parse("text/plain"), weather);
        RequestBody accidentSourceRb = RequestBody.create(MediaType.parse("text/plain"), accidentSource);
        RequestBody accidentIdRb = RequestBody.create(MediaType.parse("text/plain"), accidentId);

        //添加FileMap
        HashMap map = new HashMap<>();
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_ACCIDENT_TYPE, accidentTypeRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_TIME_STAMP, timeStampRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_COUNT, vehicleCountRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_POSITION, positionRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_ROAD_NAME, roadNameRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_REGION_CODE, regionCodeRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_REGION_NAME, regionNameRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_LONGITUDE, longitudeRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_LATITUDE, latitudeRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_WEATHER, weatherRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_VEHICLE_ACCIDENT_SOURCE, accidentSourceRb);
        map.put(HttpRequstConstant.UPLOAD_PROOF_PARA_ID, accidentIdRb);

        //默认给请求头添加了contentType json
        createFileService(TrafficHttpContrains.UploadProofsService.class, accessToken).uploadProofs(list, map)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }

如上,将图片文件以及录音文件,封装成MultipartBody.Part,封装方式和上传单个文件一样,然后添加到List当中,值得注意的地方是图片的contentType应该为”image/jpeg”,而录音以及其他文件使用流传输,可以将contentType设置为”application/octet-stream”。
对于其他参数的设置就是封装成RequestBody,很简单不用多说。

值得一提的是使用多表单上传文件的时候,需要把请求时候的请求头中”Content-Type”设置为
“multipart/form-data”,这个在下一节中会讲到。

Http请求Header的设置

这里的大部分例子来源于Retrofit官网。

固定Header设置

使用@Headers注解,如下:

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call> widgetList();
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call getUser(@Path("username") String username);

Header中带有变量

使用@Header注解作为参数,如下:

@GET("user")
Call getUser(@Header("Authorization") String authorization)

使用OkHttp interceptor添加Header

当你有大量的请求需要添加Header时,这样在每个接口中书写一遍Header是一件很麻烦的事情,最常见的情况就是Oauther授权,AccessToken访问。这里处理方式就是在使用Retrofit执行接口的时候回创建OkHttpClient,给OkHttpClient添加Header配置。
实现如下:

    // 获取用户信息等一系列需要用到AccessToken的Http请求的头的设置
    public static final String STANDARD_HTTP_HEAD_AUTHORIZATION_KEY = "Authorization";
    public static final String STANDARD_HTTP_HEAD_AUTHORIZATION_VALUE = "Bearer ";
    public static final String STANDARD_HTTP_HEAD_CONTENT_TYPE_KEY = "Content-Type";
    public static final String STANDARD_HTTP_HEAD_CONTENT_TYPE_VALUE = "application/json;charset=UTF-8";
    public static final String STANDARD_HTTP_HEAD_ACCESS_TOKEN_KEY = "x-auth-token";
    /**
     * 正常的包括AccessToken请求头的请求,Json
     *
     * @param serviceClass
     * @param accessToken
     * @param 
     * @return
     */
    public  S createService(Class serviceClass, final String accessToken) {
        if (accessToken != null) {
            //添加固定的请求Header
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Interceptor.Chain chain) throws IOException {
                    Request original = chain.request();
                    // Request customization: add request headers
                    Request.Builder requestBuilder = original.newBuilder()
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_AUTHORIZATION_KEY, HttpRequstConstant.STANDARD_HTTP_HEAD_AUTHORIZATION_VALUE + accessToken)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_CONTENT_TYPE_KEY, HttpRequstConstant.STANDARD_HTTP_HEAD_CONTENT_TYPE_VALUE)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_ACCESS_TOKEN_KEY, accessToken)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_DEVICE_TOKEN_KEY, SecurityApplication.device_token)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_DEVICE_TYPE_KEY, SecurityApplication.device_type)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_VERSION_KEY, SecurityApplication.VERSION)
                            .method(original.method(), original.body());

                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }
        //创建Retrofit客户端
        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }

如上,每次调用接口的时候使用CreatService(clz,accessToken)就可以了,
例子中包括了AccessToken信息,以及服务器端自定义的需要的device,version等请求头信息。

最后附上多表单提交File时候,请求头的设置,你需要请求头中”Content-Type”设置为
“multipart/form-data”

    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
   /**
     * 正常的包括AccessToken请求头的请求,multipart内容类型
     *
     * @param serviceClass
     * @param accessToken
     * @param 
     * @return
     */
    public  S createFileService(Class serviceClass, final String accessToken) {
        if (accessToken != null) {
            //添加固定的请求Header
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Interceptor.Chain chain) throws IOException {
                    Request original = chain.request();
                    // Request customization: add request headers
                    Request.Builder requestBuilder = original.newBuilder()
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_AUTHORIZATION_KEY, HttpRequstConstant.STANDARD_HTTP_HEAD_AUTHORIZATION_VALUE + accessToken)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_CONTENT_TYPE_KEY, MULTIPART_FORM_DATA)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_ACCESS_TOKEN_KEY, accessToken)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_DEVICE_TOKEN_KEY, SecurityApplication.device_token)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_DEVICE_TYPE_KEY, SecurityApplication.device_type)
                            .header(HttpRequstConstant.STANDARD_HTTP_HEAD_VERSION_KEY, SecurityApplication.VERSION)
                            .method(original.method(), original.body());

                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }
        //创建Retrofit客户端
        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }

文件下载

下载固定地址下的文件,接口如下:

    /**
     * 下载文件
     */
    public interface DownLoadFileService {
        @GET
        Observable downLoad(@Url String fileUrl);
    }
    /**
     * 下载文件(大型文件)
     */
    public interface DownLoadFileService {
        @Streaming
        @GET
        Observable downLoad(@Url String fileUrl);
    }

大型文件添加@Streaming注解,在返回的回调中从ResponseBody中取出数据,然后储存成文件,储存代码如下:

    /**
     * 存储retrofit下载的文件
     *
     * @param body
     * @param filePath
     * @return
     */
    public boolean writeResponseBodyToDisk(ResponseBody body, String filePath) {
        LogUtil.e("securities", "writeResponseBodyToDisk body" + body.contentLength());
        try {
            File futureStudioIconFile = new File(filePath);
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try {
                byte[] fileReader = new byte[4096];
                long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;

                inputStream = body.byteStream();
                outputStream = new FileOutputStream(futureStudioIconFile);


                while (true) {
                    LogUtil.e("", "file read before inputStream available =" + inputStream.available());
                    int read = inputStream.read(fileReader);
                    LogUtil.e("", "file read after ");
                    if (read == -1) {
                        LogUtil.e("", "file read == -1 ");
                        break;
                    }
                    outputStream.write(fileReader, 0, read);
                    LogUtil.e("", "file write after ");
                    fileSizeDownloaded += read;
                    LogUtil.e("", "file download: " + fileSizeDownloaded + " of " + fileSize);
                }
                outputStream.flush();
                return true;
            } catch (IOException e) {
                LogUtil.e("securities", "writeResponseBodyToDisk fail e" + e.toString());
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            LogUtil.e("securities", "writeResponseBodyToDisk fail e" + e.toString());
            return false;
        }
    }

PS:最近貌似有人试过,上传文件不用设置请求头中的content-type为”multipart/form-data”,待验证,后续有时间,会就Rxjava在进行详细的讲解

你可能感兴趣的:(android开发)