
1. 先说说这场笔试的整体情况1.1 考试形式与时间分配先交代一下背景。这套题是2023年秋招提前批的第一批也就是七月底八月初那会儿发的卷子。当时很多同学还在刷算法题Android专项准备不足就慌忙上了考场。整个笔试一共120分钟题目分四个模块单选20题、多选10题、简答3题、编程2题。总分100分单选和多选各占30分简答20分编程20分。从分数占比能看出来这家公司其实不太想靠选择题筛人真正的分水岭在简答和编程。选择题部分是典型的“题量大、时间紧”平均每题只有不到两分钟。其中Android基础占了一半以上涉及Activity启动模式、Handler机制、Binder通信、View绘制流程、进程间通信方式等。这些题目本身不算难但出题人会在选项里埋很多“看似正确实则偷换概念”的陷阱。比如有一道题问“singleTask启动模式下Activity的onNewIntent回调一定在onStart之前执行吗”如果你只背过启动模式的概念而不了解onNewIntent和生命周期方法之间的调用时序这题基本就挂了。多选则更狠错选、漏选都不得分完全不给蒙的机会。我记得有一道多选考的是“哪些情况会导致内存泄漏”选项里出现了“静态变量持有Activity引用”“Handler未移除Message”“单例持有Context”“ViewModel持有Activity引用”四个选项。前三个自然是正确答案但最后一个其实也正确因为ViewModel的生命周期一般跟随宿主如果你在ViewModel里持有Activity引用且没有在onCleared中释放同样会泄漏。这种题考的就是你在真实项目里有没有踩过坑。1.2 题目难易梯度与区分度整套题的难度分布呈“低-中-高”阶梯式上升。前面十道单选基本是送分题比如“四大组件有哪些”“IntentFilter的作用是什么”这部分只要系统学过Android基础就能答对。但到了第十题往后难度明显上来开始考源码层面的东西比如“ViewRootImpl在performTraversals中如何调度measure、layout、draw”这种题如果没有真正读过Framework源码大概率只能靠猜。简答题属于典型的“会者不难、难者不会”。三道题分别考Handler机制、Activity启动流程和内存泄漏定位。题目看起来都很常规但阅卷标准极其看重层次感。你如果只回答“Handler是用于线程间通信的”对不起这道题你只能拿及格分。你必须把Looper、MessageQueue、Message、Handler四者之间的关系讲清楚并说明为什么主线程的Looper不需要主动调用Looper.loop()之后还能持续运行以及epoll机制在其中的作用。只有把这些细节串联起来才能踩中得分点。编程题没有出偏题怪题一道是二叉树的层序遍历另一道是实现线程安全的LRU缓存。但第二道题加了一个Android场景的限制条件要求在内存受限环境下缓存容量不能超过给定阈值并且当超出阈值时要按最近最少使用策略淘汰。这个限制非常贴近实际业务毕竟移动端的缓存管理和服务端不太一样你不能像后端一样无限扩容必须在有限内存里做取舍。2. 按题型拆解核心考点2.1 选择题最容易被阴的细节选择题覆盖范围很广但核心集中在四个方向Java基础、Android四大组件、View体系、并发与内存管理。其中Android四大组件相关题目最多差不多占了8道。考法也比较固定基本都是围绕生命周期、启动模式、通信方式来出题。我印象最深的一道单选是“在onSaveInstanceState中保存的数据一定能在onCreate的savedInstanceState参数中恢复吗”答案是“不一定”。因为onSaveInstanceState只有在系统因配置变更或资源回收而销毁Activity时才会被调用如果用户主动按返回键退出系统不会调用它自然也就没有数据可以恢复。这道题如果你只是看过生命周期图而不理解每种销毁场景的区别很容易选错。还有一道关于Binder的选择题也很典型“Binder通信中一次完整调用发生了几次内存拷贝”答案是“一次”。传统IPC需要两次拷贝Binder通过mmap映射只需要在内核空间和用户空间之间拷贝一次。这道题是Framework面试的经典考点但在笔试里出现说明这家公司确实想招对底层原理有了解的人。关于Java基础的部分主要考了HashMap的底层实现、synchronized和ReentrantLock的区别、泛型擦除、注解的保留策略等。这些内容不像Android题目那么有场景感但也是在选择题里区分基础是否扎实的重要手段。有一个多选问“哪些集合是线程安全的”选项包括Hashtable、ConcurrentHashMap、CopyOnWriteArrayList、Collections.synchronizedList包装后的List四个选项全部正确。如果你平时只用过ConcurrentHashMap而对其他几个线程安全集合不熟悉很容易漏选。2.2 简答题考的是源码深度简答题只有三道但每一道都能拉开差距。第一道问Handler机制第二道问从点击桌面图标到Activity显示经历了哪些关键过程第三道问如何定位和解决内存泄漏。这三道题分别对应消息机制、系统启动流程和性能优化几乎覆盖了Android研发岗最核心的三个能力维度。第二道题特别值得说道说道。“从点击桌面图标到Activity显示经历了哪些过程”这道题的完整答案应该包含这样几个层次Launcher进程通过Binder通知SystemServer进程中的ActivityManagerService启动目标ActivityAMS检查Activity是否已存在、是否需要创建进程如果需要创建进程通过Zygote进程fork出应用进程应用进程入口是ActivityThread.main方法ActivityThread通过Binder向AMS报告进程已就绪AMS通知ActivityThread创建并启动ActivityActivity在onCreate中通过setContentView加载布局经过measure、layout、draw三个流程后ViewRootImpl将绘制结果提交到SurfaceFlinger合成显示。整个链路涉及Launcher、AMS、Zygote、ActivityThread、ViewRootImpl、SurfaceFlinger六个关键角色。大部分面试者能答出“AMS通知Zygote创建进程”和“Activity执行onCreate”这两步但会漏掉“ActivityThread通过Binder向AMS报告”这个关键环节。漏掉这一点说明你对App冷启动流程的理解停留在表面不清楚应用进程和应用代码初始化之间的桥梁是谁搭起来的。在笔试里这种隐含的“递进式追问”就是用来筛掉只背答案的选手的。第三道内存泄漏题相对常规只要答出LeakCanary、Android Profiler、MAT这三种工具以及静态引用、Handler、匿名内部类、资源未关闭这几个常见泄漏场景基本能拿满分。但如果你能额外写出“通过占用内存对象快照对比定位泄漏点”的方法那会给阅卷官留下不错的印象觉得你确实在项目里做过内存分析。2.3 编程题算法与Android场景题并重两道编程题一轻一重一算法一工程。第一道二叉树层序遍历是LeetCode原题属于“送分题”级别的经典题。但要注意的是笔试平台要求实现BFS并返回ListList 如果你平时习惯了用递归DFS写法现场改成队列迭代得花几分钟适应。我当时写这道题只用了不到五分钟但为了保险还是加了一个注释说明“非递归版本使用Queue实现层级遍历”让阅卷人能看到你的思路。第二道LRU缓存则是典型的“披着Android外衣的算法题”。题目要求实现一个线程安全的LRU缓存容量固定当put超过容量时淘汰最近最少使用的key。这个题在Java层面就是LinkedHashMap加synchronized的问题但难点在于题目额外要求“并发访问时get和put都须保持线程安全”。这意味着你不仅要实现LRU逻辑还要考虑锁的粒度。如果直接在get和put方法上加synchronized性能会很差更优的做法是用ConcurrentHashMap保证键值存取线程安全同时用读写锁保护双向链表的修改。如果能答出“LinkedHashMap synchronized”和“ConcurrentHashMap 读写锁 手动链表”两种方案并说明各自的适用场景和性能差异这道题基本就稳了。3. 五道典型真题的复盘与解题思路3.1 编程题二叉树层序遍历原题大致是给定一个二叉树返回其节点值的自底向上的层序遍历。即按从叶子层到根层的顺序逐层从左向右遍历。我是这样写的class Solution { public ListListInteger levelOrder(TreeNode root) { ListListInteger result new ArrayList(); if (root null) return result; QueueTreeNode queue new LinkedList(); queue.offer(root); while (!queue.isEmpty()) { int size queue.size(); ListInteger level new ArrayList(); for (int i 0; i size; i) { TreeNode node queue.poll(); level.add(node.val); if (node.left ! null) queue.offer(node.left); if (node.right ! null) queue.offer(node.right); } result.add(level); } // 如果题目要求自底向上就 Collections.reverse(result); return result; } }注意代码里的size变量必须先取出来因为queue.size()在循环过程中会变化。很多人在白板编程时容易忽略这一点导致内层循环边界错误。这类细节虽小但在笔试平台的用例测试里会直接暴露。3.2 编程题线程安全LRU缓存第二道题要求实现线程安全LRU缓存完整题目描述是这样的设计一个LRU缓存类支持get和put操作。构造时传入缓存容量capacity。get(key)返回value不存在返回-1。put(key, value)若key已存在则更新value否则插入。缓存满时淘汰最近最少使用的键。所有方法必须线程安全。我当时是用LinkedHashMap synchronized实现的因为LinkedHashMap自带accessOrder模式可以在get时把被访问的节点移动到链表尾部天然满足LRU语义。但笔试平台按用例判分所以如果你需要在非同步环境下追求更高并发度可以考虑ConcurrentHashMap 手动双向链表 ReentrantReadWriteLock的方案。不过考虑到笔试时间有限LinkedHashMap这种“一行代码搞定LRU”的方案在可读性和正确性上都更稳妥。public class LRUCache { private final int capacity; private final MapInteger, Integer map; public LRUCache(int capacity) { this.capacity capacity; this.map new LinkedHashMapInteger, Integer(capacity, 0.75f, true) { Override protected boolean removeEldestEntry(Map.EntryInteger, Integer eldest) { return size() capacity; } }; } public synchronized int get(int key) { return map.getOrDefault(key, -1); } public synchronized void put(int key, int value) { map.put(key, value); } }关于采用“LinkedHashMap synchronized”这种方案我需要说明一点这属于笔试场景下兼顾正确性与时间成本的选择。它能够满足题目“线程安全”的要求而且在代码审查时也好解释。但如果你在正式项目里遇到高并发场景synchronized会让所有读操作串行化成为性能瓶颈。到时候你再换成分段锁或者基于LongAdder的统计方案也不迟。3.3 简答题Handler机制的完整回答简答题第一道“简述Handler机制”的完整答案我在这里直接给你示范一下怎么组织逻辑。先总述Handler机制是Android消息处理的核心框架由Looper、MessageQueue、Handler、Message四个核心类组成然后依次说明每个类的职责。重点在Looper.loop()的循环判断上为什么主线程looper无限循环却不会卡死UI。这里要提到主线程的MessageQueue在无消息时会触发epoll wait进入阻塞状态让出CPU等到有消息到来时才被唤醒所以即使无限循环也不会造成CPU空转。再往下要讲清楚线程和Looper的绑定关系。主线程在ActivityThread.main方法中通过Looper.prepareMainLooper()初始化Looper子线程则需要手动调用Looper.prepare()和Looper.loop()。Handler在构造时通过Looper.myLooper()获取当前线程的Looper从而将handler与特定线程绑定。这个“handler与创建它的线程绑定”是关键很多人误以为handler属于主线程其实handler属于创建它的线程。最后补充内存泄漏的隐患。非静态内部类Handler默认持有外部Activity的强引用当handler有延迟消息未处理时Activity占用的内存无法被回收。解决办法是把Handler定义成静态内部类并在onDestroy中调用removeCallbacksAndMessages(null)移除所有消息。把这个层次写到简答题里基本上这道题可以拿满分。3.4 简答题从点击图标到Activity显示这道题是整套卷子里最能体现“源码深度”的一道题。完整回答分六步第一用户点击Launcher上的图标Launcher进程通过Binder调用ActivityTaskManagerService.startActivity。第二ATMS里经过一系列Intent解析找到目标Activity所在的进程是否存在。如果进程不存在则向Zygote进程发送创建进程的请求。第三Zygote通过fork创建新的应用进程进程入口是ActivityThread.main。ActivityThread.main做的事情非常关键创建主线程Looper同时调用Looper.loop()开启消息循环。第四ActivityThread在main方法中通过ActivityManagerService的Binder接口注册到AMS告知进程已就绪。第五AMS通过Binder告诉ActivityThread现在可以创建Activity了。ActivityThread通过Instrumentation反射创建Activity实例然后启动生命周期。第六onCreate中setContentView解析布局交给ViewRootImpl执行performTraversals完成测量、布局、绘制最终通过SurfaceFlinger合成上屏。每一步之间都有明确的Binder调用关系。如果能把“ActivityManagerService”和“ActivityTaskManagerService”的区别说清楚ATMS是Android 10之后从AMS中拆分出来的专门负责ActivityTask管理那会更加分。这个点在笔试时很少有人能答出来因为大多数资料还停留在老的AMS名称。3.5 Kotlin协程题生命周期与调度这套卷子里没有直接出Kotlin协程的大题但在多选题里出现了几道与协程相关的题目。比如“关于协程的调度器以下说法正确的是”选项涉及Dispatchers.Main、Dispatchers.IO和Dispatchers.Default的区别。这里有一个很容易搞混的点Dispatchers.IO用于网络和磁盘IO操作Dispatchers.Default用于CPU密集型计算而Dispatchers.Main用于更新UI。三者之间有大小不同的线程池切换调度器不会阻塞当前线程。另外一道多选问“协程的launch和async有什么区别”考察点在于launch返回Job、async返回Deferredasync通过await获取返回值。如果你在项目里用协程做过网络请求封装这题就是送分的。但如果只是熟悉基础语法而没真正写过协程容易把两者的返回类型搞错。字节跳动的面试风格比较偏爱Runtime和C底层但这套笔试题明显更偏业务开发说明这家公司更看重你日常开发中对Kotlin的熟练度。4. 笔试现场踩过的坑与答题技巧4.1 时间分配策略先做编程题还是先做基础题我不知道别人是怎么安排的但我的建议是拿到试卷后先花30秒扫一遍所有题目然后立刻去做编程题。为什么因为编程题除了正确性之外还需要时间调试而选择题如果一时想不起来可以先蒙一个答案标记一下后面有时间再回来看。编程题如果放在最后做一旦陷进去就来不及调整而且心理压力会影响后面的答题质量。我当时是在最开始十分钟内把二叉树层序遍历写完确认用例通过后才回头做前面的选择题。第二道LRU缓存题我留到简答题做完后用大概四十分钟完成。整体节奏是前60分钟干掉所有基础题和简答后面60分钟全部投入编程题。120分钟刚好用完没有出现最后五分钟才匆忙交卷的情况。如果你擅长算法题且刷题量足够可以直接先攻编程题用全神贯注的黄金时间把两道编程题先解出来建立优势。如果算法底子一般建议先花10-15分钟把选择题快速过一遍把确定的答案填上不确定的先标记再回头花时间做简答题和编程题。4.2 简答题的答题节奏与字数控制简答题切忌长篇大论。每道题控制在200到300字但必须分点作答。我见过很多同学在“Handler机制”这道题上写了整整一页A4纸从Linux epoll原理讲到Android消息池复用虽然知识面很广但核心得分点反而被淹没在大量细节里。阅卷人的精力有限你需要在每个得分点上用关键词打头比如“Looper.prepare()”“Looper.loop()”“MessageQueue.next()”“epoll挂起”在这些关键词后面展开一两句说明即可。用“段落关键词前置”的格式来组织答案很稳妥比如第一句直接写“Handler机制的核心是Looper不断从MessageQueue中取出消息并分发”然后分条展开。这种回答方式既能让阅卷人快速抓到重点也方便你自己控制篇幅。简答题部分如果时间充裕可以适当补充一些“进阶内容”作为加分项。比如在Handler机制的答案末尾加一句“主线程的Looper在ActivityThread.main中通过prepareMainLooper初始化而子线程Looper需要手动调用prepare和loop”这不算凑字数而是展示你真的理解Looper和线程的绑定关系。4.3 编程题的环境与IDE问题笔试平台一般用牛客或者赛码这两个平台的代码编辑器在Android语法高亮方面支持并不算好。更麻烦的是这两个平台默认支持的Java版本可能只到Java 8如果你在本地一直用Java 11以上的新语法比如var关键字在平台上可能会直接编译报错。第二个常见的坑是输入输出格式。LeetCode让你直接实现核心函数但笔试平台很多题目需要自己处理输入输出。比如二叉树层序遍历LeetCode版本直接给我们TreeNode根节点但笔试平台可能会用一维字符串表示树比如“1,2,3,null,null,4,5”需要你先解析字符串手动构建二叉树再实现层序遍历。这一步解析逻辑很容易出错建议提前熟悉“根据层序序列构建二叉树”的代码模板。第三个坑是自动保存问题。我在牛客上做过几次笔试遇到过代码写完后系统提示“草稿未保存”的情况。建议每写完一个函数就手动点一次保存不要把所有代码写完再保存。虽然大多数平台支持自动保存但自动保存有延迟万一浏览器意外关闭或网络断开代码可能丢失。4.4 心态和取舍原则第一批笔试通常不会直接给offer更多是拿到后续面试的入场券因此你要确保的是“过线”而不是“满分”。如果某道选择题你完全没思路果断选一个看起来最合理的答案并标记不要在上面停留超过一分钟。同样如果简答题某道题只能答出一个层面那就只写这个层面不要硬撑篇幅以免把确定的内容写错。我在做题时给自己定了一个原则编程题如果想了十五分钟还没有完整思路立刻切换到简单题模式先写出暴力解法确保用例能过一部分拿部分分。笔试平台通常是按通过的测试用例比例给分的暴力解法能拿到30%到50%的分数比交白卷强得多。5. 针对这份笔试怎么准备才有效5.1 Framework源码要读到什么程度笔试里考到的Framework内容主要集中在Handler机制、Activity启动流程、AMS和Binder这四块。不要上来就想读全所有源码那很容易劝退。我给你的建议是“由点到面”先把Handler机制的Java层源码完整读一遍从Handler发送消息到Looper取出消息再到dispatchMessage整个过程一定要手画出时序图把方法调用关系理清楚。然后读Activity启动流程跟着startActivity方法一路往下走不管中间经过多少层Binder调用先记住主链路。启动流程这块2023年之后不少资料已经更新为ATMSActivityTaskManagerService因为从Android 10开始Activity任务管理从AMS中拆分到了ATMS。笔试如果考启动流程你主动写出ATMS这个名称会比只写AMS要加分它能说明你关注过较新版本的源码变化。5.2 算法题怎么刷才有效笔试的算法题难度基本集中在LeetCode中等题。如果你时间有限优先刷这些高频题二叉树遍历前中后序、层序、二叉树的最近公共祖先、链表反转、环形链表、最长不重复子串、最长回文子串、接雨水、LRU缓存、三数之和、岛屿数量、两个栈实现队列、手撕快速排序、二分查找变体、动态规划入门类爬楼梯、打家劫舍、最长递增子序列。其中LRU缓存几乎是大厂的标配不管笔试面试都可能出现值得单独拿出来练习。建议你分别用LinkedHashMap和手写双向链表各写一遍两套解法都能在白板上流畅写出来才算真正掌握。5.3 项目经验怎么在笔试里体现笔试题虽然不直接考项目但简答题的“加分项”往往来自项目的真实经验。比如内存泄漏那道题如果你在项目里使用过LeakCanary并且遇到过某个版本库导致的泄漏随手就能举出具体的泄漏类型和修复方案。这类实战案例比干巴巴背“Activity泄漏有四种场景”要生动得多。我建议每位参加秋招的同学都准备一段“我在项目里做过什么优化”的叙述性文字包括优化前的问题数据、优化手段、优化后的效果。笔试简答题虽然没有直接问项目但遇到“如何定位和解决XXX问题”这类问题时项目经验就是你和其他候选人拉开差距的关键。5.4 2023年特有的新技术点2023年的Android技术栈相比前两年有明显变化。如果你是准备提前批除了常规的Java和Android基础外建议关注以下几个方向Kotlin协程已经成为必考重点要看结构化并发、Job和CoroutineScope的关系、Flow的冷流与热流、withContext的线程切换成本。Jetpack Compose虽然还没到“每个公司笔试都考”的程度但很多公司在笔试里会出关于状态提升、重组范围、remember和rememberSaveable的区别这类选择题这些内容即使没写过Compose花半天看文档也能应付。性能优化方向的热度一直很高2023年尤为突出的是启动优化和包体积优化。笔试可能会通过选择题形式考你比如“哪些方式能减少APK体积”“R8和ProGuard的区别”。如果你对R8的工作流程shrink、optimize、obfuscate、preverify有基本了解这些题目不会难住你。5.5 关于车载、OTA、AMS等进阶方向如果你关注过2023年的招聘信息会看到很多车企和硬件公司在大量招聘Android Framework工程师他们在笔试中会更侧重AMS、OTA、蓝牙、蓝牙协议栈、OpenOCD调试这些方向。这批笔试虽然还是标准的通用Android技术栈但在简答题里出现了AMS相关题目说明公司可能在techlead层面就有OS定制类的项目。具体来说如果你投递的岗位偏向“车载方向”笔试大概率会增加这几个考点设备OTA升级流程、系统分区和动态分区、A/B分区的差异、如何通过OpenOCD在Android设备上连接JTAG调试、蓝牙A2DP和HFP协议的基本差异。这些内容没有实际参与过项目很难答好如果你是非资深Android工程师不建议笔试时硬写猜到哪写到哪反而会让面试官觉得你不严谨。其实我最想说的是这张卷子整体并不难难的是在有限时间内把每个知识点都答到得分点上。基础题所有人都能答真正决定你能不能进面试的是简答题里有没有写出“ActivityThread.main中初始化Looper”这样的细节以及编程题第二题的实现有没有考虑线程安全。这些细节不是靠考前突击能记住的更多来自平时写代码时的积累。如果时间重新来一次我会在准备笔试时多花两周读Handler和AMS源码的Java层实现而不是把时间全花在刷LeetCode简单题上。