小红书2023秋招Android开发岗第一批笔试,我当时排在9月初那场,整体做下来最大的感受是:题目不算偏,但覆盖面很广,Android基础、JVM/Kotlin、算法、系统框架都有涉及。它不像社招那样深挖某个源码细节,更看重你把一个知识点讲明白、讲系统。这篇文章我把整场笔试的复盘整理出来,从题型结构、核心考点、答题策略到踩坑记录,给正在准备Android校招的同学一份可以照着准备的清单。
我当时投递的是Android开发工程师(客户端方向),笔试时间120分钟,整体题量偏大,如果前面选择题犹豫太久,后面算法题会很赶。所以这篇复盘不只是罗列知识点,我会把每个考点对应的得分策略、常见的问法陷阱、以及平时复习容易遗漏的地方一起说清楚。
1. 笔试基本盘:题型、考察范围与考前优先级
1.1 题型构成与考察范围
先说我拿到的这套题的构成,大致分四块:
- 选择题(单选+多选,约20-25道):覆盖Java/Kotlin基础、Android四大组件、Handler、多线程、集合框架、网络协议、数据结构。
- 算法题(2道,LeetCode中等偏上难度):一道偏字符串/数组操作,一道偏二叉树或动态规划。
- 简答/设计题(1-2道):考某个组件的工作原理,或者给你一个场景让你设计实现方案。
- 附加题(不一定有):部分批次的题目里会有开放性题目,比如让你设计一个图片加载库、分析卡顿问题。
从考察范围看,它基本覆盖了校招Android岗笔试的大部分标准模块。我在备课时按权重排了优先级:Java/Kotlin语言基础 > 四大组件与Handler > 并发与线程 > 算法 > Jetpack与架构 > 性能优化 > 源码原理。这篇帖子的重点会放在Android相关内容上。
1.2 考前优先级怎么排
笔试不是面试,没有追问环节,所以你的答案必须在一个题目的有限篇幅里让对方看到你知道的边界。我排优先级主要看两个维度:被考到的概率、以及投入产出比。
Java集合与并发是性价比最高的部分,ArrayList/HashMap/HashSet的底层原理、HashMap在并发下的问题、线程池参数、synchronized与volatile的区别,这些都是高频中的高频。Android部分,Activity启动流程、Handler机制、事件分发、Binder原理是必考,而且笔试中经常以选择题形式出现,不需要你手写源码,但必须理解过程。
算法题,我的建议是至少把LeetCode Hot 100刷两遍。小红书这批笔试的算法题不偏,基本没出冷门题型,但题量不小,熟练度直接决定你能不能做完。
1.3 关于时间分配的总体计划
拿到试卷后我用了5分钟快速浏览全部题目,把每道题预计耗时标了一下。选择题控制在45分钟内完成,算法题留60分钟,简答题留15分钟。这样做的原因是选择题里有很多一眼能看出答案的基础题,纠结两三道难题不如用这个时间保证算法题能完整跑通。
实际操作中,我最后多选和简答用了相对少的时间,算法第二道快结束时有些赶,这也是这次笔试的一个小遗憾。
2. Android核心基础:启动流程、消息驱动与生命周期
2.1 Activity启动流程与AMS的协作逻辑
这套题里关于Activity启动流程出现了至少两处:一道选择题问startActivity之后系统做了什么,一道简答让简述整个过程。这类题能否得分,关键在于你能不能把Launcher进程、SystemServer进程、App进程的角色分清楚。
完整流程可以按这条线记:调用startActivity后,实际会经过ActivityManagerShellCommand或应用内的ActivityTaskManager.getService(),通过Binder调用到系统进程里的ActivityTaskManagerService,再通知AMS做权限校验、任务栈调整、进程创建和生命周期调度。
笔试作答时我建议分成三层来说:
- 应用内发起:startActivity -> Instrumentation.execStartActivity -> ActivityTaskManager.getService().startActivity,这里涉及Binder IPC。
- 系统进程处理:ATMS/AMS完成ActivityRecord创建、栈管理、Intent解析、权限检查,并决定要不要创建新进程。
- 进程与生命周期回调:如果目标Activity所在进程不存在,通过Zygote fork新进程,然后Application创建、Activity创建、onCreate/onStart/onResume按顺序回调。
答题时容易漏掉的点有两个:一是新进程的创建是请求Zygote完成的,AMS本身不直接fork;二是onResume回调是在进程创建完成后由系统进程通过Binder反向调用进应用进程的。这两个点写出来,能让你的答案明显超过"背过流程"的水平。
2.2 Handler消息机制:Looper为什么必须prepare
Handler这套题在笔试里出现的频率稳定得可怕。选择、填空、简答都有可能。它考的不只是API,而是理解消息驱动模型。
消息机制的核心角色是MessageQueue、Looper、Handler三者。Looper通过MessageQueue不断取消息,Handler负责发送消息和处理消息。
笔试常考的一个点是:为什么在使用Handler之前必须调用Looper.prepare()?这个答案的关键在于Looper是一个ThreadLocal变量,每个线程只能有一个Looper实例,而prepare方法会把当前线程初始化为带Looper的线程。主线程在ActivityThread的main方法里已经预调用了Looper.prepareMainLooper(),所以你直接new Handler就行,子线程则需要自己prepare和loop。
还有一个容易失分的方向是同步/异步消息和消息屏障。笔试多选题如果出现"关于Handler机制说法正确的是",经常会在这些选项里埋雷:MessageQueue的next()方法会阻塞、IdleHandler用于空闲时处理任务、SyncBarrier可以屏蔽同步消息,这些都是需要掌握的细节。
2.3 四大组件生命周期与进程优先级
除了Activity,Service、BroadcastReceiver、ContentProvider也各有一两道题。Service主要考startService与bindService两种启动方式的区别,以及onStartCommand的返回值含义。BroadcastReceiver考静态注册与动态注册的区别、Android 8.0后静态注册隐式广播的限制。ContentProvider考的基本是它和Binder的关系,以及ContentResolver作为统一入口的意义。
这里有个容易忽略的考点:进程优先级。系统在内存不足时会按优先级杀进程,从高到低依次是:前台进程、可见进程、服务进程、后台进程、空进程。问你"哪个进程最不可能被杀"这种选择题,基本都围绕这个优先级展开。
生命周期部分,设备旋转导致的Activity重建是一个经典问法,通常紧接着会问ViewModel为什么能在旋转后存活。这里要联系到ViewModelStore和NonConfigurationInstances的关系,单纯背生命周期表格是不够的,笔试会把多个知识点串起来考。
3. Kotlin协程与Jetpack架构:笔试里的新势力
3.1 协程的挂起与恢复怎么答才不丢分
近两年校招笔试中,Kotlin协程的出现频率明显上升。选择题的方式通常是让你判断几个协程代码片段的执行顺序或最终结果。简答则会让你讲讲协程与线程的关系、为什么协程能"挂起"而不阻塞。
"挂起"的本质是协程体被编译成一个状态机,遇到挂起函数时,当前协程的执行状态和局部变量被保存,线程资源释放,等异步结果准备好后,在指定调度器上恢复执行。笔试作答时,把这几层讲清楚就行:协程依赖的Continuation、状态机机制、以及Dispatchers决定在哪个线程上执行。
setOfDispatchers这块有一个高频易错点:withContext(Dispatchers.IO)里执行耗时任务、然后回到Dispatchers.Main更新UI,这是标准写法。考协程作用域时,题目经常给一段代码问你"哪里有问题",常见埋坑是全局作用域里启动协程做UI操作导致内存泄漏,正确做法是用ViewModelScope或lifecycleScope。
3.2 MVVM与ViewModel的考察方式
笔记热词里有"android studio mvvm代码示例",说明现在面试官默认候选人懂MVVM。笔试这边,它通常结合生命周期来考,不会让你写完整代码,但会给你一段ViewModel代码判断问题。
ViewModel能做Activity重建时的数据存活,是因为它在配置变更时不会跟着Activity销毁,系统会通过NonConfigurationInstances把ViewModelStore保留下来。这一点我上一章提到过,用的就是这个考点。
MVVM这块还常考ViewModel与LiveData/Flow的配合,以及DataBinding的作用。笔试作答时,不需要展开DataBinding的具体API,但要能说清楚MVVM三层各自职责:View负责渲染和事件收集,ViewModel负责业务逻辑状态,Model负责数据来源。以及单向数据流为什么能减少状态不同步的问题。
3.3 设计题实战写法:图片加载框架怎么答
如果笔试出现开放设计题,高频题目大概率是让你设计图片加载框架或者网络层框架。我当时遇到的是图片加载框架,这种题没有标准答案,但阅卷人能从你的方案里看出是否真的接触过工程实践。
我当时的作答结构是这样的:
- 总体分层:API入口层(load/into) -> 调度层(线程池+任务队列) -> 缓存层(内存缓存+磁盘缓存) -> 网络层(下载图片) -> 解码层(BitmapFactory/ImageDecoder)。
- 缓存策略:LruCache做内存缓存,DiskLruCache做磁盘缓存,key用URL hash。这里要说明为什么用LruCache(LinkedHashMap实现LRU,Android系统原生支持)。
- 线程模型:主线程发任务,IO线程执行下载和磁盘读取,解码跑在非主线程,回调切回主线程。
- 易扩展点:支持自定义解码器、支持渐进式加载、支持生命周期感知(在Fragment销毁时取消请求)。
这样的方案结构完整,同时向阅卷人展示了你对图片加载链路的理解。任何类似设计题都可以套这个思路:入口 -> 调度 -> 缓存 -> 执行 -> 回调。
4. 性能优化与工程化:从ANR、内存到R8混淆
4.1 ANR问题:笔试中的高频场景题
ANR这样的题,虽然不会让你真的去抓一份trace文件,但会给场景判断哪些操作可能导致ANR。我记得热词里有"android 测试"和"android framework",ANR正好是这两者的交叉点。
ANR的触发场景,笔试常考的是四类:输入事件5秒未响应、BroadcastReceiver前台10秒/后台60秒未执行完、Service前台20秒/后台200秒未执行完、ContentProvider在发布过程中超时。
考法通常是这样:给一段代码,问为什么会导致ANR,或者给你一个卡顿问题让你说出排查思路。回答的切入点就围绕主线程是否有耗时操作展开。最容易错的选项是"在onCreate里做网络请求"这种:它不一定立刻ANR,但如果超过5秒就会。所以答题时要强调ANR的判定是超时机制,不是某个操作本身违规。
排查思路题则需要你像真实开发那样回答:先看/data/anr/目录下的traces.txt,再看主线程堆栈和CPU占用,结合logcat看是否有死锁或广播阻塞。这套思路笔试能写出来,说明你有实际排查过问题。
4.2 内存泄漏与卡顿优化
内存泄漏题在笔试中经常以"以下哪些会造成内存泄漏"的形式出现。经典陷阱包括:非静态内部类持有外部Activity引用(Handler就是典型)、静态变量引用Activity、匿名内部类做成异步任务后Activity销毁、单例持有Context、流未关闭。
作答的时候别只背结论,最好能补一句为什么会泄漏。比如Handler泄漏,是因为主线程的Looper持有MessageQueue,MessageQueue里有待处理的消息,Message的target指向Handler,而Handler是Activity的内部类实例,隐式持有Activity引用。这条引用链导致Activity无法被回收。笔试里能写出引用链,这道题的答案质量会明显不一样。
卡顿优化题一般是问你怎么定位掉帧。我的思路是:利用Systrace或Perfetto抓trace,看主线程执行时间线,再结合布局层级(减少嵌套、使用ConstraintLayout)、避免在onDraw里创建对象、减少过度绘制几个层面回答。
4.3 R8与代码混淆:笔试里的隐藏加分项
针对热词里出现的"android r8"和"android 马甲包代码混淆",说明混淆相关问题是这两年笔试的新增关注点。它会问R8与ProGuard的区别,这题不难但很多人答不好。
R8是AGP 3.4以后默认的代码压缩与混淆工具,它把ProGuard的压缩、优化、混淆、脱敏四个步骤合到了一起,整体更快。笔试作答时,说清楚R8包含的四个职能就够了:shrink(删除无用代码和资源)、optimize(字节码级优化)、obfuscate(类名/方法名混淆)、preverify(预校验)。
另外一个常见考法是给一个keep规则,让你判断某个类能不能被混淆。核心是记住keep规则的含义:-keep表示保留类和成员不混淆、-keepclassmembers保留类成员、-keepnames只保留原名不参与重命名。要注意的是keep规则会保留大量代码不利于压缩,所以不要滥用。
4.4 AGP版本与构建工具链
笔试的一个冷门题方向是构建链路,比如问你Android Gradle Plugin(AGP)和Gradle版本的关系。热词里那串"android studio hedgehog | 2023.1.1 patch 2支持agp8版本吗",本质上考的就是版本兼容意识。
这里不需要准确背出每个版本的兼容矩阵,但必须知道:AGP版本和Gradle版本存在对应关系,AGP 8.x需要Gradle 8.0以上;升级Android Studio后,旧项目有时候要同步升级AGP;AGP 8.0移除了很多旧API,使用了transform API的三方库会不能编译。
笔试如果出这种题,通常是给你一组组合让你判断是否合法。作答技巧是记一条主线:Android Studio大版本发布时,会默认配套一个AGP版本,升级Studio时先看项目的AGP是否需要同步升级。这个问题实战价值很高,因为秋季校招很多同学拿自己电脑上现成的项目去笔试,构建环境不一致就会浪费时间。
5. 做题实战:算法、开放题与时间分配策略
5.1 算法题的三个手写陷阱
笔试的算法题不是让你白板写完整可运行代码,但判题系统会看核心逻辑。我这次遇到的两道题都不难,但有两个地方差点丢分。
第一个是边界条件。比如字符串处理题里,空字符串、单个字符、全相同字符这些Case,如果你没有在编码前想清楚,很容易写出if条件不全的代码。建议养成习惯:拿到题先花30秒列出边界情况,再写代码。第二个是复杂度的把控,做题时标注清楚时间和空间复杂度,这是加分项。第三个是不要用IDE的自动补全和格式化功能,笔试环境有时候没有补全,平时就要练手写代码的能力。
5.2 开放题怎么答才像有经验的人
开放设计题不需要你写出完整实现,重点是展现你的工程视角。我建议用总分结构回答:先一句话说明整体方案,再分模块描述,每个模块写明职责和关键实现,最后说不足和可扩展性。
还是以图片加载框架为例,开头概括:"整体采用三级缓存+线程池调度+生命周期感知架构"。然后分别写内存缓存、磁盘缓存、网络层、解码层的职责。结尾加上一句"如果后续要支持视频封面加载,可以在解码层做扩展",这种写法说明你考虑过框架的演进方向。
阅卷人看到这种答案,容易和背题背出来的人区分开:背题的人只会列组件名,有经验的人会写组件间的交互和取舍。
5.3 做题检查清单:交卷前一定要看的三件事
我习惯预留最后5分钟做检查,主要看三件事:
- 选择题是否勾选了所有题目,多选有没有少选(多选少选有时按比例给分,空着必然零分)。
- 算法题代码是否考虑了空输入和非法输入,方法签名是否跟题目要求一致。
- 简答题的关键术语是否写对,比如ActivityThread、Instrumentation、Binder这些词不能拼错。
这三件事花不了几分钟,但每年都有人因为漏选、签名不一致和拼写错误丢分。
6. 踩坑记录与秋招笔试速查表
6.1 我踩过的几个典型坑
第一个坑是选择题的时间没控制好。我在一道关于HashMap并发问题的多选题上纠结太久,导致后面算法时间被压缩。复盘反思,笔试里遇到拿不准的题,可以先标记,做完后面的题再回来想,而不是卡在原地。
第二个坑是Kotlin协程代码分析题。给出了一段包含delay、withContext、async的代码,问最终输出顺序。我在是否切回主线程的问题上判断失误。这类题的关键是记住:async + await在同一个上下文中执行时依旧可以并发,但结果收集是顺序的;withContext不会新开协程,只切换调度器。平时多跑几段这种代码,比单纯背结论更有用。
第三个坑是环境准备。我用的开发机AGP版本和线上笔试环境不一致,编译报错浪费了不少时间。笔试前一定确认自己的Gradle、AGP、Android Studio版本能正常同步,并且提前测试白板代码环境,别等到开考才发现环境不对。
6.2 笔试常见问题速查表
| 问题类型 | 关键点 | 易错提醒 |
|---|---|---|
| Activity启动流程 | ATMS/AMS、Zygote、Binder | 别忘记进程创建由Zygote完成 |
| Handler机制 | Looper.prepare、ThreadLocal | 子线程需要手动prepare/loop |
| Service启动方式 | startService与bindService | onStartCommand返回值含义 |
| 进程优先级 | 前台>可见>服务>后台>空 | 记住服务进程在后台进程前 |
| HashMap并发 | 死循环、数据丢失 | Java 8后改用红黑树,并发仍不安全 |
| 协程挂起 | 状态机、Continuation | 挂起不阻塞线程 |
| MVVM | 单向数据流、ViewModelStore | 数据状态由ViewModel持有 |
| ANR | 输入5秒、广播前台10秒 | 主线程耗时是根因 |
| R8 | shrink/optimize/obfuscate | keep规则不要滥用 |
| AGP与Gradle | 版本要匹配 | AGP 8需Gradle 8+ |
| 内存泄漏 | Handler泄漏、静态引用 | 引用链要能讲清 |
| 事件分发 | dispatchTouchEvent返回true消费 | 分清三个方法职责 |
| Binder | 一次拷贝、MMAP | 区别于传统IPC两次拷贝 |
| 图片加载设计 | 分层+缓存+线程调度 | 别忘了生命周期感知 |
| ContentProvider | 跨进程、Binder | notifyChange观察者机制 |
6.3 给你的最后建议
笔试只是秋招长跑中的一站,真正决定你走多远的,是能不能持续把基础概念讲清楚。我在备考那段时间,最大的体会是:把每个知识点用自己的话写一遍,比刷十篇面经更有效。面经给你的是题目,而你能不能在笔试现场准确输出,取决于你对原理的理解深度。
如果你这周就要上考场,建议按这个顺序突击:先把四大组件、Handler、Binder这三个Android基石过一遍,把Java集合和并发的选择题刷一遍,再把LeetCode Hot 100里字符串和二叉树题过一遍。这三个模块占掉笔试60%以上的分数,优先拿稳它们,再去看R8、AGP、协程这些加分项。
最后分享一个小技巧:笔试前可以自己给自己做一次限时模拟,卡着120分钟做一套综合题,让自己适应边做题边标记不确定题目的节奏。这个习惯帮我稳住了很多本来会慌的场面。祝各位秋招顺利。