安卓学习笔记---Retrofit2.0 实现图文(参数+图片)上传方法总结

转载博客地址:

http://www.jb51.net/article/120237.htm


最近项目里用到了类似图文上传的功能,以前都是封装OkHttp的文件上传功能,这次想换个姿势,想用Retrofit2.0实现这样的功能,本来以为挺简单的,没想到进入了深坑,连续调整了好几种姿势都报了同一个错,接着网上类似的文章找了一大推,讲得都是模棱两可,或者对多参数格式不够友好,最后还是去看了相关的源码,自己把这个问题提出来解决了,在这里记录一下。

一、定义网络请求接口

?
1
2
3
4
5
public interface GoodsReturnApiService {
   @Multipart
   @POST (Compares.GOODS_RETURN_POST)  //这里是自己post文件的地址
   Observable postGoodsReturnPostEntitys( @PartMap Map map, @Part List parts);
}

上面定义了一个接口用于上传文件请求,有几个注解需要说明一下, @Multipart这是Retrofit专门用于文件上传的注解,需要配合@POST一起使用。

方法postGoodsReturnPostEntitys(@PartMap Map map, @Part List parts)第一个参数使用注解@PartMap用于多参数的情况,如果是单个参数也可使用注解@Part。

在类型Map中,Map第一个泛型String是服务器接收用于文件上传参数字段的Key,第二个泛型RequestBody是OkHttp3包装的上传参数字段的Value,这也是图文上传成功的关键所在。在后面会具体说到。

第二个参数使用注解@Part用于文件上传,多文件上传使用集合类型List,单文件可以使用类型MultipartBody.Part,具体的使用同样后面讲。

这里着重说明一下,postGoodsReturnPostEntitys(@PartMap Map map, @Part List parts)方法参数这样写纯属个人习惯,你也可以直接使用一个参数postGoodsReturnPostEntitys(@PartMap Map map),不过后面对RequestBody的处理方式也要跟着变化,这里就不详细说了,只会介绍上面这种简便清晰的方式。

二、初始化Retrofit

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class HttpRequestClient {
 
   public static final String TAG = "HttpRequestClientTAG" ;
 
   private static Retrofit retrofit;
 
   private static OkHttpClient getOkHttpClient() {
     //日志显示级别
     HttpLoggingInterceptor.Level level= HttpLoggingInterceptor.Level.BODY;
     //新建log拦截器
     HttpLoggingInterceptor loggingInterceptor= new HttpLoggingInterceptor( new HttpLoggingInterceptor.Logger() {
       @Override
       public void log(String message) {
         Log.d(TAG, message);
       }
     });
     loggingInterceptor.setLevel(level);
     //定制OkHttp
     OkHttpClient.Builder httpClientBuilder = new OkHttpClient
         .Builder();
     //OkHttp进行添加拦截器loggingInterceptor
     httpClientBuilder.addInterceptor(loggingInterceptor);
     return httpClientBuilder.build();
   }
 
   public static Retrofit getRetrofitHttpClient(){
     if ( null == retrofit){
       synchronized (HttpRequestClient. class ){
         if ( null == retrofit){
           retrofit = new Retrofit.Builder()
               .client(getOkHttpClient())
               .baseUrl(Compares.URL)
               .addConverterFactory(GsonConverterFactory.create())
               .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
               .build();
         }
       }
     }
     return retrofit;
   }
}

为了演示,Retrofit封装比较简陋,为的是查看网络拦截,就不详细说了。

三、发起文件上传请求

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private void postGoodsPicToServer(){
     Map params = new HashMap<>();
     //以下参数是伪代码,参数需要换成自己服务器支持的
     params.put( "type" , convertToRequestBody( "type" ));
     params.put( "title" ,convertToRequestBody( "title" ));
     params.put( "info" ,convertToRequestBody( "info" );
     params.put( "count" ,convertToRequestBody( "count" ));
 
     //为了构建数据,同样是伪代码
     String path1 = Environment.getExternalStorageDirectory() + File.separator + "test1.jpg" ;
     String path2 = Environment.getExternalStorageDirectory() + File.separator + "test1.jpg" ;
 
     List fileList = new ArrayList<>();
 
     fileList.add( new File(path1));
     fileList.add( new File(path2));
 
     List partList = filesToMultipartBodyParts(fileList);
 
     HttpRequestClient.getRetrofitHttpClient().create(GoodsReturnApiService. class )
         .postGoodsReturnPostEntitys(params,partList)
         .subscribeOn(Schedulers.newThread())
         .observeOn(AndroidSchedulers.mainThread())
         .subscribe( new Observer() {
           @Override
           public void onSubscribe( @NonNull Disposable d) {
 
           }
 
           @Override
           public void onNext( @NonNull GoodsReturnPostEntity goodsReturnPostEntity) {
 
           }
 
           @Override
           public void onError( @NonNull Throwable e) {
 
           }
 
           @Override
           public void onComplete() {
 
           }
         });
}

上面的params和fileList都是构造的伪代码,需要根据自己项目的业务需求改变。

下面是上传文件成功第一个关键,对参数请求头(姑且叫这个名字,对应Retrofit上传文件时参数那部分请求头,下文件(图片)请求头同理,对应文件那部分请求头)的content-type赋值,使用convertToRequestBody()方法。

?
1
2
3
4
private RequestBody convertToRequestBody(String param){
     RequestBody requestBody = RequestBody.create(MediaType.parse( "text/plain" ), param);
     return requestBody;
   }

因为GsonConverterFactory.create()转换器的缘故,会将参数请求头的content-type值默认赋值application/json,如果没有进行这步转换操作,就可以在OKHttp3的日志拦截器中查看到这样的赋值,这样导致服务器不能正确识别参数,导致上传失败,所以这里需要对参数请求头的content-type设置一个正确的值:text/plain。

下面是上传文件成功第二个关键的地方,将文件(图片)请求头的content-type使用方法filesToMultipartBodyParts()对其赋值"image/png",并返回MultipartBody.Part集合。

?
1
2
3
4
5
6
7
8
9
private List filesToMultipartBodyParts(List files) {
     List parts = new ArrayList<>(files.size());
     for (File file : files) {
       RequestBody requestBody = RequestBody.create(MediaType.parse( "image/png" ), file);
       MultipartBody.Part part = MultipartBody.Part.createFormData( "multipartFiles" , file.getName(), requestBody);
       parts.add(part);
     }
     return parts;
   }

说到底,还是对参数请求头和文件(图片)请求头的content-type属性赋值处理,不要让Retrofit 默认赋值,这里才是关键。

根据以上信息,自己写的方法图片上传和文件上传

//图片上传接口   @Multipart用于文件上传的注解,需要配合@POST一起使用
@Multipart
@POST("upload.jhtml")
Observable> fileUploadImage(@PartMap Map map, @Part MultipartBody.Part part);

//文件上传接口
@Multipart
@POST("upload.jhtml")
Observable> fileUploadPlayRecord(@PartMap Map map, @Part MultipartBody.Part part);

图片上传示例

Map params = new HashMap<>();
//以下参数是伪代码,参数需要换成自己服务器支持的
params.put("token", RequestBodyUtil.convertToRequestBody(SharedPreferenceUtil.getString("token")));
params.put("fileType",RequestBodyUtil.convertToRequestBody("image"));

File file = new File(imageUrl);

RequestBody requestBody = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody);

final UserService userService = RetrofitProvider.getInstance().create(UserService.class);
emit(userService.fileUploadImage(params, part), new ApiResponseListener() {
    @Override
    public void onApiSuccess(ImageUrl imageUrlData) {
        LoggerUtil.i("UserUpdate", "headImagePath-->" + imageUrlData.getUrl());
    }

    @Override
    public void onApiFail(int errorCode, String errorMessage) {
        ToastUtil.showToast(UserUpdateActivity.this,errorMessage);
    }
});

文件上传

final File file = new File(Contant.PATH);
if (!file.exists()) {
    file.mkdirs();
}
File fileSave = new File(Contant.PATH + "aa.txt");
if (!fileSave.exists()) {
    try {
        fileSave.createNewFile();
        print("测试的数据");

    } catch (IOException e) {
        e.printStackTrace();
    }
}

Map params = new HashMap<>();
//以下参数是伪代码,参数需要换成自己服务器支持的
params.put("token", RequestBodyUtil.convertToRequestBody(SharedPreferenceUtil.getString("token")));
params.put("fileType",RequestBodyUtil.convertToRequestBody("file"));
params.put("action", RequestBodyUtil.convertToRequestBody("result"));
params.put("memberId",RequestBodyUtil.convertToRequestBody(String.valueOf(LoginControllor.getLoginMember().getMember().getId())));
params.put("msId", RequestBodyUtil.convertToRequestBody(String.valueOf(forId)));

RequestBody requestBodyPart = RequestBody.create(MediaType.parse("multipart/form-data; charset=utf-8"), fileSave);
MultipartBody.Part part = MultipartBody.Part.createFormData("file", fileSave.getName(), requestBodyPart);


UserService userService = RetrofitProvider.getInstance().create(UserService.class);

emit(userService.fileUploadPlayRecord(params, part), new ApiResponseListener() {
    @Override
    public void onApiSuccess(PlayRecord playRecord) {
        LoggerUtil.i("Play", "playRecord-->" + playRecord);

    }

    @Override
    public void onApiFail(int errorCode, String errorMessage) {
        ToastUtil.showToast(Play.this,errorMessage);
    }
});


你可能感兴趣的:(Retrofit,Okhttp3)