每年到了秋招季,总会收到不少学弟学妹的消息,问“Android岗笔试题到底考什么”、“怎么准备才不白费劲”。我翻了翻自己整理的面经笔记,里面存着爱奇艺2020校招Android方向笔试题(第二场)的完整回忆版。说实话,这套题在当年算是比较有代表性的——它不像有些大厂那样上来就是四道hard级算法题压场,也没有完全放飞考一堆偏门框架源码,而是很踏实地把数据结构与算法、Java基础、Android核心机制和一部分工程实践问题揉在了一起,覆盖面广,但又没有特别离谱的超纲内容。
这篇文章我就以这套题为主线,把每一类考题背后的考察意图、核心知识点的理解方式、实际答题时的思考路径,以及我在复盘过程中积累的备考经验一次性说清楚。不管你是正在准备校招的应届生,还是想系统梳理Android知识体系的在职开发,这篇内容都应该能帮你少走一些弯路。
1. 这一场笔试到底考察什么:岗位画像与命题逻辑
很多人拿到笔试题第一反应是“赶紧刷题”,但我觉得先搞清楚“出题人想招什么样的人”更重要。爱奇艺的Android方向岗位,说白了要的是能直接上手做业务、同时对底层原理有一定感知的工程师。视频类App的业务复杂度摆在那里,播放器、弹幕、评论、缓存、个性化推荐,这些功能对内存占用、渲染效率、网络策略都有比较高的要求,所以笔试题目会明显偏向那些“工作中真正会用到的知识”。
1.1 从招聘岗位说起:爱奇艺Android方向的岗位画像
视频类App的Android开发,日常打交道最多的几块内容:播放器内核的对接与封装、列表页的流畅度优化、大图与视频缓存策略、多线程下载与断点续传、复杂的UI状态管理。这就决定了面试官在出题时,一定会重点关注你对以下几个维度的掌握程度:
- Java语言基础是否扎实,尤其是集合类、并发工具、JVM内存模型这些“高频考点”。视频App在播放过程中会有大量的异步任务和缓存读写操作,稍不注意就会踩到线程安全或内存泄漏的坑。
- Android 四大组件与消息机制的理解深度,特别是 Handler、Looper、Activity 启动模式这些“必问题”。弹幕推送、播放状态回调、界面刷新,所有东西都绕不开这套底层机制。
- 数据结构与算法,重点在链表、二叉树、字符串处理和动态规划这些“常规题型”。笔试面试不可能像竞赛那样花式炫技,大部分题目考的就是你能否在限定时间内写出清晰、正确的代码。
- 对性能优化和常见崩溃问题有没有实际排查经验。视频类App特别看重这点,内存抖动、卡顿、ANR、OOM,这些都是很现实的问题。
所以你可以把这场笔试理解成一次“准入门槛体检”——它不指望你把所有题目都答得完美,但会通过一套题快速筛出那些基础扎实、思维清晰、有工程感觉的候选人。
1.2 第二场的整体命题风格与题型分布
爱奇艺2020校招Android方向的第二场笔试,整体题量与时间设置是标准的“45分钟选择题 + 90分钟编程题”模式。选择题覆盖了计算机基础、Java、Android、网络协议等模块,编程题则是两道算法题加一道安卓相关的设计/实现题。
从难度梯度来看,选择题里大概有60%属于“背过就能答”的基础题,30%需要你真正理解机制之后才能推导出答案,剩下10%属于拉开差距的“陷阱题”。编程题的第一道一般比较温和,属于“练过就会”的水平;第二道开始考察边界处理与代码鲁棒性;最后一道Android题看起来像是在写代码,实际上是在考察你对组件生命周期、异步任务、内存管理等工程知识的综合运用。
说白了,这套题玩的不是“偏难怪”,而是“你能不能把学过的知识在压力下稳定输出”。
2. 核心考点详解:数据结构与算法题的解题思路
算法题是校招笔试的硬骨头,也是很多人最怕的部分。但我复盘爱奇艺这套题之后发现,它的算法题并不追求“炫技”,而是老老实实地考察基本功。这里我把几类高频考点展开讲讲,并给出我在实际答题时的思考路径。
2.1 链表类题目:边界条件是重中之重
链表题几乎是所有大厂笔试的“保留节目”。原因很简单:链表涉及指针操作,能很好地考察一个人的逻辑严密性,而且代码量适中,适合在笔试环境中限时完成。
常见考法包括:反转链表、判断链表是否有环、找链表中倒数第K个节点、合并两个有序链表、删除链表中的重复节点。
以“反转链表”为例,很多人第一时间能写出迭代版本:
public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode nextTemp = curr.next; curr.next = prev; prev = curr; curr = nextTemp; } return prev; }这段代码本身没问题,但我在实际批阅简历、帮朋友做模拟面试时发现,至少有50%的人会忽略一个细节:输入链表为空或只有一个节点时的返回值是否正确。如果 head 为 null,上面的代码会直接返回 null,这没问题;但如果题目要求的是“反转后返回新的头节点”,我们就需要确认函数签名和返回值定义。
另外,在笔试平台上写代码时,很多人容易把ListNode的定义写错,比如漏掉了构造方法,或者把val和next的访问修饰符写成了private。这些小细节在实际笔试中非常致命,因为平台不会像IDE那样给你自动补全。
2.2 二叉树与递归:明确边界条件的推导逻辑
二叉树题目是我个人觉得“性价比最高”的复习方向,因为题型相对固定,而且只要掌握了递归模板,大部分题都能很快找到思路。
爱奇艺这套题里出现过的二叉树考点包括:二叉树的前序/中序/后序遍历、层序遍历、二叉树的最大深度、判断是否为平衡二叉树、最近公共祖先等。
以“判断是否为平衡二叉树”为例,标准做法是自底向上递归:
public boolean isBalanced(TreeNode root) { return height(root) != -1; } private int height(TreeNode node) { if (node == null) { return 0; } int leftHeight = height(node.left); if (leftHeight == -1) { return -1; } int rightHeight = height(node.right); if (rightHeight == -1) { return -1; } if (Math.abs(leftHeight - rightHeight) > 1) { return -1; } return Math.max(leftHeight, rightHeight) + 1; }很多同学第一次看到这个解法时会觉得奇怪:为什么用 -1 表示“不平衡”?直接把左右子树高度差大于1时返回 false 不就行了吗?
这里的关键在于,如果我们在递归过程中发现某个子树已经不平衡了,其实就没必要再计算其他子树的高度了。返回 -1 是一种“短路机制”——上层调用拿到 -1 后直接继续返回 -1,避免了多余的递归计算。这就是所谓的“自底向上”思想,也是我在做二叉树题目时最常用的优化手段。
2.3 动态规划与字符串处理:从暴力解法推导优化
动态规划是笔试中的分水岭题型,爱奇艺这套题的编程题中也有涉及。不过它考察的DP题通常不是那种需要非常高阶优化的难题,而是比较经典的“最长公共子序列”、“编辑距离”、“最长回文子串”之类的题目。
以“最长回文子串”为例,暴力解法是枚举所有子串然后判断是否为回文,时间复杂度是 O(n^3),在笔试中基本不可能通过所有测试用例。中心扩展法能降到 O(n^2):
public String longestPalindrome(String s) { if (s == null || s.length() < 1) { return ""; } int start = 0, end = 0; for (int i = 0; i < s.length(); i++) { int len1 = expandAroundCenter(s, i, i); int len2 = expandAroundCenter(s, i, i + 1); int len = Math.max(len1, len2); if (len > end - start) { start = i - (len - 1) / 2; end = i + len / 2; } } return s.substring(start, end + 1); } private int expandAroundCenter(String s, int left, int right) { while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { left--; right++; } return right - left - 1; }这个解法的核心在于意识到“回文串一定有一个中心”,而中心有两种情况:一个字符(奇数长度)或两个相等字符(偶数长度)。想通了这一点,代码写起来就很顺了。
我建议大家在备考时把常见的DP题型整理成一个模板清单:背包问题、最长公共子序列、最长递增子序列、编辑距离、回文子串。每道题至少能手写两遍,做到“看到题目就能反应出状态转移方程”的地步。
3. 核心考点详解:Java基础与Android机制
如果说算法题是“筛选器”,那Java基础和Android机制就是“压舱石”——这些题决定了你是否能拿到后续面试的入场券。爱奇艺这套题在基础部分出得比较全面,下面我把考察频率最高的几个方向逐个解读。
3.1 Java集合框架:HashMap是永远的主角
Java集合类是笔试选择题的“题库大户”,而HashMap又是其中最核心的考点。关于HashMap,你需要清楚的不仅是“Key-Value存储”,还有它背后的数据结构与扩容机制。
我在复盘这套题时梳理了一下,与HashMap相关的考点主要有这些:
- HashMap 的底层结构:数组 + 链表 + 红黑树。为什么要引入红黑树?因为当链表过长时,查找效率会从 O(1) 退化为 O(n),红黑树能将最坏情况下的查找时间复杂度降到 O(log n)。
- 哈希冲突的解决方法:链地址法。两个Key的hash值相同或映射到同一个数组下标时,会以链表形式串联起来。
- 扩容机制:当size超过threshold(capacity * loadFactor)时,会触发resize。默认容量是16,默认负载因子是0.75。为什么负载因子是0.75而不是1或0.5?这是时间与空间的折中——负载因子太高会导致冲突概率增大,太低则浪费空间。
- 为什么HashMap是线程不安全的:多个线程同时put时可能导致数据覆盖,甚至JDK7之前并发扩容可能形成环形链表,导致CPU 100%。所以并发场景应该用ConcurrentHashMap。
这里有一个高频选择题:HashMap 和 Hashtable 有什么区别?标准答案是Hashtable是线程安全的(方法加了synchronized),不允许null作为Key或Value;HashMap线程不安全,允许null。但在实际笔试中,如果你能补充一句“Hashtable因为所有方法都加锁,并发效率低,基本已被ConcurrentHashMap取代,所以实际项目中很少直接用”,会给阅卷人留下更好的印象。
3.2 JVM内存模型与垃圾回收机制
JVM这块是Android开发面试的“深水区”,但笔试一般不会考得太深,主要集中在内存区域的划分、GC算法、类加载过程这几个点上。
关于内存区域,记住这张表就够用了:
| 区域 | 线程共享? | 存放内容 | 异常类型 |
|---|---|---|---|
| 程序计数器 | 否 | 当前线程执行的字节码行号 | 无 |
| 虚拟机栈 | 否 | 局部变量表、操作数栈、方法返回值 | StackOverflowError |
| 本地方法栈 | 否 | Native方法调用 | StackOverflowError |
| 堆 | 是 | 对象实例、数组 | OutOfMemoryError |
| 方法区 | 是 | 类信息、常量、静态变量 | OutOfMemoryError |
在Android场景下,我们要额外关注的是:移动设备内存有限,堆内存往往只有几百MB,所以“内存泄漏”是比JVM理论更实际的问题。笔试选择题里很可能出现这样的题目:以下哪几种情况会导致Activity内存泄漏?
常见的正确答案包括:非静态内部类持有Activity引用(比如Handler)、Activity被静态变量引用、未注销BroadcastReceiver、流对象未关闭等。
3.3 并发编程:synchronized与volatile的底层区别
并发编程是Java基础中的重点,也是很多人复习时容易“背了忘、忘了背”的部分。爱奇艺这套题的选择题中出现了关于synchronized、volatile、ThreadLocal的题目,这里我展开说说。
先讲 volatile。它的核心语义是“可见性”和“禁止指令重排序”,但不保证“原子性”。我们最熟悉的应用场景就是单例模式中的双检锁:
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }为什么这里的 instance 必须加 volatile?因为instance = new Singleton()并不是一个原子操作,它在JVM层面主要分为三步:分配内存、初始化对象、将引用指向内存。如果发生指令重排序,其他线程可能拿到“已分配内存但尚未完成初始化”的对象,导致后续使用出错。volatile 的禁止重排序语义正好解决了这个问题。
再说 synchronized。它修饰实例方法时锁的是当前对象,修饰静态方法时锁的是Class对象,修饰代码块时锁的是括号里的对象。笔试经常会问“synchronized和ReentrantLock的区别是什么”,这类题属于“必须背熟”的基础题:synchronized是JVM层面实现的,ReentrantLock是JDK层面实现的;synchronized不需要手动释放锁,ReentrantLock需要lock/unlock配合;ReentrantLock支持公平锁、可中断、多条件等待等高级功能。
3.4 Android消息机制:Handler、Looper与MessageQueue
如果说Java并发是“前菜”,那Handler消息机制就是Android笔试的“主菜”。几乎所有Android岗的笔试题都会涉及Handler,爱奇艺这套题也不例外。
核心知识点其实就一句话:Handler通过Looper从MessageQueue中取消息,然后通过dispatchMessage分发给handleMessage处理。但要真正答好相关题目,你需要理解这几个层次:
- Handler:负责发送消息和处理消息。发送消息时通过
enqueueMessage将Message放入MessageQueue。 - Looper:每个线程最多只能有一个Looper。
Looper.loop()是一个死循环,不断从MessageQueue中取消息。 - MessageQueue:内部是一个单项链表结构,按时间排序。
next()方法会阻塞等待下一条消息。 - Message:可以设置
what、obj、arg1、arg2等字段,建议通过Message.obtain()复用对象,避免频繁创建。
考题可能会这样出:在子线程中创建一个Handler,需要先做什么?答案是先调用Looper.prepare()和Looper.loop()。如果不调用Looper.prepare(),会直接抛出“Can‘t create handler inside thread that has not called Looper.prepare()”的异常。这个知识点只要写过自定义线程中的Handler就会印象深刻。
另外一个高频变形题:Handler导致的内存泄漏怎么解决?标准答案是:把Handler定义为静态内部类,或者使用WeakReference持有Activity的引用,同时在onDestroy中移除所有消息:handler.removeCallbacksAndMessages(null)。
3.5 Activity启动模式与生命周期
Activity这块属于“背了就有分”的题目,但爱奇艺这套题在启动模式上出了一道比较有区分度的选择题,考察的是standard、singleTop、singleTask、singleInstance这四种模式在不同场景下的路由表现。
我这里用一张表帮大家快速记忆:
| 启动模式 | 特点 | 典型场景 |
|---|---|---|
| standard | 每次启动都创建新实例,放入原Task | 默认模式,通用页面 |
| singleTop | 如果栈顶已经是该Activity的实例则不新建 | 收到推送后跳转的详情页 |
| singleTask | 如果Task中存在该Activity的实例则移去顶部并清空其上所有Activity | App主页 |
| singleInstance | 该Activity独自存在于一个Task中 | 来电页面、闹钟提醒 |
这里有个容易混淆的点:singleTask和singleInstance的区别。singleTask只是“尽量复用栈内已有实例”,它所在Task中还可以有其他Activity;而singleInstance是“整个Task里只能有这一个Activity”,不允许其他Activity进入。
生命周期题也几乎必考,特别是“启动A跳转B时,生命周期回调顺序”这类题。完整的顺序是:A.onPause -> B.onCreate -> B.onStart -> B.onResume -> A.onStop。这里的关键是,A.onPause先执行,B完成启动后A才执行onStop,这体现了Android以窗口焦点切换为核心的调度逻辑。
3.6 View绘制流程与自定义View
Android自定义View这块,笔试通常会考察View的测量、布局、绘制三个阶段,以及相关方法的作用。爱奇艺这套题里有一道关于onMeasure的题,问的是MeasureSpec的三种模式。
MeasureSpec是View测量时的核心概念,由32位int组成,高2位代表模式,低30位代表大小。三种模式分别是:
- UNSPECIFIED:父容器不对View有任何限制,通常用于系统内部测量。
- EXACTLY:父容器已确定精确大小,对应match_parent和具体dp值。
- AT_MOST:父容器指定了最大大小,对应wrap_content。
理解了MeasureSpec,很多自定义View的题目就迎刃而解了。比如面试官问“自定义View时,如果想让wrap_content生效,需要做什么?”答案就是重写onMeasure,在测量模式为AT_MOST时,给View设置一个默认大小,否则默认情况下wrap_content会等同于match_parent。
4. 常见问题与考场避坑实录:笔试现场的实战技巧
这一部分我想写点“只可意会”的内容。我帮不少人做过笔试复盘,也在毕业后回学校做过模拟笔试的评审,总结出了一些普通面经里不会写、但实际非常影响分数的细节。
4.1 时间分配是最容易被低估的决策
爱奇艺这套笔试整体的时间是有限的,很多同学在做选择题时因为某道JVM题卡住,结果后面编程题都没时间仔细想。我见过太多这样的案例:选择题满分60分拿了50分,编程题两道只AC了一道,结果总分反而比选择题40分、编程题两道全AC的同学低。
我的建议是,拿到试卷先花两三分钟快速浏览全部题目,判断各题型的性价比。选择题如果一道题超过2分钟还没头绪,先标记跳过,回头再看。编程题先做自己最有把握的那道,拿到稳定分之后再去啃难题。这里的逻辑是:笔试看的是总分,不是单题正确率。先把能拿的分全拿到,是被验证过最高效的策略。
4.2 编程题的“代码卫生”比你想象得更重要
很多笔试平台支持本地IDE调试,但最终的评测是在线上完成的。写出来的代码,除了要能通过测试用例,还要注意“代码卫生”——这个问题在面试官人工review时会放大。主要包括:
- 变量命名是否清晰,能不能用有意义的英文单词,而不是a、b、c这种缩写。
- 是否有冗余代码或注释,注释是否表达了思路而不是抄了一遍代码。
- 函数边界是否处理清楚,比如输入null、空数组、单元素数组的情况下,代码是否能正常返回。
举个例子,写链表反转时,我建议顺手把空链表和单节点的情况在注释里写一句。这不只是为了加分,更是为了让你的思路在代码中自然流露出来。如果一个候选人交上来的代码连变量名都懒得好好起,面试官很难相信他有很好的工程习惯。
4.3 选择题要注意“是否”类陷阱
爱奇艺这类大厂的笔试题,特别喜欢在选择题里用“以下说法错误的是”“正确的是”“不正确的是”这些问法。很多人不是不会这个知识点,而是没看清题目到底问的是“正确”还是“错误”,一慌张就选反了。
我在实际做题时有一个小习惯:先在草稿纸上写出题目的问法关键词(是从中选择正确的,还是错误的),然后逐项判断。这个方法听起来很笨,但在压力状态下非常有效,能显著减少粗心导致的丢分。
另外,多选题是校招笔试的“重灾区”。我见过很多同学在做多选时,因为过度谨慎少选了一个选项而丢分,又因为过于自信多选了一个错误选项直接零分。如果有“少选得分、选错不得分”的评分规则,建议采用“保守策略”——只选有绝对把握的选项。如果题目没说漏选怎么计分,那就尽量选全,宁可错选也不要漏选。
4.4 安卓实操题的答题思路:把过程用文字写出来
最后一类题型是Android相关的简答或代码补全题,比如“请说明如何优化一个列表页的滑动卡顿”“如何设计一个图片缓存库”。这种题没有绝对标准答案,但阅卷人会考察你的分析思路是否完整。
先说“列表卡顿优化”这道题。一个合格的答案应该覆盖以下几个维度:
- 布局层级:减少不必要的嵌套,使用ConstraintLayout替代多层LinearLayout。
- 图片加载:使用合适的缩放,避免加载原图;使用Glide/Fresco等库,并设置合适的缓存策略。
- RecyclerView使用:使用ViewHolder复用,使用notifyItemChanged而不是notifyDataSetChanged。
- 异步处理:耗时的数据解析、文件IO放到子线程。
- 内存抖动:避免在onBindViewHolder中创建大量临时对象,减少GC频率。
再如“设计一个图片缓存库”。回答思路应该是:LruCache作为内存缓存、DiskLruCache作为磁盘缓存、网络请求作为最后一级,形成三级缓存结构。同时要考虑线程池管理、图片压缩、生命周期感知等细节。
这类题考察的不是“标准答案”,而是你对一个实际问题的完整思考链。建议平时多做一些框架级的设计推导,养成从“使用场景 -> 核心架构 -> 关键细节 -> 异常处理”这个顺序思考的习惯。
5. 备考建议与复盘方法:从一套题到一套体系
复盘完这套题之后,我想再聊一个更宏观的话题:如何高效准备Android校招笔试。很多人刷了几百道LeetCode,却依然在笔试中翻车,原因往往不是题刷少了,而是知识不成体系。
5.1 建立“题目 -> 考点 -> 知识树”的三层映射
每做完一套笔试题,不要着急做下一套。我建议你先做一件更重要的事:把题目中的每一个考点映射到你自己的知识体系中。比如,看到一道关于“多个线程同时操作ArrayList”的题,就要想到:(1) ArrayList不是线程安全的;(2) 替代方案有CopyOnWriteArrayList、Collections.synchronizedList;(3) 这些方案各自的适用场景和性能差异。
通过这种映射,你刷的不是“一道题”,而是“一类知识”。等积累了几套真题之后,你会发现高频考点其实就那么几十个,完全可以建立一个速查手册。
5.2 手写代码与本地环境仿真
笔试时很多人不是不会,而是“一进IDE就手生”。尤其是链表、二叉树这类需要手动构建测试用例的题目,如果平时只是用LeetCode的网页编辑器作答,很容易忽略“如何自己构造测试数据”这个基础能力。
我的建议是:在本地IDE中自己搭建一个最小化的练习环境。用Android Studio或IntelliJ IDEA新建一个Java工程,然后手动编写反转链表、二叉树遍历、动态规划等题目的完整代码,包括main函数和测试用例。这样做有三个好处:一是熟悉了IDE快捷键与自动补全的节奏,二是锻炼了手动构造输入输出的能力,三是提前适应了“代码写完后要自己验证”的工程习惯。
5.3 把“不会的题”变成“复盘的素材”
很多人做完一套题,对答案看一遍就过了,这样效率很低。我的做法是维护一个错题本,但记的不是题目本身,而是“我当时为什么做错”的归因分析。格式大概是:
- 知识点盲区:比如不知道HashMap在JDK8中引入了红黑树,导致判断树化条件时选错。
- 思路偏差:比如链表反转时没考虑头节点的边界处理。
- 审题失误:比如把“选择不正确的”看成了“选择正确的”。
- 时间不足:比如一道动态规划题花了30分钟,导致最后一题没时间写。
这个错题本在考前一周是最宝贵的复习资料。因为到了冲刺阶段,你不需要再从头看一遍所有知识点,只需要回看错题本,就能精准定位自己的薄弱环节。
5.4 技术广度:别忽视网络协议与Linux基础
最后提醒一个容易被忽略的方向:网络协议。爱奇艺这套笔试的选择题里也出现了TCP三次握手、HTTP与HTTPS的区别等题目。视频类App对网络请求的依赖极高,所以这些考点在校招笔试中经常出现,就算没出现在笔试中,面试环节也大概率会问。
准备网络协议,可以重点复习TCP的三次握手与四次挥手、TCP与UDP的区别、HTTP/HTTPS的握手过程、HTTP的基础报文结构。这不需要你像网络工程师那样精通,但至少要能画出流程图并且能清晰解释每一步的意图。
写在最后:一次笔试的真正价值
文章写到这里,整套题的核心考点、解题思路、备考方法基本都覆盖了。回到最初的问题:爱奇艺2020校招Android方向笔试题(第二场)到底值不值得认真复盘?
我的体会是,它的价值不在于“押中原题”,而在于帮你验证自己的知识体系是否完整。我自己在复盘这套题时,就发现自己对“HashMap扩容时红黑树与链表互转的阈值”掌握得不够清楚,对“View绘制流程中measure与layout的先后关系”也一度混淆。这种“发现自己不知道”的过程,恰恰是备考阶段最宝贵的收获。
如果你正在准备Android校招,不妨以一个更平静的心态去面对每一次笔试。把它当成一次针对自己知识盲区的“体检”,而不是一场非赢不可的战斗。每套题做完,都能比上一套进步一点,这就够了。