面灵AI→

精臣客户端一面:MVC/MVP/MVVM、协程与消息队列项目

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 个人介绍;技术栈偏 C++/后端,Android 从哪里学、实习前是否了解、哪部分最熟?
  2. MVC、MVP、MVVM 的差异;View 如何感知 ViewModel 的变化?
  3. WebView 的实现原理;追问动态代理及源码了解程度
  4. 你对 Kotlin 协程的理解?Android 上线程切换大致如何实现?
  5. 是否了解 Flutter、是否有兴趣加入 Flutter 混合项目?
  6. Protobuf 与 JSON 的区别;消息队列项目做什么、你做了什么?
  7. 精确匹配、广播和通配符订阅各是什么、怎么实现?项目特色与最大卡点是什么?
  8. 快手实习最大收获、最困难的事、压力和交付压力如何?
  9. 有什么想了解的问题?

《参考解析》

MVC/MVP/MVVM 的差异,核心是「谁持有谁的引用、谁负责更新视图」。 MVC 里 Controller 既处理输入又更新 View,View 与 Model 可能直接耦合,逻辑容易堆在 Activity/Fragment 里(Android 早期最常见的问题);MVP 让 Presenter 持有 View 接口,View 通过接口回调更新,问题是 Presenter 与 View 强耦合、需要写大量接口且生命周期处理繁琐;MVVM 用数据绑定 + 可观察数据解耦:ViewModel 暴露可观察状态(LiveData/StateFlow/ObservableField),View 订阅状态变化自动刷新,ViewModel 不持有 View 引用,便于单测。View 如何感知 ViewModel 变化:传统做法是观察者模式——ViewModel 持有 ObservableField/LiveData,View 侧 observe 并在回调里更新 UI;关键在于订阅要绑定生命周期(viewLifecycleOwner)避免泄漏与内存抖动。DataBinding/Compose 更进一步:编译器生成绑定代码(Compose 则是重组机制),状态变化直接触发最小范围的 UI 更新。

WebView 的底层是「Chromium 渲染引擎 + Java/Native 桥」。 它内部有独立的渲染进程(多进程架构)、网络栈、JS 引擎(V8)、以及一个 WebViewCore/AwContents 与 Chromium 交互;JS 与 Java 之间通过 addJavascriptInterface(有安全风险,4.2 前可反射调用任意方法)、evaluateJavascript、WebMessagePort 或 URL scheme 拦截通信。动态代理在 Android 里随处可见:Retrofit 用 Proxy.newProxyInstance 把接口方法调用转成 HTTP 请求(InvocationHandler 里解析注解、拼参数、发起请求),Binder 的 Proxy 也是同思路;底层原理是 JDK 在运行时生成一个实现目标接口的类,方法调用统一进 InvocationHandler.invoke。源码层面被追问时可以答:Proxy.newProxyInstance 会先找缓存里的代理类,没有就生成字节码(ProxyGenerator)并加载,代理类持有 InvocationHandler 引用,所以只能代理接口的 public 方法。

Kotlin 协程与线程切换。 协程是「用户态的轻量线程」,本质是编译器把 suspend 函数改写成状态机 + Continuation(挂起时保存执行点与局部变量,恢复时从断点继续);suspend 只是编译期标记,运行时表现为回调链,因此挂起不阻塞线程(这也是它比线程轻量的原因:几万个协程可以跑在少数线程上)。线程切换靠调度器:Dispatchers.Main 切到主线程(通过 Handler 投递到主线程 Looper)、Dispatchers.IO 用专用线程池(弹性、适合阻塞 IO)、Default 用 CPU 核数大小的池;withContext(Dispatchers.IO) 在挂起点把后续续体交给目标调度器执行,切回来同理。要补充的工程点:结构化并发(coroutineScope/supervisorScope 保证取消传播与异常处理)、viewModelScope 绑定生命周期自动取消、以及共享可变状态要用 Mutex/StateFlow 而不是裸变量。

Protobuf 与 JSON 的区别要落到「序列化与契约」上。 Protobuf 是二进制编码 + IDL 编译生成代码:更小(字段用 tag 编码,无冗余 key 名)、更快(解析无需文本扫描)、强类型且向前向后兼容(新增字段用新 tag,老客户端自动忽略未知字段);代价是可读性差(抓包要工具解)、需要 schema 与代码生成流程、以及变更字段号有严格约束(改 tag 号会破坏兼容)。JSON 是文本、可读、无 schema(或可选 schema)、跨语言零成本,但体积大、解析慢、没有强类型约束(字段名写错编译期发现不了)。选型:移动端与后端通信量大、对流量和性能敏感用 Protobuf;对外 API、调试友好、变动频繁用 JSON。消息队列项目的三个订阅模式:精确匹配是「topic 完全相等」(用哈希表 O(1) 查找);广播是发给所有订阅者(遍历订阅者列表,注意慢消费者会拖累整体,要配队列与背压);通配符订阅(如 a.*.c、a.#)需要前缀树/主题树做匹配——把 topic 按分隔符切段建 Trie,节点存订阅者,匹配时对 *(单层)和 #(多层)分别分支递归,这比逐个正则匹配快得多,也是主流 MQTT broker 的实现方式。项目卡点通常集中在「并发订阅/退订与匹配的线程安全」(用读写锁或不可变快照切换)和「慢消费者导致的内存堆积」(队列上限 + 丢弃策略 + 背压)。

「技术栈偏 C++ 为什么投客户端」这类问题的答法是给一条可信的迁移路径。 可以答:C++/后端的底子(内存模型、并发、协议与数据结构)在客户端是直接复用的,补的是 UI 生命周期与渲染框架;Android 的学习顺序是从「四大组件 + 生命周期」到「架构模式 + 网络/存储」再到「性能与稳定性治理」,并且能举出自己真实做过的东西(哪怕是小工具或开源项目)。面试官想确认的是「你不是随便投的」,所以要给出具体动作(读过哪些源码、做过什么 Demo、为什么对这个方向感兴趣),而不是只表达意愿。