Retrofit2.0 實現(xiàn)圖文(參數(shù)+圖片)上傳方法總結(jié)
最近項目里用到了類似圖文上傳的功能,以前都是封裝OkHttp的文件上傳功能,這次想換個姿勢,想用Retrofit2.0實現(xiàn)這樣的功能,本來以為挺簡單的,沒想到進入了深坑,連續(xù)調(diào)整了好幾種姿勢都報了同一個錯,接著網(wǎng)上類似的文章找了一大推,講得都是模棱兩可,或者對多參數(shù)格式不夠友好,最后還是去看了相關(guān)的源碼,自己把這個問題提出來解決了,在這里記錄一下。
一、定義網(wǎng)絡請求接口
public interface GoodsReturnApiService {
@Multipart
@POST(Compares.GOODS_RETURN_POST) //這里是自己post文件的地址
Observable<GoodsReturnPostEntity> postGoodsReturnPostEntitys(@PartMap Map<String, RequestBody> map, @Part List<MultipartBody.Part> parts);
}
上面定義了一個接口用于上傳文件請求,有幾個注解需要說明一下, @Multipart這是Retrofit專門用于文件上傳的注解,需要配合@POST一起使用。
方法postGoodsReturnPostEntitys(@PartMap Map<String, RequestBody> map, @Part List<MultipartBody.Part> parts)第一個參數(shù)使用注解@PartMap用于多參數(shù)的情況,如果是單個參數(shù)也可使用注解@Part。
在類型Map<String, RequestBody>中,Map第一個泛型String是服務器接收用于文件上傳參數(shù)字段的Key,第二個泛型RequestBody是OkHttp3包裝的上傳參數(shù)字段的Value,這也是圖文上傳成功的關(guān)鍵所在。在后面會具體說到。
第二個參數(shù)使用注解@Part用于文件上傳,多文件上傳使用集合類型List<MultipartBody.Part>,單文件可以使用類型MultipartBody.Part,具體的使用同樣后面講。
這里著重說明一下,postGoodsReturnPostEntitys(@PartMap Map<String, RequestBody> map, @Part List<MultipartBody.Part> parts)方法參數(shù)這樣寫純屬個人習慣,你也可以直接使用一個參數(shù)postGoodsReturnPostEntitys(@PartMap Map<String, RequestBody> map),不過后面對RequestBody的處理方式也要跟著變化,這里就不詳細說了,只會介紹上面這種簡便清晰的方式。
二、初始化Retrofit
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封裝比較簡陋,為的是查看網(wǎng)絡攔截,就不詳細說了。
三、發(fā)起文件上傳請求
private void postGoodsPicToServer(){
Map<String,RequestBody> params = new HashMap<>();
//以下參數(shù)是偽代碼,參數(shù)需要換成自己服務器支持的
params.put("type", convertToRequestBody("type"));
params.put("title",convertToRequestBody("title"));
params.put("info",convertToRequestBody("info");
params.put("count",convertToRequestBody("count"));
//為了構(gòu)建數(shù)據(jù),同樣是偽代碼
String path1 = Environment.getExternalStorageDirectory() + File.separator + "test1.jpg";
String path2 = Environment.getExternalStorageDirectory() + File.separator + "test1.jpg";
List<File> fileList = new ArrayList<>();
fileList.add(new File(path1));
fileList.add(new File(path2));
List<MultipartBody.Part> partList = filesToMultipartBodyParts(fileList);
HttpRequestClient.getRetrofitHttpClient().create(GoodsReturnApiService.class)
.postGoodsReturnPostEntitys(params,partList)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GoodsReturnPostEntity>() {
@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都是構(gòu)造的偽代碼,需要根據(jù)自己項目的業(yè)務需求改變。
下面是上傳文件成功第一個關(guān)鍵,對參數(shù)請求頭(姑且叫這個名字,對應Retrofit上傳文件時參數(shù)那部分請求頭,下文件(圖片)請求頭同理,對應文件那部分請求頭)的content-type賦值,使用convertToRequestBody()方法。
private RequestBody convertToRequestBody(String param){
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), param);
return requestBody;
}
因為GsonConverterFactory.create()轉(zhuǎn)換器的緣故,會將參數(shù)請求頭的content-type值默認賦值application/json,如果沒有進行這步轉(zhuǎn)換操作,就可以在OKHttp3的日志攔截器中查看到這樣的賦值,這樣導致服務器不能正確識別參數(shù),導致上傳失敗,所以這里需要對參數(shù)請求頭的content-type設置一個正確的值:text/plain。
下面是上傳文件成功第二個關(guān)鍵的地方,將文件(圖片)請求頭的content-type使用方法filesToMultipartBodyParts()對其賦值"image/png",并返回MultipartBody.Part集合。
private List<MultipartBody.Part> filesToMultipartBodyParts(List<File> files) {
List<MultipartBody.Part> 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;
}
說到底,還是對參數(shù)請求頭和文件(圖片)請求頭的content-type屬性賦值處理,不要讓Retrofit 默認賦值,這里才是關(guān)鍵。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
玩轉(zhuǎn)AppBarLayout實現(xiàn)更酷炫的頂部欄
玩轉(zhuǎn)AppBarLayout,實現(xiàn)更酷炫的頂部欄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
RecyclerView中使用CheckBox出現(xiàn)勾選混亂的解決方法
這篇文章主要為大家詳細介紹了RecyclerView中使用CheckBox出現(xiàn)勾選混亂的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Android ViewPager與radiogroup實現(xiàn)關(guān)聯(lián)示例
本篇文章主要介紹了Android ViewPager與radiogroup實現(xiàn)關(guān)聯(lián)示例,具有一定的參考價值,有興趣的可以了解一下。2017-03-03
Android ImageView隨手勢變化動態(tài)縮放圖片
這篇文章主要為大家詳細介紹了Android ImageView隨手勢變化動態(tài)縮放圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05
Android 中FloatingActionButton(懸浮按鈕)實例詳解
這篇文章主要介紹了Android 中FloatingActionButton(懸浮按鈕)實例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android實現(xiàn)進度條(ProgressBar)的功能與用法
這篇文章主要為大家詳細介紹了Android實現(xiàn)進度條(ProgressBar)的功能與用法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-08-08
Android自定義ListView實現(xiàn)下拉刷新上拉加載更多
Listview現(xiàn)在用的很少了,基本都是使用Recycleview,但是不得不說Listview具有劃時代的意義,我們可以自己添加下拉刷新,上拉加載更多功能。本文就來利用自定義ListView實現(xiàn)下拉刷新上拉加載更多效果,需要的可以參考一下2022-10-10
RecyclerView+SnapHelper實現(xiàn)無限循環(huán)篩選控件
這篇文章主要為大家詳細介紹了RecyclerView+SnapHelper實現(xiàn)無限循環(huán)篩選控件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10

