okhttp文件上传失败,居然是Android Studio背锅?

}

Response response;

try {

response = chain.proceed(request);

} catch (IOException ex) {

}

try {

if (tracker != null) {

response = trackResponse(tracker, response); //2、追踪响应体

}

} catch (Exception ex) {

StudioLog.e(“Could not track an OkHttp3 response”, ex);

}

return response;

}

可以确定它就是一个网络监控器,但它是不是AS的网络监听器,我却还持怀疑态度,因为我这个项目没开启Profiler分析器,但我最近在开发room数据库相关功能,开启了数据分析器Database Inspector,难道跟这个有关?我尝试关掉Database Inspector,并且重启app,再次尝试文件上传,居然成功了,是真的成功了,你能信?我也不信,于是,再次开启Database Inspector,再次尝试文件上传,失败了,异常跟之前的一模一样;接着,我关闭Database Inspector,并且打开Profiler分析器,再次尝试文件上传,一样失败了。

我想到这里,基本可以认定OkHttp3Interceptor就是Profiler里面的网络监控器,但也好像缺乏直接证据,于是,我尝试改了下ProgressRequestBody类,如下:

public class ProgressRequestBody extends RequestBody {

//省略相关代码

private BufferedSink bufferedSink;

@Override

public void writeTo(BufferedSink sink) throws IOException {

//如果调用方是OkHttp3Interceptor,不写请求体,直接返回

if (sink.toString().contains(

“com.android.tools.profiler.support.network.HttpTracker$OutputStreamTracker”))

return;

if (bufferedSink == null) {

bufferedSink = Okio.buffer(sink(sink));

}

requestBody.writeTo(bufferedSink);

bufferedSink.flush();

}

}

以上代码,仅仅加了一句if语句,这条语句可以判断当前调用方是不是OkHttp3Interceptor,是的话,不写请求体,直接返回;如果OkHttp3Interceptor就是Profiler里的网络监控器,那么此时Profiler里应该是看不到请求体的,也就是看不到请求参数,如下:

可以看到,Profiler里的网络监控器,没有监控到请求参数。

这就证实了OkHttp3Interceptor的确是Profiler里的网络监控器,也就是AS动态注入的。

OkHttp3Interceptor 与文件上传是否有直接的关系?

通过上面的案例分析,显然是有直接关系的,当你未打开Database Inspector、Profiler时,文件上传一切正常。

OkHttp3Interceptor是如何影响文件上传的?

回到正题,OkHttp3Interceptor是如何影响文件上传的?这个就需要继续分析OkHttp3Interceptor的源码,来看看追踪请求体的代码:

public final class OkHttp3Interceptor implements Interceptor {

private HttpConnectionTracker trackRequest(Request request) throws IOException {

StackTraceElement[] callstack =

OkHttpUtils.getCallstack(request.getClass().getPackage().getName());

HttpConnectionTracker tracker =

HttpTracker.trackConnection(request.url().toString(), callstack);

tracker.trackRequest(request.method(), toMultimap(request.headers()));

if (request.body() != null) {

OutputStream outputStream =

tracker.trackRequestBody(OkHttpUtils.createNullOutputStream());

BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));

request.body().writeTo(bufferedSink); // 1、将请求体写入到BufferedSink中

bufferedSink.close(); // 2、关闭BufferedSink

}

return tracker;

}

}

想到这里问题就很清楚了,上面备注的第一代码中request.body(),拿到的就是ProgressRequestBody对象,随后调用其writeTo(BufferedSink)方法,传入BufferedSink对象,方法执行完,就将BufferedSink对象关闭了,然而,ProgressRequestBody里却将BufferedSink声明为成员变量,并且为空时才会赋值,这就导致后续CallServerInterceptor调用其writeTo(BufferedSink)方法时,使用的还是上一个已关闭的BufferedSink对象,此时再往里面写数据,自然就

java.lang.IllegalStateException: closed异常了。

四、如何解决

==================================================================

知道了具体的原因,就好解决,将ProgressRequestBody里面的BufferedSink对象改为局部变量即可,如下:

public class ProgressRequestBody extends RequestBody {

//省略相关代码

@Override

public void writeTo(BufferedSink sink) throws IOException {

BufferedSink bufferedSink = Okio.buffer(sink(sink));

requestBody.writeTo(bufferedSink);

bufferedSink.colse();

}

}

改完后,开启Profiler里的网络监控器,再次尝试文件上传,ok成功了,但又有一个新的问题,ProgressRequestBody是用于监听上传进度的,OkHttp3Interceptor、CallServerInterceptor先后调用了其writeTo(BufferedSink)方法,这就会导致请求体写两次,也就是进度监听会收到两遍,而我们真正需要的是CallServerInterceptor调用的那次,咋整?好办,我们前面就判断过调用方是否OkHttp3Interceptor

于是,做出如下更改:

public class ProgressRequestBody extends RequestBody {

//省略相关代码

@Override

public void writeTo(BufferedSink sink) throws IOException {

//如果调用方是OkHttp3Interceptor,直接写请求体,不再通过包装类来处理请求进度

if (sink.toString().contains(

“com.android.tools.profiler.support.network.HttpTracker$OutputStreamTracker”)) {

requestBody.writeTo(bufferedSink);

} else {

BufferedSink bufferedSink = Okio.buffer(sink(sink));

requestBody.writeTo(bufferedSink);

bufferedSink.colse();

}

}

}

你以为这样就完了?相信很多人都会用到

com.squareup.okhttp3:logging-interceptor日志拦截器,当你添加该日志拦截器后,再次上传文件,会发现,进度回调又执行了两遍,为啥?因为该日志拦截器,也会调用ProgressRequestBody#writeTo(BufferedSink)方法,看看代码:

//省略部分代码

class HttpLoggingInterceptor @JvmOverloads constructor(

private val logger: Logger = Logger.DEFAULT

) : Interceptor {

@Throws(IOException::class)

override fun intercept(chain: Interceptor.Chain): Response {

val request = chain.request()

val requestBody = request.body

if (logHeaders) {

if (!logBody || requestBody == null) {

logger.log(“–> END ${request.method}”)

} else if (bodyHasUnknownEncoding(request.headers)) {

logger.log(“–> END ${request.method} (encoded body omitted)”)

} else if (requestBody.isDuplex()) {

logger.log(“–> END ${request.method} (duplex request body omitted)”)

} else if (requestBody.isOneShot()) {

logger.log(“–> END ${request.method} (one-shot body omitted)”)

} else {

val buffer = Buffer()

//1、这里调用了RequestBody的writeTo方法,并传入了Buffer对象

requestBody.writeTo(buffer)

}

}

val response: Response

try {

response = chain.proceed(request)

} catch (e: Exception) {

throw e

}

return response

}

}

可以看到,HttpLoggingInterceptor内部也会调用RequestBody#writeTo方法,并传入Buffer对象,到这,我们就好办了,在ProgressRequestBody类增加一个Buffer的判断逻辑即可,如下:

public class ProgressRequestBody extends RequestBody {

//省略相关代码

@Override

public void writeTo(BufferedSink sink) throws IOException {

//如果调用方是OkHttp3Interceptor,或者传入的是Buffer对象,直接写请求体,不再通过包装类来处理请求进度

if (sink instanceof Buffer

|| sink.toString().contains(

“com.android.tools.profiler.support.network.HttpTracker$OutputStreamTracker”)) {

requestBody.writeTo(bufferedSink);

} else {

BufferedSink bufferedSink = Okio.buffer(sink(sink));

requestBody.writeTo(bufferedSink);

bufferedSink.colse();

}

}

}

这样就完了?也不见得,如果后续又遇到什么拦截器调用其writeTo方法,还是会出现进度回调执行两遍的情况,只能在遇到这种情况时,加入对应的判断逻辑

到这,也许有人会问,为啥不直接判断调用方是不是CallServerInterceptor,是的话监听进度回调,否则,直接写入请求体。想法很好,也是可行的,如下:

public class ProgressRequestBody extends RequestBody {

//省略相关代码

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

点击:《Android架构视频+BAT面试专题PDF+学习笔记​》

即可免费获取~

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

视频和PDF**(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

[外链图片转存中…(img-95yHhRjj-1710670309576)]

[外链图片转存中…(img-mPojDMna-1710670309576)]

[外链图片转存中…(img-adoM1a2p-1710670309577)]

点击:《Android架构视频+BAT面试专题PDF+学习笔记​》

即可免费获取~

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值