
挂掉 B 站 Android 面试之后我花了两天时间把一二面的细节全部复盘了一遍。说实话走出这场面试的瞬间很挫败但冷静下来之后我反而觉得这两轮面试的含金量非常高——B 站绝对不是那种靠背八股文就能过关的地方它对基础扎实度、项目深挖能力和问题拆解思维的要求比我在准备时预估的要高出一截。这篇文章不是单纯记录问了什么而是想把每道题背后的考察意图、我当时为什么答崩了、正确的回答思路应该是什么都拆开揉碎了讲清楚。如果你也在准备中大厂的 Android 岗位希望能帮你避开我踩过的坑。1. 为什么说 B 站一面是基础面试的标准模板先聊结论B 站一面的题目范围不算偏几乎没有上来就怼冷门源码的操作它更看重的是你对核心知识有没有形成体系化的理解而不是记住了多少孤立的知识点。但它的问题设计得很聪明几乎每一道题都会留一个再往深处问一层的口子。你如果只在第一层回答面试官会顺着往下追问直到你暴露真实的理解边界为止。我印象最深的一个感受是B 站面试官提问时非常喜欢基于场景来展开。比如同样问 Handler它不是直接问Handler 的原理是什么而是给了一个具体场景让你分析。这就要求你不仅要背出主线程 Looper 循环 MessageQueue 队列 Handler 发送消息这套骨架还要能灵活拆解消息延迟、同步屏障、内存泄漏这类衍生问题。这种问法跟很多公司直接丢八股文的风格完全不同它更接近实战中排查问题时的思维方式。一面通常控制在 40~60 分钟前 20 分钟会重点围绕 Java/Kotlin 基础、并发、Android 四大组件展开中间 15 分钟会切入自定义 View、绘制流程、事件分发这些 UI 核心内容最后留出时间考察你用过的框架和简单算法。整体节奏其实是偏快的如果前面的题答得模糊面试官会迅速调整话题方向后面能展示自己的机会就会变少。我在一面中总结出的核心经验是基础题不是看你背了多少而是看你有没有真正理解为什么这样设计。比如问到 View 的绘制流程时如果只答出测量、布局、绘制三个步骤面试官大概率会追问measure 过程中 MeasureSpec 是怎么传递的为什么 onMeasure 可能被调用多次自定义 View 里 wrap_content 为什么需要自己处理。这些问题没有真正写过自定义 View、没有读过源码的人很难流畅回答。另外一点比较重要的是一面的最后通常会有手写算法题B 站比较常见的是链表操作、数组遍历、二分查找这类中等偏简单的题目。它考察的核心不是你刷了多少 LeetCode而是你在实战环境下思路是否清晰、边界处理是否周全。我面的那道题是链表相关的难度不大但因为前面基础部分被追问得有点慌写代码时第一版没处理好空指针边界面试官在旁边提醒了一句我才补上。这里想提醒大家基础概念的压迫感会直接影响你后面写算法题的心态一面前半段的节奏一定要稳住。我当时就是觉得这题我肯定能答上而产生的轻敌心理结果被连续追问之后整个人的状态开始发飘。2. 一面实录那些看似基础却最见功底的问题2.1 开场定基调自我介绍就决定了面试官的追问方向B 站的自我介绍环节很短面试官听你说完后会直接挑介绍里提到的一个点开启追问。我当时把简历里基于 RecyclerView 实现的复杂列表页性能优化拿出来说了面试官立刻追问了 RecyclerView 的缓存机制细节。这里有个很多人都会踩的坑自我介绍不要贪多挑一两个自己真正吃透的项目点去讲给自己搭一个可控的舞台。如果你把七八个技术点全倒出去面试官只会挑你最薄弱的那个方向去深挖反而容易翻车。我建议自我介绍用三段式结构第一段说清楚你是谁、几年经验、主要做什么方向第二段挑一个最有代表性的项目概括业务背景和你的核心贡献第三段抛出自己最擅长的一两个技术关键词把面试官的注意力引导到你准备好的方向上。这样既显得你有总结能力也给后面的问答留出了主动权。2.2 并发与 Java 基础volatile、synchronized、线程池一个都没少B 站一面在 Java/Kotlin 并发这块问得很细我遇到的几个典型问题包括volatile 的可见性和禁止指令重排底层靠什么实现synchronized 在对象头里是怎么做锁升级的线程池的 corePoolSize 和 maximumPoolSize 之间的调度逻辑以及 ConcurrentHashMap 在 Java 8 里为什么放弃分段锁而改用 CAS synchronized。先说 volatile。我当时答了保证可见性防止指令重排序但被追问JMM 底层怎么保证的时候就有点含糊了。正确的回答链路应该是volatile 修饰的变量在写操作时会生成一个带有 lock 前缀的汇编指令这个指令会触发缓存一致性协议比如 MESI让其他 CPU 核心里对应的缓存行失效同时通过内存屏障禁止前后指令重排序。面试官真正想听的是你能从字节码、汇编、CPU 缓存这几个层面去理解它而不是停留在面试题标准答案的层面。synchronized 锁升级是我二面前自己补了源码才搞清楚的。它的完整链路是偏向锁 → 轻量级锁自旋锁、自适应自旋→ 重量级锁。无竞争时对象头里的 Mark Word 记录线程 ID这个线程再次进入同步块时无需任何 CAS 操作一旦有其他线程竞争就撤销偏向锁升级为轻量级锁通过 CAS 把 Mark Word 拷贝到栈帧的 Lock Record 中如果 CAS 失败且自旋达到阈值就膨胀为重量级锁依赖操作系统 mutex 实现阻塞和唤醒。这些细节不读 OpenJDK 源码和 HotSpot 的实现光靠背是背不下来的。ConcurrentHashMap 的演进也是一个高频考点。Java 7 的 Segment 继承自 ReentrantLock本质上是一个锁分解的思路Java 8 改成了数组 链表/红黑树 CAS synchronized的结构锁粒度细到单个桶只在哈希冲突链表的头节点加 synchronized这样并发度更高而且在链表转红黑树的过程中保证线程安全。面试官如果追问为什么红黑树阈值是 8你可以从泊松分布的角度解释在负载因子为 0.75 的情况下链表中节点数量为 8 的概率已经低至千万分之一超过 8 说明哈希分布异常这时用红黑树来对冲极端情况下的查询退化。2.3 Android 基础几乎每个问题都在往底层戳一面问 Android 基础的方式还挺有代表性的它不会只问Activity 的启动模式有哪些而是会让你结合具体场景去选型。比如 standard、singleTop、singleTask、singleInstance 各自的适用场景以及 launchMode 和 Intent Flag 同时设置时谁优先——实际规则是 Intent Flag 的优先级高于 manifest 中的 launchMode。还有 taskAffinity 对 singleTask 启动的影响以及 startActivity 时系统如何通过 ActivityTaskManager 找到合适的 Task。这些点不读 ActivityTaskManager 相关源码很难答得完整。再比如 Activity 的启动流程正确回答的链路大致是startActivity → ActivityTaskManagerService → 进程通信Binder→ zygote 进程 fork 出应用进程如果目标应用没有启动→ ActivityThread.main → attach → 创建 Application → 创建并启动 Activity → 执行 onCreate。这里面有一个很关键的细节app 进程的创建请求是怎么通过 Binder 传给 zygote 的中间经历了 system_server 的哪些调度。我当时能说出大部分流程但 fork 进程具体是由 system_server 里面的哪块逻辑触发的讲得比较模糊。这块硬伤其实是可以提前补的比如 ActivityManagerService 内部会通过 ProcessList.startProcessLocked 方法向 zygote 发送创建进程的 socket 消息而 ZygoteProcess.attemptUsapZygote 或 zygoteSendArgsAndGetResult 负责具体的通信过程。View 绘制和事件分发在 B 站一面的出现频率也很高。我遇到的题是自定义 View 的 onDraw 高频刷新怎么优化这其实是个开放题可以从避免在 onDraw 里创建对象、使用 HardwareLayer、脏区域重绘、避免过度绘制这几个方向去展开。但面试官追问了一句invalidate 和 postInvalidate 的区别我答了前者在 UI 线程调用后者可以在子线程调用他又追问为什么 postInvalidate 可以跨线程这里就需要答出 ViewRootImpl 里的 ViewRootHandler 和 Choreographer 的调度机制了。其实 postInvalidate 最终会通过 ViewRootImpl 的 handler 切换到 UI 线程再触发 performTraversals所以本质上还是在主线程完成重绘。2.4 自定义 View 与事件分发考验的不只是背流程B 站一面在自定义 View 这块的考察深度是超出我预期的。刚开始只是让我简述 measure → layout → draw 的流程这部分我答得还算顺但当面试官抛出一个实际场景——一个横向滑动的卡片列表卡片内部还有一个纵向滚动的区域你怎么处理嵌套滚动冲突我就明显感觉自己的逻辑开始混乱了。这个问题的本质是事件分发的拦截逻辑正确处理方式通常有两种外部拦截法在父 View 的 onInterceptTouchEvent 里根据滑动方向决定是否拦截和内部拦截法通过 requestDisallowInterceptTouchEvent 请求父 View 不要拦截。但更进阶的回答还需要补充嵌套滚动机制 NestedScrollingParent/NestedScrollingChild 的存在——Android 5.0 之后提供了更优雅的联动方案CoordinatorLayout AppBarLayout 就是典型实现。另外面试官还问到了requestLayout 和 invalidate 的区别。这个问题很多人能答出invalidate 只重绘、requestLayout 会重新测量布局但要答得出彩需要补充说明 requestLayout 会向上递归调用 parent.requestLayout最终触发 ViewRootImpl.performTraversals 走完整的 measure、layout、draw而 invalidate 只是把当前 View 标记为脏区域等待下一个垂直同步信号来临时执行 draw。更进一步可以提到如果连续调用 requestLayout 和 invalidate在同一个 ViewRootImpl 的消息循环里其实会合并成一次绘制系统做了这类优化。我当时答到了这个层面看到面试官稍微点了点头这也是我在一面里少有的几个高光时刻。2.5 Kotlin 协程如果简历写了就必须能打B 站现在 Android 端已经全面转向 Kotlin协程基本属于必问范围。我面的问题是协程的挂起和恢复是怎么实现的withContext 和 launch 的区别协程的异常传播机制是什么样的。协程挂起的底层原理最核心的概念是 CPSContinuation Passing Style。Kotlin 编译器会把挂起函数改造成一个带 Continuation 参数的状态机函数每个挂起点对应一个状态分支。当协程调用一个挂起函数时如果结果没有准备好会直接 return 一个 COROUTINE_SUSPENDED 标记同时返回一个 Continuation 给调用方当后台任务完成后会调用这个 Continuation 来恢复执行实际上就是重新调用那个状态机函数并切换到下一个状态分支。这里面还牵扯到拦截器ContinuationInterceptor、协程调度器Dispatchers.Main、IO、Default的工作原理。我面的时候把 CPS 和状态机解释清楚了但 StrictMode 检测主线程卡顿和协程内部怎么联动这块没有讲得特别透。协程的异常传播也很重要。launch 启动的协程异常默认会往父协程传递一层层取消父协程并启动异常处理器而 async 的异常则默认是被包住的等待 await 调用时才重新抛出。如果希望某个协程的异常不影响父协程可以用 SupervisorJob 或 supervisorScope。B 站这类带有大量并发请求的应用里协程的异常隔离策略直接关系到线上稳定性所以面试官问这个问题背后其实是在考察你有没有应对单点故障引发链路崩溃的意识。2.6 算法题难度不大但边界处理不能翻车一面手写算法题我遇到的是反转链表中第 m 到第 n 个节点这题LeetCode 92 的变体。这题考察的核心是指针操作熟练度和边界处理。我当时想到的思路是穿针引线先找到第 m 个节点的前驱然后用头插法逐个把第 m1 到第 n 个节点插到前驱后面。实现的时候需要非常小心要提前保存 pre 节点、反转区间的第一个节点、以及反转区间的最后一个节点指向的后继节点。我当时在 pre 为 nullm1的情况下处理得不够干净面试官提醒我可以用一个 dummy 节点来统一处理避免 pre 为空的分支。这是非常典型的一类提醒——面试官其实在考察你写代码时有没有防御式编程的习惯而不只是看最终结果对不对。一面结束后面试官让我在反问环节提问。我建议大家在这个环节多问与团队技术栈、项目挑战相关的问题比如团队目前在播放页性能优化上遇到的主要瓶颈是什么这会让面试官觉得你有实际的项目洞察而不是只关心薪资和加班。3. 二面全记录简历项目被彻底撕开问题一个比一个锋利二面通常是一面通过后一两周内约到的面试官一般是团队里的技术 Leader 或者资深工程师。如果说一面考的是知识的广度与纵向延伸二面就是在考 你在真实项目里到底是怎么做技术决策的。我最大的感受是二面最需要的不是会而是会讲。同样一个项目你能否把背景、目标、方案选型、数据验证、踩坑回滚讲得清清楚楚几乎直接决定了你能不能过。3.1 第一个暴击项目里的图片加载优化被追问到体无完肤我简历里有这么一条主导了 XX 模块的图片加载性能优化将大图加载耗时降低了约 40%内存峰值降低约 30%。这句话我在一面已经讲过一遍二面一开始面试官就顺着它追问。他问的第一个问题还算温和你是怎么定位大图加载耗时的问题的我答了用线上监控和帧率掉帧采集发现列表快速滑动时光标卡顿进一步抓 trace 后发现图片解码耗时占比较高。他接着问你有没有对比过不同图片格式的解码速度WebP、HEIC、AVIF 在 Android 上的支持程度和解码性能分别是怎样的到这里我已经感觉有点吃力了。我对 WebP 有了解但 HEIC 和 AVIF 在 Android 各版本上的兼容性细节并没有背过很深。他继续追问Glide 默认的 Downsampler 是怎么处理采样率的BitmapFactory 的 inSampleSize 和 inJustDecodeBounds 是怎么配合工作的如果你用 Glide 加载一张 4096×4096 的图片它会怎么计算采样比例再往下他问到了 Glide 的缓存策略在 LruCache内存缓存和 DiskLruCache磁盘缓存中分别缓存的是什么格式的数据ActiveResources 在其中扮演什么角色以及 View 层出了 recycle 机制之后Glide 怎么知道自己该用哪个缓存副本。这个连环追问的残忍之处在于如果你只是会用Glide是绝对答不完整的。我硬着头皮答到了 inSampleSize 是二次幂的取整逻辑和 ActiveResources 缓存正在被使用的资源但明显感觉到自己对 Glide 源码的掌握只停留在面试速成的深度没有真正形成体系。事后复盘我总结出对简历里写的技术点至少要准备到你从这个方案的实现原理对比过哪些替代方案最后为什么选它这一层否则二面根本扛不住。3.2 播放器与视频业务场景B 站的 Android 面试躲不开这道坎B 站是视频平台所以 Android 面试里播放器相关的问题几乎是必考的。二面直接抛出一个场景题如果页面上正在播放一个视频用户点击评论区的一条评论页面需要跳转到另一个详情页并开始播放对应的视频你会怎么设计秒开方案。这个问题其实考验的是一个技术方案设计的综合能力不是单点知识。我当时回答的思路是提前在列表页做预加载把待播放视频的关键帧或切片数据缓存到本地在跳转前把播放器实例传递到新页面直接复用。但面试官追问如果你的方案导致多个视频流同时预加载带宽和内存怎么控制你有没有想过使用 ExoPlayer 的 MediaSource 拼接能力你了解播放器从网络拉流到首帧渲染全链路中最耗时的是哪一段吗这里我反思一下正确答案应该拆解为几步第一网络预连接在用户可能点击之前就建立 HTTP 连接、做 DNS 缓存第二视频关键信息时长、分辨率、首帧 offset通过接口提前下发在页面路由阶段就提前初始化好 ExoPlayer 的 MediaSource第三如果目标视频与当前视频是同一个渲染 View 容器可以直接复用播放器实例通过 setMediaItem 无缝切流第四在极端弱网情况下优先展示首帧图占位同时用音频流优先加载的策略降低黑屏等待感。这个追问暴露出的另一个问题是我对首帧渲染耗时的构成没有清晰的量化概念。播放器首帧耗时通常由 DNS 解析、TCP 连接、TLS 握手、HTTP 请求、缓冲读取、封包解析、音视频解码、渲染输出这几段组成。最容易优化的是网络部分比如预连接和预测加载最难优化的是解码器初始化尤其是第一次创建 MediaCodec 时的耗时通常要提前创建并复用。面试官问的这个场景其实距离 B 站播放页的真实业务非常贴近有相关经验的人会回答得更有底气。3.3 动态化方案与架构设计跨端技术选型背后的权衡二面还问到了一次架构层面的东西。面试官问我如果产品希望在一个新业务里同时支持 Android 和 iOS而且希望部分页面能像网页一样热更新你会怎么选型这其实是在考察你的跨端技术视野和对H5、原生、动态化三者边界的理解。我当时的回答是优先用原生 H5 的混合方案把通用性要求高、变更频繁的页面用 H5 承载对性能和体验要求高的核心链路用原生开发同时引入一套类似 TTSTitian Template Service这样的模板化方案让客户端可以通过下发模板动态渲染简单的 UI 页面。面试官点了点头但继续追问你觉得这套模板化方案和 Flutter 的跨端能力相比在 B 站这种重视频、重交互的场景下各自的优缺点是什么这个问题其实极具业务背景。B 站有大量复杂的交互页面播放页、动态流、直播弹幕需要跟播放器、长连接、高性能列表深度配合这种情况下纯 Flutter 可能面临与原生播放器桥接的成本而模板化方案则很难承载极端复杂的 UI 逻辑。但如果是一个以图文为主的轻内容社区首页Flutter 的跨端一致性优势和性能优势就会很明显。这种问题的关键不是给出唯一答案而是展示你能够在多个维度上做权衡——开发效率、动态性、性能、包体积、团队人力结构——并且有判断依据。3.4 系统底层与外延Binder、稳定性治理与性能工具链二面还问了 Binder 通信机制。这个问题几乎是大厂 Android 高级岗必问B 站自然也逃不掉。我的回答从 Linux 内核的进程隔离讲到 mmap 映射、内核缓冲区与用户缓冲区的唯一一次拷贝再到 Binder 驱动里 binder_transaction 的数据结构以及 BC_TRANSACTION 和 BR_TRANSACTION 的命令码流转。但面试官追问了一个我准备不够充分的角度如果要在同一个进程内频繁进行跨模块通信你会选择 Binder 还是其他方式为什么这个问题的重点在于理解 Binder 的性能开销和适用边界Binder 虽然比 Socket 高效但依然涉及内核态和用户态的切换如果完全不需要跨进程仅仅是在模块间解耦用事件总线比如 FlowSharedFlow 或者 Room 的跨层通信方案会是更轻量的选择。稳定性治理这块也值得一提。面试官问我项目里出现了一次只有线上用户偶发触发的崩溃你怎么定位我原本以为会问崩溃日志怎么收集、怎么符号化结果他直接抛出了一个场景logcat 里只有一段OutOfMemoryError: Failed to allocate a 33554432 byte allocation with 20971520 free bytes and 2MB until OOM你怎么判断是内存泄漏还是内存抖动正确回答要分两步第一步看是大对象分配还是持续增长型泄漏。32MB 的分配请求在剩余 20MB 的情况下失败说明当前堆碎片化严重或可用内存紧张接下来需要抓 Heap Dump 分析第二步用 ProfilerMemory Profiler 或 Perfetto统计分配曲线如果曲线呈锯齿状大概率是短时间内频繁创建大对象引发的内存抖动如果曲线呈阶梯状向上就要考虑是否静态持有 Activity 导致泄漏。为了定位具体泄漏点可以结合 LeakCanary 的 leak trace 和 hprof 文件里最短引用路径来分析。除了这些还可以补充我实际用过的方案在线上灰度阶段接入了 Memory Profiler 的周期采样配合自定义的 Hprof 分析器把大对象分配的调用栈采集下来。4. 复盘结论挂掉的原因不是题没背熟而是三个地方没对齐两轮面试结束之后我花了很长时间去做复盘想把失败的原因真正找一个确定性的答案。反复推演之后我认为问题的核心既不是八股文基础不够也不是算法题挂掉而是下面这三个没对齐。4.1 深度没到底知道是什么但没吃透为什么一面和二面里很多题我都能给出一个正确的开头但一旦面试官往深处追问我的知识体系就没有支撑了。比如我知道 Handler 是用来切换线程的但说不清楚它的同步屏障是怎么与 Choreographer 协作的我知道 Glide 会做采样压缩但 onDownsample 方法内部不同格式的采样策略差异没有真正研究过我知道 Binder 效率高但 binder_thread_read 在内核里是怎么阻塞调度和唤醒等待的讲得含混不清。这种半吊子状态比完全不会更危险因为它会让面试官觉得你有一定基础但缺乏真正深入做过事的人应有的扎实度。正确的应对方式是对简历上写到的技术点都要建立一个三层理解框架——用法层、原理层、源码层。比如写熟悉 Glide就不能只知道怎么用还要知道它的生命周期绑定、缓存层级、Bitmap 池、加载链路再进一步应该自己拉一遍 Glide 源码看 RequestBuilder、Engine、DecodeJob 这几个核心类的协作关系。没有这个深度的面试准备在大厂二面这种连续追问到墙角的风格下非常吃亏。4.2 项目讲法错配在讲功能开发而不是讲工程决策二面最让我意识到的一点是我和面试官看待项目的视角完全不一样。我习惯把项目讲成我做了什么功能、用了什么技术但面试官想听的是你在一个充满约束和不确定性的环境里是怎么做判断和取舍的。他关心的不是你用了 LruCache 磁盘缓存而是你为什么觉得这个方案比那个方案好你做了哪些实验来验证方案有效如果数据量翻十倍你的设计还成立吗。当项目介绍里缺乏置信度数据和竞品方案对比时面试官就很难判断你的技术实力到底处于什么层次。这个问题我建议用一套四段式去重构简历里的每一个项目业务背景与目标、技术方案选型与对比、落地过程中遇到的问题、数据验证与复盘思考。尤其是第四点很多面试者根本不会讲我怎么判断这次的优化是成功的如果指标定义不清整个项目就显得没有闭环。我在 B 站的二面中就是因为说不清 30% 的内存下降是怎么计算口径的显得像估算值而不是实测值这对可信度的打击很大。4.3 对 B 站业务与 Android 技术栈的结合点准备严重不足这是我在复盘后最遗憾的部分。B 站的业务特点极其鲜明——视频播放、弹幕渲染、社区评论、直播、动态流——任何一个方向都可以在面试中变成深挖的切入点。而我在准备时虽然刷了大量通用 Android 面试题却几乎没有做如果面试官问起视频播放器的首帧优化、弹幕超高并发渲染、UP 主投稿大文件上传这类业务向问题的预案。弹幕渲染就是一个非常典型的 B 站特色问题。它涉及自定义 View 的频繁绘制优化、对象池复用、时间轴切片、字体渲染缓存、多行避让算法等多项技术点。如果面试者在这些方向上能展示出对为什么要在 native 层做弹幕渲染的理解——因为 Java 层频繁创建 TextView 会引发大量 GC 和性能问题——面试官对你的业务匹配度会大幅提升。另外一个很容易被忽略的点是B 站的 Android 端大量使用了自研或者深度定制的组件比如播放器内核、网络库、数据库缓存层。面试前应该去详细研究 B 站开源的一些项目了解它在哪块技术栈上有深度的自研积累。这不仅是面试准备的策略更是检验你和团队技术氛围是否匹配的方式。5. 给后来者的备考清单源码、性能、项目语言三维度经历这场凉经后我把自己的备考方法论重新梳理了一遍形成了一套面向大厂视频类 App Android 岗位的复习框架分享出来供大家参考。这套框架不是说背完就能过但至少能保证你在面试官层层递进的追问下不会迅速掉到底。5.1 必啃源码清单与阅读方法我按重要程度排了一个源码清单面试前建议至少精读前三项Handler Looper MessageQueue重点理解 MessageQueue 的同步屏障和 IdleHandler 机制、Choreographer 如何驱动 UI 绘制、epoll 机制对消息队列空闲时的阻塞与唤醒。Activity 启动流程从 startActivity 到 ActivityRecord 创建、Task 调度、进程创建、ActivityThread 初始化完整链路至少能画出业务时序并解释每一环的职责。Binder 通信机制重点理解 mmap 原理、内核缓冲区与用户缓冲区的数据拷贝过程、binder_thread_read 的阻塞与唤醒最好能对比 Binder 与共享内存、Socket 的性能差异。Glide 的加载链路RequestManager → RequestBuilder → Engine → DecodeJob → BitmapPool → Resource 缓存的完整调用链尤其注意磁盘缓存和内存缓存的 key 规则与 Bitmap 复用机制。View 绘制流程ViewRootImpl.performTraversals 的触发条件、MeasureSpec 的生成规则、DecorView 与 ContentParent 的关系、硬件渲染与软件绘制的分界点。阅读源码时不要逐行读采用两个策略一是用问题驱动去读比如读 Handler 之前先问自己为什么子线程不能用 new Handler 更新 UI系统是怎么检测的读完代码后能自己解释清楚二是画调用链时序图把关键类的关键方法名和返回值记下来。光在博客上看别人的源码分析是不够的必须自己翻过源码面试时被追问细节才有底气。5.2 性能优化与稳定性治理的标准姿势视频类 App 对性能优化的重视程度非常高。B 站面试官问到的场景几乎都与卡顿、掉帧、内存、网络相关所以我建议把下面三套优化方法论练熟。内存优化方面要能从内存泄漏、内存抖动、内存溢出三个维度去讲泄漏的排查工具LeakCanary、Memory Profiler、 MAT、抖动与分配曲线的判别方法、避免过度创建对象的手段对象池、合理使用 StringBuilder、避免在循环里创建匿名内部类。同时要理解 Bitmap 内存模型的演进从 Java heap 到 Native heap以及 Android 8.0 之后 Bitmap 像素数据为什么放在 native 层。卡顿优化方面要能解释 Choreographer 的帧回调机制、掉帧计算的逻辑每 16.6ms 消费一个 vsync 信号FrameCallback 里计算掉帧数以及抓取主线程卡顿现场的正确手段——用 Looper 的 setMessageLogging 或自定义 Printer 监听 dispatchMessage 耗时也可以采用 Perfetto 抓取 systrace精确看到主线程的耗时函数。B 站这类播放类 App 的卡顿很多跟主线程持锁、Surface 数据排队、解码器输出 buffer 滞后有关这个知识点要特别熟悉。网络优化方面推荐准备的要点是 HTTPDNS、连接复用、智能预加载策略。视频首帧秒开的核心思路是提前把要用的资源准备好包括 DNS 解析的提前、TLS 会话复用的提前、播放器实例的提前初始化。如果能结合 OKHttp 的 ConnectionPool 机制和 ExoPlayer 的 LoadControl 策略来谈会给面试官留下有真实工程经验的印象。5.3 重写简历项目从流水账变成决策说明书二面的体验让我彻底明白了一个道理面试官不看你的项目做了什么而看你在里面解决了什么别人解决不了的问题。所以在复盘阶段我把简历里每个项目重新写了一遍每一段都必须包含四个要素目标这个项目为什么存在、约束资源、时间、并发量、兼容性等限制、方案选了哪个方案、为什么不是另一个、验证怎么证明成功指标口径是什么。举个例子如果你写优化了 App 冷启动速度不要只写减少了 Application 里初始化任务而要写清楚通过 Baseline Profile 和启动器比如自定义的启动器框架把任务划分为必须主线程同步执行和可异步或延迟执行同时用 System.currentTimeMillis 在多个埋点记录各阶段耗时最后冷启动从 1.8s 降到 1.1s这是在 XX 台测试机型上取的中位数数据。这样讲面试官才能感受到你做的不是功能迭代而是工程治理。5.4 针对 B 站这类视频社区 App 的特训方向如果你面试的目标公司是 B 站这种视频社区类产品建议额外准备三个方向第一播放器相关技术。至少要了解 ExoPlayer 的 MediaSource 体系、TrackSelector 怎么选流、LoadControl 怎么控制缓冲水位、MediaCodec 的音视频解码管线。有条件的可以自己做一个简单的视频播放 Demo用不同的 buffer 策略跑一个对比实验记录首帧时间和卡顿率。第二大流量列表与异构布局。RecyclerView 在这个场景下需要处理不同 item 类型的混合、视频封面加载、预加载触发、快速滑动时的回收复用。最好能储备一些自己动手做过的列表优化 case。第三长连接与消息推送。直播弹幕、评论实时交互都需要稳定可靠的长连接通道如何做心跳保活、消息去重、ACK 重传这些都是 B 站类型的 App 不能回避的问题。如果简历里能有一条长连接性能优化的项目会是很大的加分项。6. 写在最后凉经不是终点是校准认知的镜子回头再看这场 B 站 Android 一二面虽然结果是凉了但它给我的价值远大于那些轻易通过的小公司面试。它让我第一次清楚地意识到大厂的面试官不是在找能背答案的答题机器而是在找能对他们业务里的真实技术问题有深入思考的工程伙伴。我在这次面试中暴露的不足恰好在后续的复习方向里变成了最需要补强的短板。最后再分享两个小技巧。第一面试中如果被问到一个不会的题不要停下来沉默可以尝试用一个简单的场景把自己的思考过程讲出来哪怕最后没答对面试官至少能看到你的思维路径是清晰的。第二每次面试后一定要在当天做复盘趁记忆清晰的时候把题目、回答、卡壳点全部记下来我这次整理出来的面试笔记其实比很多付费课程都有价值。希望这篇文章能帮你在下次面试前少走一段弯路。