利用Retrofit进行各种网络请求

本文详细介绍了如何使用Retrofit2.0进行各种网络请求,包括基本的HttpClient设置、接口封装、异步和同步请求、动态URL和查询参数、上传Json和实体、表单提交、文件上传、多表单混合提交、Header设置以及文件下载。通过实例展示了Retrofit在网络请求中的应用,特别是处理复杂场景的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

利用Retrofit进行各种网络请求

目录

前言

关于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 <S>
     * @return
     */
    public <S> S createService(Class<S> 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<HttpReply<ArrayList<Dictionaries>>> getTrafficLaw();
    }
    /**
     * 查询法律条文接口(Sync)
     */
    public interface TrafficLawService {
        @GET("dictitem/getItemListByKind.json?kind=TrafficLaw")
        HttpReply<ArrayList<Dictionaries>> getTrafficLaw();
    }

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

请求示例

 Observable<HttpReply<ArrayList<Dictionaries>>> call =
        client.createService(TrafficLawService).getTrafficLaw();

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

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

不同的网络请求

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

请求地址中包含变量

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

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

请求地址中包含查询变量

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

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

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

上传Json/上传实体

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

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

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

Form提交,提交参数

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

@FormUrlEncoded
@POST("user/edit")
Call<User> 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<HttpReply<Boolean>> 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<HttpReply<Boolean>> 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<HttpReply<Boolean>> uploadProofs(@Part List<MultipartBody.Part> fileMap, @PartMap HashMap<String, RequestBody> map);
    }

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

    /**
     * 证据上传
     */
    public void upLoadProofs(Subscriber<HttpReply<Boolean>> subscriber, String accessToken, String accidentId, HashMap<Integer, String> photoMap, List<ProofTemplate> 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<MultipartBody.Part> list = new ArrayList<>();
        //添加图片证据
        for (Map.Entry<Integer, String> 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<String, RequestBody> 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<List<Widget>> widgetList();
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

Header中带有变量

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

@GET("user")
Call<User> 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 <S>
     * @return
     */
    public <S> S createService(Class<S> 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 <S>
     * @return
     */
    public <S> S createFileService(Class<S> 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<ResponseBody> downLoad(@Url String fileUrl);
    }
    /**
     * 下载文件(大型文件)
     */
    public interface DownLoadFileService {
        @Streaming
        @GET
        Observable<ResponseBody> 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在进行详细的讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值