美团高级Android面经:OkHttp拦截器链、Okio源码、LeakCanary原理、BlockCanary

14 阅读5分钟

上篇中级聊完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的开始和结束时间。

检测流程

  1. Looper.getMainLooper().setMessageLogging(customPrinter)替换Printer。
  2. println被调两次——第一次是dispatchMessage开始,第二次是结束。
  3. 计算两次时间差,超过阈值(默认500ms)判定为卡顿。
  4. 卡顿时dump主线程堆栈(Looper.getMainLooper().getThread().getStackTrace())。
  5. 展示卡顿堆栈——告诉你哪行代码执行太慢。

追问: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-Controlmax-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。