利用Retrofit进行各种网络请求

来源:互联网 发布:网络彩票开售解禁通知 编辑:程序博客网 时间:2024/05/01 01:44

利用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 <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在进行详细的讲解

0 0
原创粉丝点击