网站首页 > 开源技术 正文
OKhttp作为一款优秀的android网络框架,在我们的日常开发中经常使用到,我们除了用来发起get、post等请求,还可以用来上传文件,比如上传图片,正常来说其实是没什么问题的,但是可能会遇到上传图片失败的问题。
我在近期的项目开发中也遇到了使用OKhttp上传图片失败的问题。
项目工程由于历史悠久,迭代周期长达几年,起初网络库使用android内置的httpClient和HttpURLConnection,后来改成了Volley,再后来又换成了retrofit(原理上是通过Okhttp实现各种网络操作的)。
但是。
由于各种原因(具体原因各位可以自行脑补)项目工程中的上传图片还使用HttpURLConnection来实现,严格来讲应该统一通过retrofit来实现(整个项目工程应该采用统一的网络框架),刚好有闲暇时间,便想着把上传图片的网络层实现改为retrofit。
配置上传接口请求服务:
public interface UploadService {
@Multipart
@POST
Observable<ResponseBody> upLoadImage(@QueryMap Map<String, Object> map, @Part MultipartBody.Part file);
}
上传实现如下:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(20, TimeUnit.SECONDS)
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpeg"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("img", "image.jpg", requestBody);
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("xxxx")//这里替换为请求主机头地址
.client(okHttpClient)
.build();
UploadService apiService = retrofit.create(UploadService.class);
apiService.upLoadImage(map, body).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ResponseBody responseBody) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
这样就可以了,运行一下试试,结果,真的上传不成功。
通过charles抓包观察接口返回的Response如下:
{
"state": 0,
"msg": "unknown request header",
"data": {}
}
unknown request header?
因为我们与接口有个约定,当上传成功后返回的response的state字段值应该为1,且会把图片的远程url也一并给回来,所以这个时候其实是上传失败的。
请求参数其实是跟原来用HttpURLConnection的时候一样的,怎么换成retrofit就不行了呢,我们先看下原先用HttpURLConnection上传图片后接口返回的response是怎样的:
{
"state": 1,
"msg": "",
"data": [{
"original": "xxx.jpg",
"thumb_b": "xxx.jpg?size=1000x1000",
"thumb_s": "xxx.jpg?size=100*100",
"id": 8651
}]
}
百思不得其解,经过漫长的对比排查后,终于通过retrofit成功上传了图片,做法就是通过charles抓包打断点后改了retrofit发起的上传图片的request,把对应的Form表单格式改成跟用HttpURLConnection上传图片的一直,我们先看下一开始两种方式上传图片对应的form表单对比图:
1、通过HttpURLConnection上传图片的form表单格式如下:
2、通过retrofit上传图片的form表单格式如下:
仔细比对我们发现不同的地方是通过retrofit上传图片的form表单内容多了个Content-length内容,奈何我们服务端api不支持包含“Content-length”的上传图片请求,如果没这个字段就可以请求成功了,可是我们并没有显式地往form表单内容添加过“Content-length”,所以应该是网络库帮忙自动添加进去的,在浏览了对应的源码后,发现在MultipartBody.class中发现有对“Content-length”的写入,源码截图如下
我们看到以下这句
sink.writeUtf8("Content-Length: ")
那么我们只需要在使得这句的if条件不成立,就可以避免往form表单内容写入“Content-length”
这里的body是我们创建的RequestBody,我们最终调用的是RequestBody的create方法创建的,源码如下:
我们看到了contentLength()方法返回的是文件的大小,所以上边提到的if条件是永远成立的,因此最终上传的form表单内容也会带“Content-length”,找到原因就好办了,我们只需要在创建RequestBody的时候重写contentLength()方法,使得返回值为-1就可以让form表单内容不写入“Content-length”了,通过如下代码创建新的RequestBody:
/**
* 创建用于上传图片的RequestBody
*/
private RequestBody createRequestBody(final @Nullable MediaType contentType, final File file) {
return new RequestBody() {
@Override
public @Nullable
MediaType contentType() {
return contentType;
}
@Override
public long contentLength() {
/*上传图片最终会用到MultipartBody,表单数据最终会写入Content-Length:xxx,xxx的值为contentLength(),
* 而我们的服务端api不支持Content-Length,需要禁止Content-Length的写入,写入逻辑见MultipartBody的
* writeOrCountBytes方法*/
return /*file.length()*/-1;
}
@Override
public void writeTo(@Nullable BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(file);
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
最终我们的上传图片实现如下:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(20, TimeUnit.SECONDS)
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
RequestBody requestBody = createRequestBody(MediaType.parse("image/jpeg"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("img", "image.jpg", requestBody);
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("xxxx")//这里替换为请求主机头地址
.client(okHttpClient)
.build();
UploadService apiService = retrofit.create(UploadService.class);
apiService.upLoadImage(map, body).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ResponseBody responseBody) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
搞定,收工。
希望本文可以帮助到您,也希望各位不吝赐教,提出您在使用中的宝贵意见,谢谢。
猜你喜欢
- 2024-09-28 我放弃了okhttp、httpClient,选了这个神仙工具
- 2024-09-28 很懵圈,记录一次MinIO 使用okhttp版本的问题?
- 2024-09-28 android学习,OkHttp,拦截器(okhttp自定义拦截器放在哪一层)
- 2024-09-28 工作日报 2021.10.20 OkHttp3错误异常:unexpected end of stream
- 2024-09-28 深入浅出 OkHttp 源码解析及应用实践
- 2024-09-28 用OkHttp实现WebSocket长连接(利用输入实现预期结果的相互关联或者相作用的一组活动 描述的是)
- 2024-09-28 干货-okHttp的优点-收藏了(水瓶座女的缺点和优点)
- 2024-09-28 软件更新丨OkHttp 4.0.0 RC 3 发布,从 Java 切换到 Kotlin
- 2024-09-28 开发者必备的Android开发资源之OkHttp
- 2024-09-28 HTTP客户端连接,选择HttpClient还是OkHttp?
你 发表评论:
欢迎- 03-19基于layui+springcloud的企业级微服务框架
- 03-19开箱即用的前端开发模板,扩展Layui原生UI样式,集成第三方组件
- 03-19SpringMVC +Spring +Mybatis + Layui通用后台管理系统OneManageV2.1
- 03-19SpringBoot+LayUI后台管理系统开发脚手架
- 03-19layui下拉菜单form.render局部刷新方法亲测有效
- 03-19Layui 遇到的坑(记录贴)(layui chm)
- 03-19基于ASP.NET MVC + Layui的通用后台开发框架
- 03-19LayUi自定义模块的定义与使用(layui自定义表格)
- 最近发表
-
- 基于layui+springcloud的企业级微服务框架
- 开箱即用的前端开发模板,扩展Layui原生UI样式,集成第三方组件
- SpringMVC +Spring +Mybatis + Layui通用后台管理系统OneManageV2.1
- SpringBoot+LayUI后台管理系统开发脚手架
- layui下拉菜单form.render局部刷新方法亲测有效
- Layui 遇到的坑(记录贴)(layui chm)
- 基于ASP.NET MVC + Layui的通用后台开发框架
- LayUi自定义模块的定义与使用(layui自定义表格)
- Layui 2.9.11正式发布(layui2.6)
- Layui 2.9.13正式发布(layui2.6)
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)