2020年B站校园招聘这套Android笔试卷,到现在都有不少同学在翻,我见过好几个准备大厂校招的人拿它当模拟题练手。原因很简单:它考的东西覆盖面广、层次分明,既有基础原理的深度追问,也有工程实践的综合考察,还夹杂着不少“看起来会、一写就错”的陷阱题。这篇文章就围绕这套卷子的核心考点来拆,把每类题背后的知识点、答题思路和容易踩的坑都捋一遍,给准备Android方向面试的人一份能直接照着复习的清单。
1. 笔试卷的整体结构与考察逻辑
先说这套卷子给人的整体感觉。B站这种体量的互联网公司出校招笔试题,通常不是单纯考“会不会背概念”,而是想通过有限的题目,快速筛选出“基础扎实、有工程思维、能解决实际问题”的候选人。尤其是Android方向,客户端业务迭代快、性能问题敏感、团队成员协作频繁,所以考察点会集中在计算机基础、Java功底、Android核心机制、性能优化、网络与数据存储这几个维度。
1.1 笔试常见的题型分布
从题目构成来看,大致是三类:
- 单选题/多选题:覆盖Java语法、数据结构、操作系统、网络协议、Android基础概念,占比一般在30%到40%。这类题看似简单,但经常会在选项里埋坑,比如“以下哪个说法是错误的”这种反向问法,或者把两个相似概念放在一起对比,比如Handler.post和View.post的区别、startService和bindService的生命周期差异。
- 简答/问答题:重点考察对Android核心机制的理解深度,比如Activity启动模式、Handler消息机制、Binder通信原理等。这类题光背结论没用,面试官想看的是你能不能讲清楚“为什么这样设计”。
- 编程题/设计题:一般是一两道算法题加一个系统设计或场景题。算法题难度适中,通常涉及链表、二叉树、动态规划或字符串处理;设计题则偏业务,比如“如何设计一个图片加载库”“如何实现一个带缓存的视频列表”等。
1.2 B站这套卷子的考察侧重点
B站作为视频社区,客户端有很强的业务特征:视频播放、弹幕、评论、推荐流、直播。所以它的笔试题里,网络优化、图片加载、列表卡顿优化这类题目出现概率很高。我在实际做题和带人复盘时发现,这套卷子特别看重三个方面:
第一是基础原理的扎实程度。比如Binder机制,不只要知道它是Android的IPC方式,还要理解为什么用Binder而不是传统的Socket或共享内存,涉及拷贝次数、性能、安全性的对比。
第二是排查问题的思路。很多题目会给一个场景,比如“首页列表滑动卡顿,你怎么定位和解决”,这考察的是完整的问题排查链路:先从UI渲染层面看是否掉帧,再检查主线程是否有耗时操作,接着看布局是否存在过度绘制,最后分析内存和GC影响。
第三是工程细节的把控。比如内存泄漏的常见场景、多线程并发下的数据同步、网络请求的缓存策略等,这些都是实际开发中每天都在面对的事情。
2. 高频核心考点深度解析
这套卷子里,有几个知识点几乎是必考的,而且经常变换形式反复出现。我把它们单独拎出来讲透。
2.1 Activity启动模式与任务栈
Activity的四种启动模式——standard、singleTop、singleTask、singleInstance,是校招笔试的经典题目。但我看很多同学的回答都停留在“背定义”的层面,比如“singleTop表示如果栈顶存在就直接复用”,这远远不够。
真正的考察点在于任务栈的行为变化。例如,A启动B,B是singleTask模式且栈内已有B的实例,那么系统会调用B的onNewIntent,同时清除B之上的所有Activity。这里面有几个隐藏考点:onNewIntent默认不会刷新界面数据,需要手动调用setIntent和重新初始化数据;singleTask和singleInstance不会在同一任务栈中重复创建,但singleTask的实例可以存在于其他任务的栈中,而singleInstance则是独占一个任务栈,这个栈中只能有它一个Activity。
另外还有一个常考的问法:一个singleTask的Activity被多次启动时,onCreate会不会再次执行。答案是如果实例已经存在,就不会走onCreate,而是走onNewIntent + onRestart(如果被系统回收过则还是走onCreate)。这个细节很多人会答错。
我在笔试答疑时经常提醒:启动模式这块,一定要结合onNewIntent、onRestart、taskAffinity这些一起看,而且最好在模拟器上实际跑一遍启动流程,观察日志输出和返回栈变化,比自己死记硬背强得多。
2.2 Handler消息机制的灵魂三问
Handler机制是Android面试里出镜率最高的考点,没有之一。B站这套卷子也毫不例外,而且问得相当细,往往拆成三个层次:
第一层:Handler、Looper、MessageQueue三者的关系。Looper负责在当前线程开启消息循环,MessageQueue是消息队列,Handler负责发送消息和处理消息。一个线程只能有一个Looper,但可以有多个Handler。这个基本关系如果答不清楚,后面的都不用聊了。
第二层:MessageQueue的消息如何按时间排序。这里涉及同步屏障(SyncBarrier)、消息的when字段、阻塞唤醒机制。主线程的Looper在没有消息时会通过epoll机制休眠,不占用CPU,等到有消息或者超时再唤醒。这个设计是Android主线程能够常驻而不死循环消耗资源的关键。
第三层:为什么Handler能实现线程切换。核心在于Handler在创建时绑定了Looper所在线程,当子线程调用handler.post时,Message被放入主线程的MessageQueue中,由主线程的Looper取出并执行,所以回调代码自然运行在主线程。
常见陷阱题是:“Handler构造方法里调用了Looper.myLooper(),如果在非Looper线程new Handler会怎样?”答案是会抛出RuntimeException,因为myLooper()返回null。在Android 11以前可以new Handler()默认绑定主线程Looper,但之后必须显式传入Looper,否则崩溃。这块我建议把源码里Handler的几个构造方法都看一遍,笔试时遇到Handler相关的判断题基本能全覆盖。
2.3 内存泄漏与优化
内存泄漏是Android性能面试的必考项,这套卷子里肯定会有一两道题,而且通常不是直接问“什么是内存泄漏”,而是给出场景让你判断是否会泄漏。
高频泄漏场景有几个,我列一下:
- 匿名内部类持有外部类引用,典型的如Handler内部类持有了Activity,消息还在队列中,Activity无法被回收。
- 静态Context引用,比如在单例中保存了Activity的Context。
- 注册了Receiver或EventBus没有反注册。
- 流对象、Cursor、数据库连接没有关闭。
- 长时间持有Bitmap且未复用或未回收。
- 动画未停止或无限循环动画导致View无法释放。
回答这类题目时,光说“会泄漏”还不够,要能讲清楚泄漏链路:谁持有了谁,为什么GC无法回收,以及怎么解决。比如Handler泄漏,标准回答是:Handler在Activity销毁后仍可能持有Activity引用,消息队列中的消息引用Handler,导致Activity无法被回收;解决方案是把Handler改成静态内部类并使用弱引用,同时在onDestroy中removeCallbacksAndMessages(null)。
B站这类公司对性能优化的要求很高,因为视频播放页面和首页推荐流的渲染压力都很大。所以我建议在准备这类题目时,把LeakCanary的检测原理也看一下,比如它是如何通过Application注册ActivityLifecycleCallbacks来监听Activity销毁,再配合ReferenceQueue判断是否发生泄漏的。这些工程细节如果能在笔试里写出来,会很加分。
2.4 网络协议与HTTP
网络这块也是笔试重灾区。HTTP、HTTPS、TCP三次握手四次挥手、DNS解析,这些是基础。Android方向还会多考察一些和客户端强相关的内容,比如OkHttp的拦截器机制、Retrofit的动态代理、HTTP缓存策略。
一个经常出现的简答题是:HTTP和HTTPS的区别。除了常规的加密传输、端口不同(80和443)、需要证书之外,有个进阶的点是HTTPS的握手过程——包括客户端验证服务端证书、协商对称加密密钥(ECDHE或RSA)、之后通过对称加密传输数据。如果能在回答中提到TLS 1.3相比TLS 1.2减少了往返次数,会显得你确实了解网络原理而不是背课本。
另一个常见的是:TCP为什么是三次握手而不是两次。因为三次握手能确认双方的收发能力都正常,两次无法保证服务端确认客户端的接收能力,而且能防止旧连接请求突然到达服务端导致的资源浪费。
至于OkHttp,考试中比较喜欢问的是“OkHttp的请求流程”或者“OkHttp的拦截器有哪些”。这个可以按责任链来答:应用拦截器(自定义Interceptor)→ RetryAndFollowUpInterceptor(重试和重定向)→ BridgeInterceptor(补全请求头)→ CacheInterceptor(缓存)→ ConnectInterceptor(建立连接)→ CallServerInterceptor(发起请求并读取响应)。每层拦截器职责清晰,这就是责任链模式在Android网络库中的典型应用。
3. 编程题与算法思路实战拆解
算法题在B站这套笔试卷中通常占一道或两道,难度不会特别夸张,但要求现场写出来的代码必须能跑、能处理边界情况。我根据经验总结了几类最常出现的算法题型和对应的解题模板。
3.1 链表类题目
链表是校招笔试的最爱,因为代码量适中、边界条件多、能考察基本功。典型的题目有:反转链表、链表相交、环形链表判断、合并两个有序链表。
以反转链表为例,如果要求O(1)空间原地反转,标准解法是迭代三指针:pre、cur、next,每次把cur指向pre,然后整体右移。另一个常考的是K个一组反转链表,这需要先数够K个节点再做子链表反转,注意首尾连接。笔试时建议先把链表节点的定义写好,注意判空,再动手写核心逻辑,这样至少能拿到部分分。
3.2 二叉树与DFS/BFS
二叉树相关的题目,比如层序遍历、最近公共祖先、路径总和等,也很常见。层序遍历的核心是使用队列,每次记录当前层的节点数来控制出队次数。这里有个细节:必须提前把当前队列的大小记录下来,否则在循环中动态获取queue.size()会随着出队入队而变化,导致层边界错乱,这个坑我见过无数人踩。
如果题目要求输出每一层的平均值或者最大值,就是在层序遍历的基础上增加一个汇总逻辑即可。二叉树题目往往可以用递归和迭代两种写法,笔试时优先用递归,代码更简洁,不容易写错。
3.3 常见的手写工具类题
有时候笔试题不会直接给你算法题,而是让你手写某个工具类,比如:
- 手写单例模式(要求线程安全的双重检查锁版本)
- 手写线程安全的懒加载缓存
- 手写一个生产者消费者队列
- 手写LRU缓存
这些题表面是“写代码”,实际上考察的是并发控制、数据结构选型和边界处理能力。以手写LRU为例,核心是LinkedHashMap的accessOrder机制,重写removeEldestEntry方法即可实现;如果要求自己实现而不借助LinkedHashMap,则要用HashMap + 双向链表,每次访问把节点移到链表头部,淘汰时删除尾部节点。这里有一个容易被忽视的点:并发环境下需要对整个缓存加锁,或者使用ConcurrentHashMap + 锁分段,单纯用HashMap在put和get同时发生时可能产生不一致。
我在面试别人时发现,很多同学能背出LRU的思路,但一写代码就漏掉“访问已存在节点时需要先删除旧节点再头插”这一步,导致链表出现重复节点。这种细节就是笔试拉开差距的地方。
3.4 动态规划入门题型
动态规划在校招笔试中出现概率很高,B站这套卷子如果出现了,一般属于中等偏下难度,比如爬楼梯、最长递增子序列、打家劫舍。解题模板是三步:定义dp数组含义、寻找状态转移方程、确定初始化和遍历顺序。
举“最长递增子序列”为例,定义dp[i]为以nums[i]结尾的最长递增子序列长度,转移方程是:dp[i] = max(dp[j] + 1) for all j < i and nums[j] < nums[i]。这个思路不难,但笔试时一定要处理dp数组初始化为1,因为单个元素本身就是一个长度为1的子序列。如果不初始化,结果全为0。
这些算法题准备时没有什么捷径,我建议把Hot 100里链表、二叉树、字符串、动态规划的经典题型各刷10道左右,重点是理解思路而不是背代码,因为笔试题往往会在原题基础上改动条件,死记硬背容易翻车。
4. 系统设计题与开放性问题的回答策略
这套卷子还有一个让很多人头疼的部分,就是系统设计题或开放性问题。这类题没有标准答案,考察的是你分析问题、设计解决方案和权衡取舍的能力。B站这类视频平台,出题会围绕业务场景展开。
4.1 典型题型:图片加载库的设计
假设题目是“设计一个图片加载库,需要考虑哪些方面”,这是个很典型的开放题。回答时可以按这几个模块来拆:
- 内存缓存:使用LruCache管理Bitmap,根据设备可用内存分配缓存大小,一般取maxMemory / 8。
- 磁盘缓存:使用DiskLruCache,缓存压缩后的图片文件,key通常是url的md5值。
- 网络加载:通过OkHttp异步下载图片,支持HTTP缓存机制(ETag、Last-Modified)。
- 异步处理:线程池加载图片,主线程回调更新UI,注意线程池核心线程数、队列容量、拒绝策略的配置。
- 图片压缩:按ImageView的实际宽高进行采样压缩(inSampleSize),避免OOM。
- 生命周期感知:页面销毁时取消正在进行的加载任务,避免回调到已销毁的组件。
回答的逻辑要按“加载流程”来串,而不是罗列名词。比如一张图片从url到显示,先查内存缓存,命不中再查磁盘缓存,再没有就发起网络请求,下载成功后写入两级缓存,最后在主线程设置到ImageView。这样回答层次清晰,也体现你做过实际项目。
4.2 典型题型:直播或视频播放场景题
B站笔试题偶尔会出现“视频首屏秒开怎么做”或“直播弹幕如何实现不卡顿”这类场景题。这些题目不是让你写代码,而是考察对性能优化和业务架构的理解。
比如“首屏秒开”,可以回答的方向包括:预连接(提前建立网络连接)、预加载(提前缓存部分视频数据)、分片下载(支持边下边播)、播放器初始化优化(如提前创建播放器实例)等。但如果答题只说“加个缓存”,就太单薄了,需要说清楚预加载的触发时机、缓存大小限制、内存和磁盘的配合策略。
我当时的一位同事总结过一个思路,我觉得很值得参考:回答场景题时,给自己定一个框架——用户路径、耗时分析、优化手段、效果验证。先描述用户在什么路径上感知到了什么问题,再分析这个问题的耗时分布在哪几个阶段,然后针对每个阶段给出优化手段,最后说怎么验证优化效果(如首帧耗时、缓存命中率、卡顿率)。这个框架在笔试和面试场景下都非常好用。
4.3 回答开放题的避坑建议
写开放题最容易犯的毛病有这几个:
第一是答得不系统,想到哪写到哪。比如问“如何优化列表卡顿”,只写“用RecyclerView不用ListView”就结束了。应该从主线程耗时、布局层级、渲染频率、内存分配几个维度展开。
第二是只有结论没有方案。比如写“使用缓存”,但没有说缓存放在哪、淘汰策略是什么、怎么保证一致性,这让面试官感觉你没有真正做过。
第三是不考虑边界和异常。设计一个缓存系统时,如果能主动提到“磁盘缓存写入失败怎么处理”“应用进程被杀后缓存是否有效”“弱网环境下缓存策略应该怎么调整”,这些细节会大幅提升答案质量。
5. 备考路线与复习资源推荐
如果目标是B站或者其他大厂的Android校招岗位,这套卷子可以当作一次摸底测试。我的建议是先限时做一遍,找到自己的薄弱模块,再有针对性地复习。以下是我根据带人经验总结的比较高效的备考节奏。
5.1 基础阶段:Java、数据结构和操作系统
Java是Android的根基,重点看集合源码(HashMap、ArrayList、LinkedList)、并发(synchronized、volatile、Lock、线程池)、JVM内存区域和GC算法。数据结构方面,数组、链表、栈、队列、二叉树、哈希表、堆这几种类型要熟练。操作系统方面,重点看进程与线程、死锁、内存管理、虚拟内存,这些在笔试中偶尔会以选择题出现。
时间安排上,如果每天能投入3到4小时,建议用两周时间过一遍Java基础和数据结构的核心内容。这个阶段不建议直接啃源码,先理解整体结构和设计意图,等面试前再深入细节。
5.2 核心阶段:Android系统和组件原理
Android部分需要重点掌握的内容包括:
- Activity启动流程与启动模式
- Service生命周期与绑定原理
- BroadcastReceiver注册方式与作用
- ContentProvider的跨进程数据共享
- Handler与Looper机制
- Binder与AIDL原理
- View的绘制流程(Measure、Layout、Draw)
- 事件分发机制
- RecyclerView的缓存机制与优化
- 四大组件之间的通信方式
这里我特别想强调跟着源码看一遍的必要性。很多人觉得读源码难,但像Activity启动过程、View的绘制流程其实核心路径并不长,花一个周末的时间下载AOSP源码或者直接在Android Studio里看SDK源码,把关键方法走一遍,效果比刷十篇博客都好。笔试里一些细节题,比如“onMeasure传入的MeasureSpec中的EXACTLY和AT_MOST分别对应什么”,不读源码很难记得牢。
5.3 强化阶段:性能优化与大厂真题
性能优化是拉开差距的地方。除了前面提到的内存泄漏,还要准备弱网优化、启动优化、包体积优化、布局优化、卡顿监控。推荐的资料包括:
- 《Android开发艺术探索》关于性能优化的章节
- 官方文档里的应用性能优化指南
- LeakCanary、BlockCanary的源码与实现原理
真题练习方面,牛客网、力扣上的Android面试题合集可以作为刷题素材,近几年的校招真题多练几套。尤其注意,限时演练非常重要,很多真实考试挂掉的考生都是因为时间分配不合理——前面选择题纠结太久,最后编程题没时间写。
5.4 项目经历与简历准备的小建议
除了笔试本身,B站这种公司也会通过简历和面试来综合评判候选人。我的建议是,选一个有代表性、能讲清楚技术难点的项目写在简历上。所谓能讲清楚,指的是你能回答这几个问题:
- 项目背景是什么,解决的是谁的问题
- 你负责的模块技术方案是什么,为什么这么选
- 实现了哪些功能和指标
- 踩过哪些坑,怎么解决的
- 如果重做一遍,哪里会做得不一样
把这些准备好,笔试通过之后面试环节也会顺利很多。
6. 笔试常见失分点与细节陷阱
最后这部分,我把这套卷子以及类似的大厂Android笔试卷里,最容易出错的地方集中做一次整理。每一条都是我见过真实考生栽跟头的,拿出来说说。
6.1 概念混淆类陷阱
- startService和bindService混为一谈。startService启动的Service与启动者无关,即使启动者退出,Service仍然存活;bindService则与绑定者绑定,解绑时如果没其他绑定则销毁。
- onSaveInstanceState和onPause的执行顺序。onSaveInstanceState在onPause之后、onStop之前调用,不是onStop之后。这个顺序在很多选择题里被当作干扰项。
- 进程和线程的区别。进程是资源分配的最小单位,线程是CPU调度的最小单位。Android中每个应用默认运行在自己的进程中,但同进程可以有多个线程。
- dp和px的区别。dp是密度无关像素,px是物理像素。在mdpi(160dpi)下1dp等于1px,其他密度下需要按比例换算。
6.2 细节记忆类陷阱
- RecyclerView并没有自带点击事件,需要自己实现OnItemClickListener。
- HashMap允许key和value为null,Hashtable不允许,ConcurrentHashMap也不允许。
- 接口中的变量默认是public static final,接口中的方法默认是public abstract。
- 静态代码块在类加载时执行,且只执行一次,执行顺序是静态代码块 > 构造代码块 > 构造函数。
- equals相等则hashCode必须相等,hashCode相等则equals不一定成立。
- 字符串比较用equals而不是==,Integer在-128到127之间有缓存,超过范围用==可能返回false。
6.3 答题策略类建议
笔试时间有限,我的策略是:先做会的题,再做半会的题,最后啃不会的题。选择题如果完全不会,不要空着,判断题型的概率至少能蒙对一些。简答题即使不知道完整答案,也要把相关的、已知的知识点写上,因为阅卷时通常按点给分,写总比不写好。
编程题如果时间不够,先写出暴力解法也能拿一部分分数,总比交白卷强。很多大厂的笔试通过率并不高,但只要你把基础题的分全拿住,再在编程题上做出一个题目,基本就能过线。
我个人在实际操作中的体会
这套2020年B站的Android笔试卷虽然过去几年了,但我一直把它当作考察Android基本功的“试金石”。它不偏不怪,考察的都是日常开发中最核心的知识点,同时又有足够的深度来区分“会用”和“懂原理”的人。如果你能把这套卷子里的考点全部掌握,再稍作算法题训练,应付大部分互联网公司的Android校招笔试应该是够用的。
最后再分享一个复习时的小技巧:不要只刷题,每做完一道题,都问自己一句“这题改个条件我还能答对么”。比如Activity启动模式,你背会了四种模式,但换成“A启动B,B又启动C,此时C是singleTask,C点击返回会回到哪个Activity”这种变体题,还能答对吗?这种自我追问式的复习,比盲目刷题效率高得多。