Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

主流第三方库

中级面试常问“X 库的原理“,尤其网络层和图片库。你做 SDK 偏底层,这些上层库要补实战理解。

一、OkHttp

Android 网络底座(Retrofit 默认基于它)。

  • 核心:拦截器链(责任链模式)。请求依次经过拦截器:
    1. 应用拦截器(开发者添加,如加 header、log)。
    2. RetryAndFollowUp(重试、重定向)。
    3. Bridge(补全 header、gzip)。
    4. Cache(缓存)。
    5. Connect(建立连接)。
    6. 网络拦截器(开发者添加)。
    7. CallServer(真正发送收发)。
  • 连接池(ConnectionPool):复用 TCP/HTTP 连接,常见默认配置是最多空闲连接约 5 个、保活约 5 分钟,但这是 OkHttp 版本/构造参数相关的可配置默认值。复用可减少 TCP/TLS 握手;HTTP/2 下同一连接还能多路复用多个 stream。
  • 缓存:基于 HTTP 缓存语义(Cache-Control、ETag),磁盘 LRU。
  • 分发器(Dispatcher):异步请求用线程池执行,控制最大并发和单 host 并发;常见默认值是 maxRequests=64maxRequestsPerHost=5,可通过属性调整。WebSocket 等特殊连接是否计入限制要看版本文档。

OkHttp 请求链路与调优

机制解决什么问题面试追问
Dispatcher控制异步 Call 何时进入执行队列,避免无限并发打爆线程/服务端并发不是越大越好,要结合服务端限流、HTTP/2、业务优先级调整。
ConnectionPool复用连接,减少 TCP/TLS 握手和慢启动空闲连接会占 fd/内存;移动端网络切换时要能重建连接。
HTTP/2 multiplexing一个 TCP 连接上并发多个 stream,减少同域名多连接开销仍受服务端支持、流控、丢包影响;TCP 层队头阻塞没有完全消失。
Cache利用 HTTP 语义减少网络请求只对可缓存响应生效;动态接口要和服务端协商 Cache-Control/ETag。
val client = OkHttpClient.Builder()
    .dispatcher(Dispatcher().apply {
        maxRequests = 32
        maxRequestsPerHost = 8
    })
    .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
    .build()

上面数字不是“最佳实践模板”,只是说明默认项可配置。真实项目要根据接口耗时、弱网、服务端限流、HTTP/2 支持和页面优先级做压测。

二、Retrofit

把 HTTP API 封装成 Kotlin/Java 接口。

  • 核心:动态代理(Proxy.newProxyInstance)create() 为接口生成代理对象,调用方法时被 InvocationHandler 拦截。
  • 解析方法上的注解(@GET/@POST/@Query/@Body)生成 ServiceMethod/RequestFactory,构造 OkHttp 的 Request。
  • CallAdapter:适配返回类型(Call、协程 suspend、Flow、RxJava)。协程版可理解为把 OkHttp Call 的异步回调桥接为挂起恢复,取消协程时也要取消底层 Call。
  • Converter:序列化转换(Gson/Moshi/kotlinx.serialization),负责请求体/响应体和业务对象之间的转换。
  • 协程用法:接口方法加 suspend 直接返回 data class,无需 Call.enqueue。

Retrofit 三层扩展点

扩展点负责内容常见坑
注解解析URL、HTTP method、Query/Header/Body 参数动态 URL、编码、空参数要按接口契约处理。
ConverterJSON/XML/Proto 与对象互转Kotlin 非空/默认值、混淆字段名、泛型类型擦除。
CallAdapterCall<T> 适配成 suspend/RxJava/Result 包装错误处理边界要统一:HTTP error、网络异常、解析异常不要混在一起。

面试回答结构:先说“接口不是实现类,Retrofit 用动态代理拦截方法调用”;再说“注解生成请求,Converter 负责数据转换,CallAdapter 负责返回类型”;最后补“底层网络仍交给 OkHttp”。

三、图片库(Glide / Coil)

  • 三级缓存:内存(ActiveResources 弱引用 + LruCache)→ 磁盘(DiskLruCache)→ 网络。
  • 生命周期绑定:Glide 官方 API 强调传入 Activity/Fragment 后会随宿主销毁自动清理请求。实现层面常见解释是注入无 UI 的 RequestManagerFragment/等价生命周期组件监听宿主生命周期;这是库内部实现,版本可能演进,面试要说“实现层面”。Coil 基于协程 + Lifecycle,更贴近 Kotlin 项目。
  • Bitmap 复用(BitmapPool):复用可复用的 Bitmap 内存,减少频繁分配和 GC;硬件位图、尺寸/配置不匹配时不一定能复用。
  • Coil:Kotlin 优先、协程实现、API 轻量,Compose 场景友好;Glide 生态成熟、缓存/变换/集成丰富,老项目更多。
  • 优化点:指定尺寸(override)避免加载过大图、合适的缓存策略、占位图。

图片加载机制拆解

机制Glide 侧重点Coil 对照注意点
生命周期Glide.with(activity/fragment) 绑定宿主,停止/销毁时暂停或清理请求ImageLoader + coroutine/lifecycle,Compose 集成自然不要在长生命周期 context 上加载短生命周期 View。
缓存 key通常由 model、尺寸、变换、选项、签名等共同决定同样需要区分 data/size/transformation“同 URL”不代表同一缓存条目,尺寸和变换会影响命中。
内存缓存ActiveResources + MemoryCachememoryCache内存命中快,但占用大;列表页要控制目标尺寸。
磁盘缓存原始数据/转换后资源策略可配diskCache磁盘命中省网络但有 IO;策略按图片来源选择。
BitmapPool复用 Bitmap 内存降低 GCbitmap pool/解码组件实现不同高版本硬件位图不可随意复用/修改。

RecyclerView 中的核心不是“手动取消所有请求”,而是每次绑定都给可复用 View 发起新请求或显式 clear。Glide 文档也提示,如果某个复用 View 不再加载图片,要 clear() 避免旧请求回调把旧图设置回来。

四、序列化库

  • Gson:反射解析,易用但运行时反射有性能开销、不感知 Kotlin 默认值/空安全(可能给非空字段塞 null)。
  • Moshi:Square 出品,支持 codegen(编译期生成 adapter,无反射),对 Kotlin 友好。
  • kotlinx.serialization:Kotlin 官方,编译期插件生成,类型安全、多平台、无反射,新项目推荐。@Serializable 注解。
机制适合场景面试注意
Gson运行时反射为主老项目、简单 Java modelKotlin 非空/默认值坑多,混淆要保留字段/注解。
Moshi反射或 codegen adapterKotlin + Square 生态codegen 性能和类型安全更好,但要配置 KSP/KAPT。
kotlinx.serialization编译期插件生成 serializerKotlin/多平台/Compose 新项目需要 @Serializable,与 Retrofit 配合要加 converter。

五、其他常见库

  • EventBus:发布订阅解耦组件通信。现代趋势用 Flow/SharedFlow 替代(类型安全、可控生命周期)。
  • 依赖注入:Hilt(主流,见 07)、Koin(纯 Kotlin、运行时解析、轻量但无编译期校验)。DI 的核心价值不是“少写 new”,而是把对象创建、作用域和替换点显式化,方便测试和模块解耦。
  • 协程之外的响应式:RxJava(老项目多,操作符丰富但学习曲线陡),新项目协程 + Flow 已基本替代。
模式/库解决的问题现代替代/边界
EventBus跨组件发布订阅,调用方不直接依赖接收方容易隐藏数据流、难追踪生命周期;页面内优先 LiveData/StateFlow/SharedFlow,跨模块事件要定义清晰契约。
Hilt编译期 DI、作用域管理、Android 组件注入适合中大型项目;需要理解 Component/Scope,不要滥用单例。
KoinDSL 声明依赖,上手快运行时解析,错误可能到运行时才暴露;适合小项目或快速迭代。
RxJava复杂异步流组合老项目维护常见;新代码可优先协程 Flow,但迁移要逐步做。

高频面试题

Q1:OkHttp 的拦截器链是什么设计模式?顺序? 责任链模式。顺序:应用拦截器 → RetryAndFollowUp → Bridge → Cache → Connect → 网络拦截器 → CallServer。每个拦截器处理后调 chain.proceed 传递。

Q2:应用拦截器和网络拦截器区别? 应用拦截器在最外层,只调用一次,能看到原始请求(重定向前),不一定有网络;网络拦截器在 Connect 之后,可能多次调用(重定向/重试),能看到真实网络请求和中间响应。

Q3:Retrofit 的核心原理? 动态代理。create() 用 Proxy 为接口生成代理对象,调用方法时 InvocationHandler 拦截,解析注解构造 OkHttp Request,经 CallAdapter 适配返回类型(协程/Call/Flow),Converter 做序列化。

Q4:Retrofit 怎么支持协程的? 方法加 suspend 后,Retrofit 内部把 OkHttp Call 的异步回调适配成协程挂起恢复,常见实现会通过类似 suspendCancellableCoroutine 的方式处理取消,直接返回结果或 Response,无需手动 enqueue。

Q5:Glide 如何感知生命周期防止泄漏? Glide.with(activity/fragment) 返回与宿主生命周期绑定的 RequestManager,宿主停止/销毁时可暂停或清理请求。实现层面常见解释是注入无 UI 的 RequestManagerFragment/生命周期组件监听回调,但这是库内部实现细节,回答时不要说成永久 API 契约。

Q6:Glide 三级缓存? 内存(活动资源弱引用 + LruCache)、磁盘(DiskLruCache,可缓存原图或转换后)、网络。命中内存最快,逐级回退。

Q7:Gson 在 Kotlin 里有什么坑? Gson 用反射、不走构造函数,可能绕过 Kotlin 的非空检查和默认值,给非空字段赋 null 导致后续 NPE。建议用 Moshi 或 kotlinx.serialization。