}
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移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
点击:《Android架构视频+BAT面试专题PDF+学习笔记》
即可免费获取~
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
视频和PDF**(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
[外链图片转存中…(img-95yHhRjj-1710670309576)]
[外链图片转存中…(img-mPojDMna-1710670309576)]
[外链图片转存中…(img-adoM1a2p-1710670309577)]
点击:《Android架构视频+BAT面试专题PDF+学习笔记》
即可免费获取~
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。