OkHttp与Retrofit上传文件详解

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: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);

书到用时方恨少,纸上得来终觉浅。希望对你有所帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/115070.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Anycast 公网加速 AIA解决方案

    Anycast 公网加速 AIA解决方案Anycast公网加速AIA简介Anycast公网加速(AnycastInternetAcceleration,AIA)是一个覆盖多地的动态加速网络,可以大幅提升您业务的公网访问体验。不同于其他应用层加速服务,AIA能实现IP传输的质量优化和多入口就近接入,减少网络传输的抖动、丢包,最终提升云上应用的服务质量,扩大服务范围,精简后端部署。Anycast公网加速AIA功能Anycast公网加速提供多种强大功能,提升应用访问体验的同时,易于部署和管理。1、公网IP任播购买

  • java script 下载_JavaScript下载[通俗易懂]

    java script 下载_JavaScript下载[通俗易懂]JavascriptPlus是一个小巧的Javascript脚本辅助编程工具,主要方便开发者对js代码进行测试、预览以及运行等操作,特点包括用不同的颜色显示语法和关键词,有稍许的程序输入预测功能,测试运行子程序等等。JavascriptPlus是一款功能强劲的javascript文本编辑器。内置的智能系统能够提示你-各种Javascript物件、性质和触发事件,-各种Html和Shee…

  • python基础知识点(精心整理)_python编程基础知识

    python基础知识点(精心整理)_python编程基础知识在Python里,标识符有字母、数字、下划线组成。在Python中,所有标识符可以包括英文、数字以及下划线(_),但不能以数字开头。Python中的标识符是区分大小写的。以下划线开头的标识符是有特殊意义的。以单下划线开头_foo的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用fromxxximport*而导入;以双下划线开头的__foo代表类的私有成员;以双下划线开头和结尾的foo代表Python里特殊方法专用的标识,如init()代表类的构造函

  • Unity AssetBundle介绍

    Unity AssetBundle介绍1.什么是AssetBundle?AssetBundle是一种资源压缩包。资源打包AssetBundle有两个好处:减小安装包的大小,资源可以在进入游戏时候,网络下载。 热更资源,方便修复线上资源问题引起的Bug,或新增加游戏内容。AssetBundle分为两种类型:场景AssetBundle&非场景AssetBundle。2.AssetBundle结构2.1As…

  • java android实例_Android开发精典案例60个

    java android实例_Android开发精典案例60个【实例简介】Android开发精典案例60个【实例截图】【核心代码】2-1(Activity生命周期)3-1(Button与点击监听器)3-10-1(列表之ArrayAdapter适配)3-10-2(列表之SimpleAdapter适配)3-11(Dialog对话框)3-12-5(Activity跳转与操作)3-12-6(横竖屏切换处理)3-3(ImageButton图片按钮)3-4(EditTe…

  • ag-grid 设置单元格以及行的颜色「建议收藏」

    ag-grid 设置单元格以及行的颜色「建议收藏」在使用ag-grid的时候有通过单元格的值设置不同行颜色,然后百度了网上的方法,汇总了一下,具体效果图如下:话不多说,直接上代码。<!doctypehtml><html><head><metacharset=”utf-8″><metaname=”viewport”content=”width=device-width,initial-scale=1,shrink…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号