天天看點

OkHttp與Retrofit上傳檔案詳解

Http上傳原理

Http上傳需要用到multipart/form-data請求方式,Http協定原始方法不支援multipart/form-data請求,那這個請求自然就是由原始的請求方法拼裝而成,具體規則如下:

1、multipart/form-data的本質上還是Post請求

2、multipart/form-data與post方法的不同之處:請求頭,請求體。

3、multipart/form-data的請求頭必須包含一個特殊的頭資訊:Content-Type,且其值也必須規定為multipart/form-data,同時還需要規定一個内容分割符用于分割請求體中的多個post的内容,如檔案内容和文本内容自然需要分割開來,不然接收方就無法正常解析和還原這個檔案了。

4、multipart/form-data的請求體也是一個字元串,不過和post的請求體不同的是它的構造方式,post是簡單的name=value值連接配接,而multipart/form-data則是添加了分隔符等内容的構造體。

抓包結果如下:

Request URL:https://your_base_url/open/qiniu/image
Request Method:POST
Status Code: OK
​
Request Headers
Accept-Encoding:gzip
Connection:Keep-Alive
Content-Length:
Content-Type:multipart/form-data; boundary=ed67c97edeaeb8df43d9
Host:your_base_url
token:d5240-de2aba5af71f567acd
User-Agent:Dalvik/2.1.0 (Linux; U; Android 5.1.1; vivo X7 Build/LMY47V) app_name/
​
Request Payload
--ed67c97edeaeb8df43d9
Content-Disposition: form-data; name="file"; filename="coin.jpg"
Content-Type: image/jpg
Content-Length: 
--ed67c97edeaeb8df43d9--
           

可以看到Request Headers中包含了Accept-Encoding、Content-Length、Content-Type、Host、User-Agent等參數,OkHttp會自動生成boundary,無需手動編寫規則;Request Payload中包含了具體的上傳内容,Content-Disposition包含了上傳檔案名以及part參數key值,Content-Type指明了上傳檔案的字尾格式。

User-Agent一般都會重寫,以其可以包含系統資訊和使用者自定義的資訊,系統資訊可以通過System.getProperty("http.agent")獲得,然後再拼接上app獨有的資訊即可

OkHttp上傳單個檔案

先直接看代碼吧

public static final String MULTIPART_FORM_DATA = "image/jpg";  // 指明要上傳的檔案格式

public static void okHttpUpload(String partName, String path, final UploadCallback callback){
       File file = new File(path);    // 需要上傳的檔案
       RequestBody requestFile =    // 根據檔案格式封裝檔案
               RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);
​
  // 初始化請求體對象,設定Content-Type以及檔案資料流
       RequestBody requestBody = new MultipartBody.Builder()
               .setType(MultipartBody.FORM)   // multipart/form-data
               .addFormDataPart(partName, file.getName(), requestFile)
               .build();
​
  // 封裝OkHttp請求對象,初始化請求參數
       Request request = new Request.Builder()
               .url(UPLOAD_URL)    // 上傳url位址
               .post(requestBody)    // post請求體
               .build();
​
       final okhttp3.OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
       OkHttpClient okHttpClient = httpBuilder
               .connectTimeout(, TimeUnit.SECONDS)   // 設定請求逾時時間
               .writeTimeout(, TimeUnit.SECONDS)
               .build();
       // 發起異步網絡請求
       okHttpClient.newCall(request).enqueue(new Callback() {
           @Override
           public void onResponse(Call call, okhttp3.Response response) throws IOException {
               if (callback != null){
                   callback.onResponse(call, response);
               }
           }
           @Override
           public void onFailure(Call call, IOException e) {
               if (callback != null){
                   callback.onFailure(call, e);
               }
           }
       });
   }

// 調用檔案上傳方法,需要傳入requestBody的key值,本地檔案路徑以及請求回調方法   
UploadWrapper.okHttpUpload("file", mImagePath, new UploadWrapper.UploadCallback() {
   @Override
   public void onResponse(Call call, final okhttp3.Response response) {
       try {
           final String result = response.body().string();
           JSONObject jsonObject = new JSONObject(result);
           mImageUrl = jsonObject.getJSONObject("data").getString("url");               
           showImage();               
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   @Override
   public void onFailure(Call arg0, IOException e) {
       e.printStackTrace();
   }
});

`
           

代碼中的注釋已經很清楚了,RequestBody分為兩部分,第一部分是封裝檔案,封裝時需要指明檔案格式,常見的檔案格式有.txt,.jpg,.png等等,如果是上傳圖檔,則MediaType為image/jpg,這裡的jpg可以換成png等其他圖檔格式,另一部分是封裝整個請求體,如果有多個檔案要上傳或者多個post請求key-value,則可以統一封裝到RequestBody中,此時還需要指明請求Content-Type,即multipart/form-data,檔案請求體可以通過addFormDataPart方法進行封裝,最後将請求體傳入OkHttp請求中即可。

OkHttp已經幫我們預先處理了很多工作,例如boundary不需要我們手動指定,請求内容的傳遞也隻需要調用OkHttp提供的api接口即可,無需關心上文抓包中的資料格式,如果是采用Android原生的HttpURLConnection實作檔案上傳,那麼所有的這些細節就必須都要考慮。

Retrofit實作檔案上傳

由于Retrofit底層本質上還是通過OkHttp實作的,是以基本原理和OkHttp也很想,隻不過Retrofit又對OkHttp進行了一次封裝,使其更直覺更好用,如果對OkHttp還不是很了解的,請參考我之前的一篇文章Retrofit用法詳解,這裡就直接讨論檔案上傳相關的實作。

先來看代碼吧

showProgressBar();
Observable.just("")
   .subscribeOn(Schedulers.computation())  // 切換至計算線程
   .map(new Func1<String, String>() {
       @Override
       public String call(String s) {
       // 圖檔壓縮
           mImagePath = BitmapExtKt.compressImageFileByQualityAndSize(Uri.parse(mImagePath).toString(),
                   getCacheDir().getPath() + File.separator + "coin.jpg", , , );
           return mImagePath;
       }
   })
   .subscribeOn(Schedulers.io())    // 切換至IO線程
   .flatMap(new Func1<String, Observable<UploadResultEntity>>() {
       @Override
       public Observable<UploadResultEntity> call(String s) {
           // 封裝請求體
           MultipartBody.Part body = UploadWrapper.prepareFilePart("file", mImagePath);
           // 具體的檔案上傳請求
           return mCoinService.uploadFile(body)
               .compose(new DefaultTransformer<Response<CommonResponse<UploadResultEntity>>,
                       CommonResponse<UploadResultEntity>>(mActivity))
               .map(new Func1<CommonResponse<UploadResultEntity>, UploadResultEntity>() {
                   @Override
                   public UploadResultEntity call(CommonResponse<UploadResultEntity> response) {
                       return response.data;
                   }
               });
       }
   })
   .observeOn(AndroidSchedulers.mainThread())   // 切換至Android主線程
   .subscribe(new Subscriber<UploadResultEntity>() {
       @Override
       public void onCompleted() {
           dismissProgressBar();
       }
       @Override
       public void onError(Throwable e) {
           dismissProgressBar();
           e.printStackTrace();
       }
       @Override
       public void onNext(UploadResultEntity uploadResultEntity) {
           mImageUrl = uploadResultEntity.url;
           showImage();
       }
   });
​
// 封裝請求體,可以看到這裡和OkHttp的請求體封裝基本上是一樣的
@NonNull
public static MultipartBody.Part prepareFilePart(String partName, String path) {
   File file = new File(path);
   RequestBody requestFile =
           RequestBody.create(MediaType.parse(UploadWrapper.MULTIPART_FORM_DATA), file);
   return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
​
// post請求定義,通過@Multipart指定multipart/form-data格式,@Part指定具體的請求體
@Multipart
@POST("/open/qiniu/image")
Observable<Response<CommonResponse<UploadResultEntity>>> uploadFile(@Part MultipartBody.Part file);
           

這裡用到了RxJava,如果對RxJava不是很熟悉,可以參考我之前的一篇文章RxJava與Retrofit實戰總結,這裡就不過多讨論了。重點看Http請求的代碼

@Multipart
@POST("/open/qiniu/image")
Observable<Response<CommonResponse<UploadResultEntity>>> uploadFile(@Part MultipartBody.Part file);
           

Http定義在Retrofit中顯得很簡單直覺,通過注解的方式即可指定請求方式,url,請求參數以及傳回值等等,請求參數也可以通過不同的注解完成封裝,詳細地可以參考Retrofit用法詳解,對于上傳請求,需要通過指定注解@Multipart,請求參數是以@Part的方式傳遞的。然後我們再來看一下請求體的具體封裝方法

@NonNull
public static MultipartBody.Part prepareFilePart(String partName, String path) {
   File file = new File(path);
   RequestBody requestFile =
           RequestBody.create(MediaType.parse(UploadWrapper.MULTIPART_FORM_DATA), file);
   return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
           

可以看到基本上和OkHttp的封裝方式是一樣的,隻不過api接口名稱不一樣而已,圖檔封裝格式以及需要傳遞的參數個數和參數類型也基本上一樣,是以就不再過多解釋了。

多檔案上傳利用Retrofit也很簡單,大部分都是一樣的,隻是需要傳遞更多的參數,參考代碼如下:

// 上傳多個檔案
@Multipart
@POST("upload")
Call<ResponseBody> uploadMultipleFiles(
       @Part("description") RequestBody description,
       @Part MultipartBody.Part file1,
       @Part MultipartBody.Part file2);
           

書到用時方恨少,紙上得來終覺淺。希望對你有所幫助。

繼續閱讀