上篇中级聊完Retrofit和Glide,这篇进入美团高级。高级面试考源码级理解——"OkHttp拦截器链怎么串?Okio的Segment池怎么复用?LeakCanary怎么检测泄漏?BlockCanary怎么定位卡顿?"
美团高级要求能读框架源码、做性能排查。面试官要你能讲清楚内部实现。
今天8道题覆盖美团高级核心考点。
Q1:OkHttp的拦截器链怎么工作?
核心:责任链模式。请求从第一个拦截器依次经过每个拦截器,响应原路返回。
默认顺序:RetryAndFollowUp(重试重定向)→ Bridge(桥接)→ Cache(缓存)→ Connect(连接)→ CallServer(发请求读响应)。
RealInterceptorChain:持有拦截器列表和index。每个拦截器调chain.proceed(request)创建新chain(index+1)传给下一个。
追问:自定义Interceptor加在addInterceptor和addNetworkInterceptor有什么区别?addInterceptor在RetryInterceptor之前执行(能修改缓存策略),addNetworkInterceptor在ConnectInterceptor之后执行(能拿到真实网络响应)。日志拦截器通常加addNetworkInterceptor。
Q2:OkHttp的连接池怎么管理?
ConnectionPool:管理TCP连接复用和回收。默认5个空闲连接keep-alive 5分钟。
连接复用:发请求先查连接池有没有可复用连接(同host端口协议)。有直接用(省TCP+TLS握手),没有新建。
空闲回收:后台线程定期清理超时连接。Deque存储,put/get时触发cleanup。
追问:OkHttp怎么判断连接能不能复用?检查地址(Address)是否匹配、Socket是否关闭、Http2Connection的stream数是否达到上限。HTTP/2可以多路复用——一个连接上同时发多个请求(stream)。HTTP/1.1一个连接同时只能处理一个请求。
Q3:Okio的Segment池怎么工作?
Segment:8KB的byte数组,Segment间用next指针组成链表。
SegmentPool:全局链表池,最大64KB(8个Segment)。需要内存时take,用完recycle。避免频繁GC。
Buffer:Okio核心缓冲区,内部Segment链表。写追加Segment到尾,读从头部取消费。
追问:Okio对Java IO的优化核心是Segment池复用内存和Source/Sink接口简化读写。OkHttp底层所有IO操作都用Okio。
Q4:LeakCanary怎么检测Activity内存泄漏?
原理:Activity destroy后应该被GC回收。如果没被回收就是泄漏。
检测流程:registerActivityLifecycleCallbacks监听生命周期 → Activity onDestroy时创建WeakReference关联ReferenceQueue → 5秒后检查是否被回收 → 没回收触发GC再查 → 还没回收dump hprof → Shark分析GC Roots引用链找泄漏路径。
结果展示:Debug包直接弹通知显示泄漏路径——"MainActivity泄漏,因为匿名Runnable持有Activity引用"。
追问:LeakCanary只在Debug包生效吗?是的。leakcanary-android只在debugImplementation依赖。Release包不打包LeakCanary代码(零开销)。但线上可以用LeakCanary的Shark库做轻量检测——只记录泄漏次数上报,不dump hprof。
Q5:BlockCanary怎么检测卡顿?
原理:Hook主线程Looper的Printer。Looper.loop()每次dispatchMessage前后会调Printer.println。BlockCanary替换Printer,记录dispatchMessage的开始和结束时间。
检测流程:
Looper.getMainLooper().setMessageLogging(customPrinter)替换Printer。- println被调两次——第一次是dispatchMessage开始,第二次是结束。
- 计算两次时间差,超过阈值(默认500ms)判定为卡顿。
- 卡顿时dump主线程堆栈(
Looper.getMainLooper().getThread().getStackTrace())。 - 展示卡顿堆栈——告诉你哪行代码执行太慢。
追问:BlockCanary和Systrace什么区别?BlockCanary线上可用(轻量级监控),Systrace线下分析工具(需要adb抓trace)。BlockCanary告诉你"卡在哪行代码",Systrace告诉你"卡在整个渲染流程的哪个阶段"。两者互补。
Q6:OkHttp的缓存策略怎么实现?
CacheInterceptor:基于HTTP标准缓存(Cache-Control/ETag/Last-Modified)。
流程:查CacheStrategy决定走缓存还是网络 → 走缓存直接返回 → 走网络带条件(If-None-Match/If-Modified-Since),304就用缓存 → 走网络无条件拿新Response存缓存。
Cache-Control:max-age=300缓存5分钟、no-cache每次验证、no-store不缓存。
追问:缓存存在cache目录下,文件名URL的MD5。DiskLruCache管理,LRU淘汰,默认50MB上限。
Q7:OkHttp的异步请求怎么实现?
AsyncCall:内部是Runnable。client.newCall(request).enqueue(callback)创建AsyncCall放入dispatcher的readyAsyncCalls队列。
Dispatcher:管理请求并发。maxRequests默认64,maxRequestsPerHost默认5。每发一个请求检查是否达到上限——没达到就提交到线程池执行,达到就排队等待。
执行流程:AsyncCall在线程池执行 → 走拦截器链 → 网络请求完成 → 回调callback.onResponse/onFailure(在OkHttp的工作线程,不在主线程)。Android里需要runOnUiThread切主线程更新UI。
追问:OkHttp的线程池怎么配置?Dispatcher.executorService默认是ThreadPoolExecutor(0, Integer.MAX_VALUE, 60s, SynchronousQueue)——核心线程0、最大不限、空闲60秒回收。适合短生命周期的大量并发请求。
Q8:源码阅读面试怎么展示?
读法:从入口跟(OkHttpClient.newCall → RealCall → 拦截器链),不逐行读。先画流程图理清主干,再深入感兴趣的拦截器。
面试展示:不要背"OkHttp有5个默认拦截器"。讲你读到的细节——"CacheInterceptor缓存判断逻辑200多行,核心是CacheStrategy根据HTTP规范决定走缓存还是网络"。
结合实际:讲读源码解决了什么问题——"读OkHttp源码发现ConnectInterceptor连接超时和读超时分开配置,我们读超时只设5秒太短,改15秒后超时率降80%"。
面试Tips:美团高级OkHttp考拦截器链(责任链模式)和连接池管理。Okio考Segment池复用和Buffer链表。LeakCanary考WeakReference+ReferenceQueue+hprof分析流程。BlockCanary考Hook Looper Printer。缓存考CacheStrategy和Cache-Control。读源码面试要展示发现而不是背书——讲你读到了什么细节、解决了什么问题。
下一篇进入美团资深——美团组件化方案、美团热修复、美团跨进程通信、多渠道打包。
读过OkHttp源码的同学评论区聊聊,你觉得哪个拦截器写得最精妙?
本系列连载中,关注不迷路,下一篇:美团资深Android工程师面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。