做一个Android研发岗的笔试复盘,其实比做面经更有意思。2023年度小满春招第三批笔试,整体给我的感觉就是“筛选意味很浓”:前面的选择题只是热热身,真正拉开差距的是后面的源码题、设计题和工程排查题。它的考点分布遵循一条非常清晰的逻辑——基础扎实度、源码阅读习惯、系统机制理解、工程落地能力,层层递进。
这篇文章我会把笔试的题型分布、出题意图、核心知识点拆解、常见坑点以及备考思路完整复盘一遍。不夸张地说,这套题做完一遍,你对“Android开发到底在考什么”会有很清晰的认知。适合准备春招秋招的应届生、打算跳槽的Android工程师,以及想系统补齐Framework和技术深度、但平时又被业务需求追着跑的朋友。
1. 笔试整体设计与出题逻辑
1.1 春招第三批的定位:难度不是偶然的
小满的春招是分批组织的,每一批笔试的定位其实不太一样。第一批通常覆盖基础知识和常见原理,很多人提前刷过题就能过;第二批会加入一些中高难度的原理辨析;到了第三批,考官的意图已经从“筛掉不会的”变成了“筛出真正能干活、能扛事的人”,所以题目明显更偏综合场景和源码细节。
从题型结构上看,这套笔试卷大致可以分为四类:
| 题型 | 大致占比 | 考察目标 |
|---|---|---|
| 单选/多选题 | 25% | 基础概念是否精准,有没有记过源码细节 |
| 简答题 | 30% | 原理理解深度,能不能用结构化语言讲清楚机制 |
| 代码题 | 25% | 手写能力、边界处理、内存与性能意识 |
| 场景设计题 | 20% | 系统设计思维、问题排查能力、方案落地能力 |
这里想特别说一句:选择题并不是送分题。第三批的选择题里出现了不少“反套路”的选项,比如把Activity在不同异常情况下的生命周期混在一起,把Handler消息同步屏障和异步消息的区别放在一起,把透明度颜色值的换算藏在View的背景里。你如果只是背过结论而没有推演过过程,很容易在选项之间犹豫。
1.2 出题人真正想看什么
企业招人不是想找一个“刷题机器”,而是想找一个能处理线上问题、能读懂系统源码、能给出可落地方案的工程师。所以第三批笔试题里,源代码级的考点占的比重非常大:
- ActivityThread与AMS的协作流程
- Binder一次拷贝的原理
- Handler同步屏障的实际用途
- R8压缩与ProGuard的差异
- OTA升级与Apex模块化机制
- 分区存储与FileProvider的适配
- MVVM架构中数据流的正确使用方式
这些考点背后有一条共同的逻辑:你有没有主动去读源码的习惯。很多面试者在简历里写“熟练使用Jetpack”,但一问到LiveData为什么黏性、ViewModel为什么在配置变更后还能存活,就答不上来了。笔试也是一样的,题目可以不会,但你要让阅卷人看到你的思考脉络——是先分析问题、再定位模块、最后给出结论,而不是上来就堆术语。
2. 基础考点与高频热点深度拆解
2.1 Java与Android基础:细节决定成败
这一部分是整张卷子的地基。内容本身不难,但非常考验“记得准不准”。
Java基础方面,高频考点集中在JVM内存区域划分、GC算法与回收器选择、synchronized与Lock的区别、volatile的可见性与有序性、Java内存模型(JMM)等。这些题几乎不存在“临场发挥”的空间,你必须能准确说出:对象头里Mark Word存了什么、偏向锁什么时候升级为轻量级锁、CAS失败后CPU怎么处理。我的建议是,复习时不要只看博客,直接去读《深入理解Java虚拟机》的对应章节,再把R8、G1、ART GC的差异拉一张表出来对比。
Android基础方面,四大组件、Handler、AsyncTask、进程优先级这些老生常谈的内容照样出现,但问法更偏工程场景。比如有一道题是问FileProvider的:给出一个content://com.baidu.searchbox.fileprovider/baiddpath/android/data/com.baidu.searchbox/files/download/xxx.apk这样的Uri,问你如果直接在外部App里通过file://的方式访问这个下载目录会怎样。这题考点很明确:
- Android 7.0之后,App内部不能直接使用file:// Uri暴露文件,否则会抛FileUriExposedException。
- FileProvider生成的content:// Uri通过grantUriPermission或Intent.addFlags进行临时授权。
- android/data目录是分区存储模式下受保护的系统目录,外部App在没有授权的情况下无法直接遍历访问,即使你拿到了路径,也不能随意读取。
再比如有一道存储相关题,题目给了一个/storage/emulated/0/Android/data/xxx/files/Download这样的路径,问你在targetSdk 30以上的App里能否直接通过File对象去访问其他应用的这个目录。答案是“通常不能”,因为Android 11开始强制分区存储,即使是同级App,也不能随意跨应用读取这个受保护目录下的文件,除非对方通过FileProvider授权给你。
2.2 自定义View、UI适配与动态图标
这部分是很多人的弱项,因为日常开发中真正需要手写View的机会不多,但笔试偏偏爱考。
事件分发几乎是必考的:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent的传递顺序,以及消费、拦截在不同情况下如何组合。我当时答这道题时,干脆把流程图用文字描述清楚,重点写了“DOWN事件没有被消费时,后续的MOVE和UP也不会再派发到这个ViewGroup”这个边界情况。
View的绘制流程也一样,measure、layout、draw三大步骤的执行顺序,MeasureSpec的三种模式,自定义View wrap_content为什么需要重新计算,以及requestLayout和invalidate的区别。这些都是老题,但第三批的考法更细:比如问你在onDraw里直接修改View的宽高会不会生效,答案是“不会”,因为onDraw阶段已经过了measure和layout,修改宽高需要调用requestLayout重新走流程。
热词里反复出现的“Android动态图标主题”也值得展开。Android 13开始支持Themed Icons,也就是用户在开启“主题图标”后,系统会把启动器中的图标统一映射成单色的主题风格。对这个功能,你至少要答出几点:
- 适配方案是提供一个带有monochrome属性的图标图层。
- 系统会根据当前主题和壁纸取色来渲染这个单色图标。
- 动态图标的实现需要适配不同厂商的ROM。
当时我直接写了一段名为ic_launcher_monochrome的XML示例,再结合“Android AppCompat”的配置说明了一下兼容性,这类题就稳了。
颜色透明度对照也是一个“常见但总有人记错”的点。比如0x99是60%透明度、0xAA是67%透明度、0xCC是80%透明度,如果问你setColor(Color.parseColor("#80FFFFFF")),你要能快速反应出这是50%透明度。我笔试前整理了一张常用透明度十六进制对照表,建议你也保存一份:
| 透明度 | 十六进制值前缀 | 说明 |
|---|---|---|
| 0% | FF | 完全不透明 |
| 20% | 33 | 比较透明 |
| 50% | 80 | 半透明 |
| 70% | B3 | 轻微透明 |
| 90% | E6 | 接近不透明 |
这张表看起来简单,但到了手写代码题里,你随手写错一个色值,UI验收时就是一场事故。
3. Framework与系统级原理:真正拉开分数的地方
3.1 AMS、Binder与消息机制
到了第三批笔试,源码级题目已经成为主角。Activity启动流程是绕不过去的一道题:从Activity.startActivity到AMS,再到ActivityThread的handleLaunchActivity,最后到onCreate回调,这条路上经过哪些核心类、跨了几次进程、Binder调用发生在哪个环节,能写出来的人并不多。
Binder机制的原理也很常考:为什么比传统的System V共享内存更适合Android进程通信,一次拷贝是指什么,mmap映射在哪里起到作用,Binder线程池是怎么维护的。你得把这些讲清楚,而不是只说“Binder是Android的IPC方式”一句话。
AMS(ActivityManagerService)本身也是个高频考点。它管理什么:Activity栈、Task与启动模式、进程调度相关广播与Service的生命周期。可以顺便提一下你在看“Android AMS”热词时自然会遇到的那些衍生问题,比如最近任务列表为什么有的Task不显示、singleTask启动模式在跨App时表现如何。
消息机制方面,Handler、Looper、MessageQueue三件套是必问的,但第三批考得更深的是:同步屏障(SyncBarrier)与异步消息。比如问“为什么Choreographer的VSYNC回调能优先于普通消息执行”,答案就是同步屏障拦截了同步消息,让异步消息可以先执行。这个细节如果你没读过MessageQueue的源码,大概率是答不出来的。
3.2 OTA、Apex与系统安全相关的硬核内容
这一块是很多Android笔试题的“隐藏加分项”,因为大多数面试者是完全不看的。但小满第三批笔试里居然出现了系统升级与系统组件模块化的题,足见这家公司对系统底层能力有要求。
OTA升级离不开安全启动校验和分区管理。A/B升级机制把系统分区做成两组槽位,当前槽位跑着系统,另一个槽位空闲。升级包下载完成后写入备用槽位,重启时bootloader切到新槽位启动,如果启动失败还可以回滚到旧槽位。这套机制和Android 14里Magisk模块升级后失效、root方案需要重新适配等现象都有关联:
- 升级后系统分区被替换,原有的修改会被覆盖。
- 启动时要校验vbmeta签名,任何对系统分区的篡改都会导致校验失败。
- root或者高度定制系统时,升级不是简单的“点击安装”,而是要先解包payload然后重新打包。
Apex模块化则是Android 10引入的重要机制。它把Runtime、Media、Tethering等系统组件封装成独立的apex包,可以独立于系统OTA进行更新,解决了“系统组件每次都要跟着整包升级走”的问题。你在笔试题里看到com.android.runtime、com.android.media这种包名,要能识别出这是APEX模块。
另外,Android 10之后对设备存储和硬件模块的访问也变得更加严格,出现了i2c-tools这类底层调试工具在Android上使用的场景。笔试里如果给你一个硬件调试题,你要懂得:i2c-tools在Android上要么通过ADB shell执行,要么需要root权限在底层总线上去读传感器寄存器;这种题的背后其实是考察你对“Android碎片化生态下系统定制与驱动调试”的认知。
3.3 系统App、蓝牙与底层调试
有一道题让我印象很深:关于PhoneStateListener和TelephonyManager的。它问你:如何监听电话状态、网络信号变化、SIM卡状态更新?如果你在应用层做过系统级开发,答案应该是注册TelephonyManager.listen,然后传入PhoneStateListener的子类重写onCallStateChanged、onSignalStrengthsChanged这些方法即可。但如果你深挖一层:系统服务如何把电话状态回调到应用层的PhoneStateListener?这就涉及ITelephonyRegistry这个Binder接口,以及监听器注册与事件回调的底层链路。
Android蓝牙开发也是热词里的常客。要答好这类题,你至少得知道三类内容:
- 经典蓝牙的发现、配对、连接、Socket通信。
- BLE的GATT协议、Service/Characteristic/Descriptor三层模型、扫描回调与连接间隔。
- 蓝牙权限在Android 6.0、Android 12、Android 13中的变化,特别是BLUETOOTH_SCAN、BLUETOOTH_CONNECT这类运行时权限。
底层调试方面,Android车载与嵌入式场景会用到openocd、JTAG/SWD调试和Fastboot工具。这类题对普通应用开发者来说偏难,但如果你面试的是车机方向或系统开发岗,基本就是必考项。比如openocd通过JTAG接口直接连到SoC上,把Android系统的早期启动日志抓出来,再去定位bootloader、kernel或者init阶段的问题。顺带一句:Android车载场景还会频繁出现多屏互动、Camera HAL、车辆属性回传这些高度定制化的内容,遇到这类题,必须把“Android Automotive OS的架构分层”表述清楚。
4. 架构设计、性能优化与工程化实践
4.1 MVVM与Jetpack:不止是会调API
MVVM架构在笔试里相对友好,因为它有一定套路:ViewModel负责持有和管理UI相关数据,LiveData或StateFlow负责数据驱动UI更新,Repository层屏蔽数据来源。但工程化水平更高的团队,会追问“你的数据流是怎么设计的”。
比如有一道题,给出一个MVVM代码片段,让你指出问题。答案是:ViewModel里直接持有View引用导致内存泄漏,LiveData的setValue和postValue混用导致多线程环境下数据丢失,以及Repository层没有做网络请求的生命周期绑定。这些点如果平时只是在项目里“照着模板写”,很难发现。
架构设计题中,“android Settings布局”也是系统级App开发中会遇到的经典场景。它问的是:系统Settings应用怎么组织上千个设置项?答案核心是Preference机制,以及PreferenceFragmentCompat的列表化管理。你需要知道PreferenceScreen、PreferenceCategory、SwitchPreferenceCompat、ListPreference等控件的使用方式,以及系统Settings里如何通过Intent筛选和动态加载不同的偏好项。
4.2 性能优化、测试与工程效率
性能优化题是区分“会写代码”和“会写高质量代码”的分水岭。小满第三批里出现了一道启动优化场景题:冷启动过程中,Application的onCreate里同步初始化了十个第三方SDK,导致启动耗时严重,你怎么优化?
一般人的思路是“把初始化放到子线程”,但你要答出:哪些SDK必须放在主线程初始化,哪些可以异步,哪些其实完全不需要在Application里初始化,可以考虑按需初始化。这只是第一步,后面还要加上启动耗时打点、启动器TaskGraph之类的手段,甚至用字节码插桩方式监控每个初始化器的耗时。
R8代码压缩与混淆也是必考项。你要清楚R8与ProGuard的区别:R8在AGP 3.4之后默认开启,融合了压缩、优化、混淆、脱糖四个步骤,压缩资源和移除无用代码的能力更强。笔试中常见考点是:keep规则怎么配、反射类为什么要keep、为什么release包崩溃堆栈无法直接看、如何利用mapping文件还原。
Android测试方向也会来一两道送分题:单元测试JUnit、UI测试Espresso、MockWebServer隔离网络数据、Robolectric在本地跑Android环境相关测试。如果你在简历里写了“有质量意识”,至少要知道Instrumentation测试和单元测试的区别。
工程效率方面,Android Studio版本与AGP版本的对应关系也是热门考点。比如Android Studio Hedgehog 2023.1.1 Patch 2支持哪些AGP版本,答案是它支持AGP 8.0到8.1,如果项目里用的是AGP 8.2,就需要升级到对应版本的Studio或者手动指定Gradle版本。这类问题考的不是背版本号,而是你懂不懂“Studio与AGP之间有一个兼容矩阵”这个工程概念。
还有一个非常常见的报错是:could not load compiled classes for settings file ...,这个通常是Gradle缓存损坏、JDK版本不匹配或者settings.gradle脚本执行出错。要按顺序排查:先看Gradle版本和AS版本匹不匹配,再清理~/.gradle/caches,最后再检查settings.gradle里有没有引用不存在的项目目录。
5. 笔试实战策略与避坑经验
5.1 答题顺序:先把能拿的分拿住
我的建议是,拿到卷子先花两分钟扫一遍题目,把“一眼就知道答案”的选择题和简答题先做掉,然后再去啃代码题和设计题。不要在第一道选择题上卡太久,这种笔试的题量通常偏大,一道题磨十分钟,后面代码题就没时间写了。
代码题优先写思路、再写实现。比如手写一个LRU Cache,你先写“用LinkedHashMap或双向链表+HashMap实现,O(1)查询和删除”,再补代码,阅卷人会看到你是真懂。哪怕代码有小bug,思路清晰也能拿到大部分分。
5.2 高频易错项清单
整理几个我在备考和考试中反复遇到的易错点,分分钟能救你一道题:
- Application的onCreate里不要做耗时操作,更不能直接开线程做SharedPreferences读大json,因为异步写入没有回调会造成数据丢失。
- Handler中创建内部类持有Activity引用会造成内存泄漏,必须用静态内部类+WeakReference或者Lifecycle-aware的协程。
- Android 12以上,FileProvider的grantUriPermission只对Intent里携带的Flag有效,没有Flag时就算Uri暴露了,对方也无权访问。
- 动态图标的monochrome是单色图层,不能直接放彩色图,否则系统主题图标功能失效。
- R8混淆后,枚举和注解默认是被优化的,如果你的代码里通过getDeclaredFields获取枚举字段,需要加keep规则。
- 协程 + LiveData的组合里,viewModelScope默认是Main dispatcher,但要考虑到数据量大时必须切IO线程。
这些易错项都是工程中真实踩过的坑,笔试里出现它们的概率极高。
5.3 笔试之后的面试衔接
笔试做完了不代表结束,很多题其实都是面试的引子。你写完AMS启动流程,面试官下一句可能就是“如果启动流程中Service没绑定成功,你怎么沿着AMS的日志去排查”;你写完OTA与Apex机制,面试官可能会问“你平时怎么做系统版本兼容”。
我的建议是:笔试后把每道答错的题、没答完整的题都记录下来,每个知识点补一篇源码级笔记。不要只背结论,而是跟着源码跑一遍流程,记录类名、方法名、关键调用栈。这样到面试环节,你可以随时用笔试中遇到的真实题当话头,把话题引到你擅长的领域去,掌握面试节奏。
写在最后的一点心得
笔试这东西,对基础扎实的人来说是展示,对临时抱佛脚的人是煎熬。我在复盘小满第三批笔试时最强烈的感受是:出题人几乎没有出偏题怪题,所有考点都是日常开发中真正用得上的东西,只是要求你从“会用”变成“懂原理”。
我个人实际备考时的经验是,不要死记硬背各种知识点总结,而是画自己的源码调用图、写自己的性能优化踩坑记录、维护自己的版本兼容清单。当这些东西变成你的一部分之后,笔试自然会成为你展示工程素养的舞台。
最后再分享一个小技巧:如果笔试时间紧张,优先级可以这样排——单选多选必拿,简答题挑最有把握的先写,代码题哪怕只写伪代码也要把思路写清楚,设计题务必画清模块边界和数据流向。放心,你在纸上画下的每一根连线,阅卷人都看在眼里。