編輯:關於Android編程
最近項目裡用到了類似圖文上傳的功能,以前都是封裝OkHttp的文件上傳功能,這次想換個姿勢,想用Retrofit2.0實現這樣的功能,本來以為挺簡單的,沒想到進入了深坑,連續調整了好幾種姿勢都報了同一個錯,接著網上類似的文章找了一大推,講得都是模稜兩可,或者對多參數格式不夠友好,最後還是去看了相關的源碼,自己把這個問題提出來解決了,在這裡記錄一下。
一、定義網絡請求接口
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)第一個參數使用注解@PartMap用於多參數的情況,如果是單個參數也可使用注解@Part。
在類型Map<String, RequestBody>中,Map第一個泛型String是服務器接收用於文件上傳參數字段的Key,第二個泛型RequestBody是OkHttp3包裝的上傳參數字段的Value,這也是圖文上傳成功的關鍵所在。在後面會具體說到。
第二個參數使用注解@Part用於文件上傳,多文件上傳使用集合類型List<MultipartBody.Part>,單文件可以使用類型MultipartBody.Part,具體的使用同樣後面講。
這裡著重說明一下,postGoodsReturnPostEntitys(@PartMap Map<String, RequestBody> map, @Part List<MultipartBody.Part> parts)方法參數這樣寫純屬個人習慣,你也可以直接使用一個參數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封裝比較簡陋,為的是查看網絡攔截,就不詳細說了。
三、發起文件上傳請求
private void postGoodsPicToServer(){ Map<String,RequestBody> 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<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都是構造的偽代碼,需要根據自己項目的業務需求改變。
下面是上傳文件成功第一個關鍵,對參數請求頭(姑且叫這個名字,對應Retrofit上傳文件時參數那部分請求頭,下文件(圖片)請求頭同理,對應文件那部分請求頭)的content-type賦值,使用convertToRequestBody()方法。
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集合。
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; }
說到底,還是對參數請求頭和文件(圖片)請求頭的content-type屬性賦值處理,不要讓Retrofit 默認賦值,這裡才是關鍵。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
Android權限對於開發者來說既是最好的朋友,也是最壞的敵人。即便是優秀且有信譽的應用開發人員和公司,也需要深度的Android權限以保證基礎 性的功能。
一、為什麼需要自定義鎖屏頁鎖屏作為一種黑白屏時代就存在的手機功能,至今仍發揮著巨大作用,特別是觸屏時代的到來,鎖屏的功用被發揮到了極致。多少人曾經在無聊的時候每隔幾分鐘劃
我們畫常規圖形時一般用word上面自帶的插件或visio畫圖。但是我在畫linux內核文件系統結構圖、內存分配以及學習數據結構樹、圖時,發現使用Graphviz非常的方便
簡單拓展RadioButton控件使用如果不看這些,就直接下托RadioButton和CheckBox的區別:1、單個RadioButton在選中後,通過點擊無法變為未選