OkHttp与Retrofit上传文件详解
2017-07-04 12:45
483 查看
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:200 OK Request Headers Accept-Encoding:gzip Connection:Keep-Alive Content-Length:117276 Content-Type:multipart/form-data; boundary=ed67c97e-2000-47de-9033-77aeb8df43d9 Host:your_base_url token:794d5240-de2a-465b-9a5a-66f71f567acd User-Agent:Dalvik/2.1.0 (Linux; U; Android 5.1.1; vivo X7 Build/LMY47V) app_name/1.5.0 Request Payload --ed67c97e-2000-47de-9033-77aeb8df43d9 Content-Disposition: form-data; name="file"; filename="coin.jpg" Content-Type: image/jpg Content-Length: 117075 --ed67c97e-2000-47de-9033-77aeb8df43d9--
可以看到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(100, TimeUnit.SECONDS) // 设置请求超时时间 .writeTimeout(150, 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", 1000, 0, 0); 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);
书到用时方恨少,纸上得来终觉浅。希望对你有所帮助。
相关文章推荐
- OkHttp与Retrofit上传文件详解
- Retrofit的详解与使用(多文件上传)
- RxJava+Retrofit+OkHttp实现文件上传
- Retrofit2 multpart多文件上传详解
- Rxjava+ReTrofit+okHttp深入浅出-终极封装三(文件上传)
- Android OkHttp Post上传文件并且携带参数实例详解
- Retrofit2 multpart多文件上传详解
- RxJava + Retrofit+okhttp 封装,包含对相同格式请求数据、相同格式返回数据处理,显示 Material Design 加载 dialog,文件上传下载进度展示、全局异常捕捉。
- RxJava+Retrofit+OkHttp深入浅出-终极封装三(文件上传)
- RxJava+Retrofit+OkHttp深入浅出-终极封装三(文件上传)
- Retrofit2 multpart多文件上传详解
- Retrofit 的使用详解(下载 多文件上传)
- RxJava+Retrofit+OkHttp深入浅出-终极封装三(文件上传)
- PHP文件上传实例详解!!!
- struts2的文件类型上传的详解
- PHP文件上传详解
- FastDFS的配置、部署与API使用解读(8)FastDFS多种文件上传接口详解
- nodejs-post文件上传原理详解
- FTP上传下载文件案例详解
- RestSharp使用详解(3)OSS文件上传的问题