利用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在进行详细的讲解
- 利用Retrofit进行各种网络请求
- Retrofit进行网络请求
- 使用Retrofit进行网络请求
- 利用Retrofit进行网络访问
- AndroidStudio下使用Retrofit进行网络请求
- Retrofit+RxJava进行网络请求流程解析
- 利用Handler进行网络请求
- Retrofit各种姿势请求
- 智能厨房重构-Retrofit和RxJava进行网络请求
- 使用Retrofit进行Http、Https网络请求(快速上手)
- Android Kotlin 开发--偶遇Rxjava、Retrofit进行网络请求
- 关于retrofit进行网络请求时发生的奇怪错误
- Retrofit和RxJava加OkHttp网络请求进行二次封装
- 使用Retrofit进行Http、Https网络请求(快速上手)
- Retrofit 网络请求
- Retrofit网络请求
- Retrofit网络请求入门
- 初试Retrofit网络请求
- mybaits批量存储记录(有实例)
- git 的补丁使用方法
- 测试Android Studio自动生成Demo(Master Detail Flow)
- jquery简单应用
- SDK制作初篇
- 利用Retrofit进行各种网络请求
- ubuntu 16.04 编译安装g2o出错的解决方案
- C++关于共用体的理解
- LeetCode 202. Happy Number
- android之File文件简单操作
- 文件夹显示过滤器Beyond Compare如何处理
- Vim编辑器常用命令
- 整合Spring Data JPA与Spring MVC: 分页和排序
- Linux常用命令