ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

爱奇艺2020校招Android笔试题复盘:核心考点与避坑指南

爱奇艺2020校招Android笔试题复盘:核心考点与避坑指南 爱奇艺2020校招Android方向的第一场笔试题说实话放到现在来看依然非常有嚼劲。我最近重新把这张卷子翻出来逐题复盘了一遍发现里面的考点并没有过时反而越来越像大厂面试的“地基题”不考花哨的框架不追新特性专门盯基础盯原理盯你在日常开发里有没有真正想过“为什么”。这篇博客就把这套题涉及的Android核心考点、我当时答题的思路、以及一些容易踩坑的地方完整拆开讲一遍。不管你是准备春招秋招的应届生还是工作了两三年想跳槽的Android开发这套题都很值得拿来当自测清单。做完一遍你会发现很多平时写代码时模模糊糊的概念其实是面试官最喜欢拿来“一探深浅”的地方。1. 整体设计与考点拆解一张卷子到底在考什么1.1 试卷结构从题型分布看考察意图拿到的第一场笔试题整体题量不算少题型也比较固定基本由三部分组成单选题、多选题、编程题。选择题集中考察Java基础和Android框架核心机制编程题则偏向算法和逻辑偶尔会有一道简单的线程或设计模式题目混在里面。从大厂出题的思路来看这种组合很典型选择题用来筛“基础是否扎实”编程题用来筛“代码功底是否过关”。而且笔试题和面试题有个本质区别——面试官不在场没有追问和引导的机会。这意味着题目必须把知识查考得尽量明确不能有太多模糊地带。所以你会发现笔试题很少出现“谈谈你对XXX的理解”这种开放题更多是“下列哪种情况会导致ANR”或者“这段代码运行结果是什么”这种有确定答案的题。我把这份卷子里出现过的考点按权重整理了一下大概是这样一个分布考察方向预估占比典型题目类型Java基础集合、String、异常25%~30%单选/多选Android四大组件20%单选/多选Handler与消息机制10%~15%单选/多选进程与线程、ANR10%单选/多选布局与绘制流程5%~10%单选内存优化与性能5%~10%多选算法与编程题20%~25%手写代码这个权重其实给备考的人传递了一个信号大厂校招笔试不追求你“会用多少框架”而是追求你“懂不懂核心原理”。哪怕你简历上写了熟练使用OkHttp、Glide、RxJava这些框架在笔试题里基本不会出现。原因很简单框架可以速成基础只能靠积累。面试官希望通过一张卷子筛出那些真正对技术有好奇心、愿意往下钻的人。1.2 为什么大厂执着于考察这些“旧知识”很多同学会问2020年的题目放到现在还有参考价值吗我的答案是有而且价值很高。Android开发这几年框架层变化确实快Compose、Kotlin协程、Jetpack全家桶新技术一个接一个。但底层的东西变了吗没有。Handler机制依然是主线程和子线程通信的基石Binder依然是跨进程通信的核心Activity的启动模式依然决定着你任务栈的行为Java内存模型依然是并发编程绕不开的理论。这些知识就像大楼的地基不管上面装修得多豪华地基不牢一样会塌。更直白一点说笔试做题和平时写业务是两个维度的事。写业务你可能只需要知道“这样写能跑”笔试要的是“你知不知道它内部是怎么跑的”。比如大家天天用HashMap但问到JDK 1.8在什么情况下会把链表转红黑树多少人能立刻答出来问到HashMap扩容时为什么是2的幂次方多少人能解释清楚这些就是笔试选择题最喜欢的出题角度。2. 核心细节解析与实操要点逐个击破高频考点2.1 Java基础题集合、String与异常看似简单实则全是坑先说Java基础这部分。爱奇艺这套题里Java基础占比不低而且考察得很细。集合类基本是必考的HashMap更是重中之重。我印象比较深的是一道关于HashMap的题问的是JDK 1.8中HashMap在什么条件下会将链表转换为红黑树。答案是当链表长度大于等于8且数组长度大于等于64时。但很多同学只记得“链表长度大于8”这半句忽略了“数组长度最小为64”这个前提。如果你只答了前半句这道题就丢了分。为什么要同时满足两个条件这里面的设计逻辑很重要。链表本身在数据量小的时候遍历开销并不大但是当链表过长比如上百个节点遍历查找的性能就会显著下降。红黑树的引入是为了应对极端哈希冲突的情况但树化本身也有代价——树节点的大小大约是普通链表节点的两倍维护红黑树的平衡也需要额外的开销。所以HashMap的设计者把树化的触发条件定得比较保守只有在链表足够长、同时数组容量足够大意味着整体数据量已经不低的时候才树化否则宁愿多做一次resize来分散哈希冲突。再比如String相关的题目。有一道经典的题是问String、StringBuilder、StringBuffer三者的区别。这个题目虽然老掉牙但依旧能筛掉一批人。核心区别有三String是不可变对象每次拼接都会生成新对象循环拼接场景下性能极差StringBuilder是可变对象线程不安全但性能最高StringBuffer在StringBuilder基础上加了synchronized线程安全但有一定性能损耗。这道题本身不难但面试官通常会在编程题或面试追问里结合实际场景——比如让你用循环拼接1万次字符串问你会选哪个为什么。异常体系也是一个常考的点。有一类题特别喜欢考Error和Exception的区别以及运行时异常和受检异常的区别。这里的核心要义是Error是JVM层面的严重错误比如OutOfMemoryError、StackOverflowError应用程序通常无法处理也不应该尝试捕获Exception是程序层面的问题可以分为IOException这类受检异常编译期强制处理和NullPointerException这类运行时异常编译期不强制。这里面有个容易混淆的点RuntimeException也继承自Exception但它不是受检异常。答题的时候要认清这一点别一看到Exception的子类就觉得必须try-catch。2.2 Android四大组件启动模式、生命周期与进程优先级Activity的启动模式几乎是每一场Android笔试的必备题。爱奇艺这套题也不例外而且考察得不只是四种模式的名称而是它们之间的区别和实际应用场景。四种标准模式standard、singleTop、singleTask、singleInstance。standard模式下每次启动都会创建新实例这是默认行为singleTop的特点是如果目标Activity已经在栈顶那么不会创建新实例而是复用栈顶实例并回调onNewIntentsingleTask则更激进如果栈里已经存在该Activity实例会把它上面的所有Activity弹出复用这个实例singleInstance则是单独开一个任务栈里面就放这一个Activity适合需要全局唯一且和外部共享的场景比如来电界面。笔试中比较刁钻的考法是配合Intent Flag来考。比如问FLAG_ACTIVITY_NEW_TASK和singleTask是不是一回事答案是不完全一样。singleTask本身就带有在目标任务栈中复用实例并清理栈顶的效果而FLAG_ACTIVITY_NEW_TASK只是尽量把Activity放入一个新的任务栈如果目标栈已存在不一定复用已有实例。实际开发中从非Activity上下文比如Application启动Activity时必须加FLAG_ACTIVITY_NEW_TASK否则会崩溃因为非Activity上下文没有任务栈。Service的两种启动方式和生命周期也是高频考点。startService启动的Service生命周期是onCreate、onStartCommand、onDestroy和启动者没有绑定关系启动者退出后Service依然在后台运行。bindService启动的Service生命周期是onCreate、onBind、onUnbind、onDestroy和绑定者绑定在一起所有绑定者都解绑后Service会走onDestroy销毁。这里容易忽略的点是如果先用startService启动再绑定那Service只会经历一次onCreate但必须先调用stopService或stopSelf才能真正停止它因为startService和bindService混用的情况下Service的停止需要两者都满足条件。ContentProvider在笔试中一般不会考得太深但会考它的基本作用跨进程数据共享底层依赖Binder实现。另外要注意ContentProvider的onCreate执行时机——它会在Application的onCreate之前执行所以不能在里面做耗时操作否则会拖慢进程启动速度。BroadcastReceiver在2020年这道题里也开始体现一些变化比如静态注册和动态注册的区别以及Android 8.0之后静态注册隐式广播的限制。其实这个问题放到现在依然是考热点因为Android系统对后台限制越来越严很多老的开发习惯已经不适用了。2.3 Handler消息机制主线程不卡的原因与内存泄漏隐患Handler几乎是每场Android笔试的必考题。爱奇艺这套题考了Handler机制和内存泄漏两个角度。先梳理标准的消息循环流程主线程创建时通过Looper.prepareMainLooper初始化一个LooperLooper内部维护一个MessageQueueHandler在创建时会通过Looper.myLooper()获取当前线程的Looper发送消息时调用enqueueMessage把Message放入MessageQueueLooper.loop()方法进入一个死循环不断从MessageQueue中取出Message通过dispatchMessage分发到Handler的handleMessage方法。这中间有一个关键点MessageQueue取消息用的是阻塞队列的思想。当队列为空时主线程会进入阻塞状态释放CPU资源所以并不会造成性能浪费。这也是“主线程为什么能一直运行但又不会占满CPU”的答案核心。另一个高频考法是为什么在子线程创建Handler会报错因为这些代码没有在Handler创建之前调用Looper.prepare()。你必须在子线程里手动调用Looper.prepare()来初始化消息队列再调用Looper.loop()让消息循环跑起来。记得在不需要的时候退出looper否则子线程会一直阻塞。Handler内存泄漏的问题是很多面试官喜欢深挖的考点。非静态内部类或匿名内部类会隐式持有外部类的引用。如果你在Activity中创建了一个Handler并且这个Handler在使用过程中有延迟消息或正在处理消息那么即使在Activity销毁后Looper中的MessageQueue仍持有Message的引用Message又持有Handler的引用Handler又持有Activity的引用最终导致Activity无法被回收。解决方式有几种第一把Handler定义为静态内部类内部用WeakReference来持有Activity第二在onDestroy时调用handler.removeCallbacksAndMessages(null)清空消息队列中的消息。笔试的答案点基本就是这两条。2.4 Binder与进程通信Android灵魂机制的核心逻辑Binder是Android里最核心的进程间通信机制也是大厂笔试的常客。爱奇艺这道题里虽然没有单独拿出一整道大题来考Binder但在多选和简答里会涉及。首先得理解一个前提Android基于Linux内核但为什么没有直接用Linux自带的管道、消息队列、共享内存、Socket这些IPC机制而是自己设计了Binder这个问题几乎可以当考试答案模板来准备。原因有三点。第一安全。传统IPC方式没有身份验证机制接收方无法获得发送方可靠的进程ID和用户ID而Binder基于Client-Server架构每个进程都有UID/PID标识在内核层就做了身份校验。第二性能。一次Binder数据拷贝只需要一次内存拷贝而传统的管道、消息队列需要两次拷贝共享内存虽然不需要拷贝但同步复杂容易出并发问题。Binder在效率和易用性之间取得了平衡。第三稳定性。Binder基于内核的驱动实现架构清晰调用过程稳定可靠。理解Binder的工作过程可以把它类比成一次跨城市的快递配送。Client是寄件人Server是收件人Binder驱动是物流中转站。寄件人把包裹交给物流站物流站根据地址把包裹送到收件人手里。整个过程只发生了一次真正意义上的“搬运”——从Client进程的用户空间拷贝到内核空间再由内核空间映射到Server进程的用户空间。实际上就是一次内存拷贝。在Android应用层我们接触最频繁的AIDL就是这么工作的。你定义好接口编译时会自动生成Proxy和Stub两个类。Proxy在客户端侧负责把调用参数序列化后通过Binder驱动发送Stub在服务端侧负责接收数据并执行真实逻辑然后把结果返回。理解这一层很多面试追问就都能接住。2.5 ANR与多线程线上Bug的高发区也是笔试的高频区ANRApplication Not Responding这个词在笔试里出现的频率非常高而且通常不是单一概念题而是结合场景出题。核心知识点有三个维度什么场景会触发ANR、ANR的超时时间、如何避免和排查。触发ANR的场景有五种左右输入事件按键和触摸在5秒内没有被处理完、BroadcastReceiver的onReceive在10秒内没有执行完、前台Service的onStartCommand在20秒内没有执行完、后台Service在60秒内没执行完、ContentProvider的publish在10秒内没有完成。这些超时时间是笔试里的硬考点记不牢很容易丢分。多线程这块经常和ANR联系在一起考。比如问在子线程里能不能更新UI答案可以但更准确地说子线程是可以更新UI的前提是你创建的View没有被附加到Window上。比如你new了一个TextView在子线程里setText这是完全没问题的但如果你这个TextView已经通过setContentView加入到了窗口层级那么在子线程里操作它就会触发崩溃。崩溃的原理是ViewRootImpl的checkThread方法检查到当前线程不是创建ViewRootImpl的线程直接抛出CalledFromWrongThreadException。笔试题有时候会故意模糊这一点要注意区分“能不能”和“会不会崩溃”这两个层面的问题。多线程常见的考察方式还有线程池的核心参数、如何优雅地停止一个线程、synchronized和Lock的区别、volatile的可见性但不保证原子性。这些如果放在Java基础部分一起考也完全是合理的。我的建议是把Java并发这一块当成“小而精”的模块来复习不要贪多把最经典的那几个问题吃透就好。3. 实操过程与核心环节实现编程题这样写才稳3.1 两道典型的编程题与完整解答思路笔试的编程题不像LeetCode那样纯考算法很多时候会结合一些工程化的细节。爱奇艺这套题里的编程题比较典型的有两类一类是纯逻辑题一类是线程相关的题。先看一个经典的两线程交替打印奇偶数。题目要求两个线程一个只打印奇数一个只打印偶数从1打印到100交替输出。这个题看起来简单但很能反映你对线程同步的理解。我用wait和notify的方式写一个标准解法public class AlternatePrint { private static final Object lock new Object(); private static int count 1; private static final int MAX 100; public static void main(String[] args) { Thread oddThread new Thread(() - { while (true) { synchronized (lock) { if (count MAX) { lock.notifyAll(); break; } if (count % 2 1) { System.out.println(Thread.currentThread().getName() : count); lock.notifyAll(); } else { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } }, 奇数线程); Thread evenThread new Thread(() - { while (true) { synchronized (lock) { if (count MAX) { lock.notifyAll(); break; } if (count % 2 0) { System.out.println(Thread.currentThread().getName() : count); lock.notifyAll(); } else { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } }, 偶数线程); oddThread.start(); evenThread.start(); } }这个写法里有几个容易出错的细节要在笔试时特别注意。一是进入while循环时要先判断count是否已经超过MAX避免死循环二是在打印完数字后要立即notifyAll()唤醒对方线程否则程序可能停在某个线程的wait上三是异常处理里要调用Thread.currentThread().interrupt()恢复中断标记这是工程上比较规范的做法笔试时写上会加分。另外这里用while而不是if来包住wait条件属于标准写法因为wait被唤醒后条件可能已经变了必须重新检查。另一个常见的手写题是实现一个线程安全的单例模式而且要求用双重检查锁DCL。这题看起来简单但里面有一个很重要的考点为什么要用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; } }很多人能写出这个结构但解释不清volatile的作用。这里的volatile有两个作用保证可见性和禁止指令重排序。关键在于创建对象这一步并不是原子的。instance new Singleton()在字节码层面大概分三步分配内存空间、初始化对象、将引用指向内存空间。如果没有volatile禁止重排序步骤2和步骤3可能被换序——一个线程先执行了引用赋值此时对象还没构造完成另一个线程进来判断instance不为null直接返回了一个半初始化的对象运行时就可能出问题。这道题的得分点就在于你能不能把这个过程讲明白。3.2 手写代码环节的答题策略与细节编程题在笔试里通常不要求你写出可以直接编译运行的完整工程但阅卷人会看你的解题思路和代码规范。有几个实际操作层面的建议可以说一说。第一先写注释再写代码。这不是浪费时间而是让阅卷人一眼看到你的思路。比如先写“// 第一步判断当前数字是否超过上限”再写对应的几行代码。这样做的好处是即使你后面代码有小bug阅卷人也能看出你整体思路是对的不至于全题给零分。第二注意边界条件。很多同学拿到题就开始埋头写主逻辑忘了处理空指针、数组越界、输入为0这些情况。但阅卷人最关注的就是这些边界。比如题目要求对数组去重你写了核心的HashSet方案但没考虑数组为空的情况——这在阅卷老师眼里是明显的代码漏洞。写完后至少扫一遍如果传入null会怎样如果传入空数组会怎样如果结果集为空怎么返回这三个问题过一遍基本就能避开大多数边界错误。第三不要用太花哨的API。笔试环境可能没有IDE的提示很多Java 8的Stream操作、Java 11的var关键字在阅卷时可能不被某些旧版JDK支持写了反而容易让阅卷人怀疑你的环境兼容意识。我建议手写代码时统一用最基础的语法for循环、ArrayList、HashMap、判空、try-catch把逻辑写清楚比堆砌高级API更能拿分。第四预留检查时间。编程题一般建议先做选择题再做编程题。时间分配上如果卷面给了60分钟编程题至少留30分钟。拿到题先花3-5分钟在草稿纸上画流程图或伪代码把逻辑理清了再往答题区写。我见过太多人一上来就敲代码结果写到一半发现逻辑绕错了又全删掉重写反而更浪费时间。4. 常见问题与排查技巧实录笔试复盘中的高频失分点4.1 为什么你背了那么多题还是挂了我在帮别人复盘笔试时发现一个非常普遍的问题很多人复习方式就是刷“面试题汇总”把答案背得滚瓜烂熟但一到笔试还是做错。原因很简单——背诵的是结论而不是推理路径。举个例子。“为什么使用Binder作为Android的IPC机制”标准答案是安全、高效、稳定三条。但如果笔试改成“下列哪种IPC方式在Android中性能最优”或者“Binder一次数据拷贝的过程是怎样的”很多人就懵了。因为只背了结论没有真正理解Binder的调用链路。所以复习的时候每个知识点都要能闭环回答三个问题是什么、为什么、怎么用。以HashMap为例是什么键值对存储结构数组链表红黑树、为什么解决哈希冲突、保证O(1)查询、怎么用开发中合适的初始化容量、避免并发修改。把这三个层面打通了不管笔试怎么变着法子考你都能接住。另外还有一个很现实的问题只看不练。编程题如果平时不在电脑上亲手敲过光看别人的代码很难在笔试现场那么紧张的环境里写出来。我建议准备校招的同学每天至少手写2到3道经典题型不需要跑通但要做到思路清晰、代码整洁。一个比较实用的训练方式是在纸上写代码不给自己任何自动补全的机会写完再对照标准答案逐行核对。这个过程一开始会很痛苦但坚持两周后手写速度会有明显提升。4.2 笔试中的几类典型“陷阱题”根据爱奇艺这套题的复盘我总结了几个比较有迷惑性的出题角度大家可以当避坑指南来看。第一类是“看似问A实则考B”的题目。比如题目问“Handler导致内存泄漏的原因是什么”很多人马上想到非静态内部类持有外部类引用但题目可能会把选项设置得更细A. Looper持有MessageQueueB. Message持有HandlerC. Handler持有ActivityD. 以上都是。如果你只记住了一个笼统结论面对这种细分的多选很可能漏选。所以复习Handler内存泄漏时脑子里要有一条完整的引用链主线程Looper → MessageQueue → Message → Handler → Activity。这一条链记住相关的多选基本全能做对。第二类是“常识答题但技术上有坑”的题。例如“子线程中能否更新UI”凭常识很多人直接选不能。但准确答案应该是在子线程中更新未附加到Window的View是允许的更新已附加的View会崩溃。笔试把“是否崩溃”作为考点考的就是你对ViewRootImpl机制的理解深度。这种题一旦做错就会让阅卷人对你的基础产生怀疑。第三类是“方案选择”题比如“下列哪种方式不适合做耗时操作后的UI更新”。这类题的选项通常是Handler、runOnUiThread、View.post、直接在子线程操作。正确答案是最后一个但很多同学在“runOnUiThread”和“View.post”之间犹豫。其实这两个都是把任务切回主线程执行的方法而View.post的实现底层还会经过Handler所以它们本质上是一样的。梳理清楚这层关系这类题就不会再丢分。4.3 私藏的备考与答题时间管理经验结合我自己做这份卷子的体验有几个很实用的时间管理心得可以分享。选择题遇到不会的先凭第一感觉选一个在题号旁边做个标记不要死磕。因为选择题的分值通常不高没必要因为一道题卡住十分钟导致后面的编程题没时间写。整套卷子按照分值分配时间基本上是“分值占比等于时间占比”。编程题分值最高又是客观的得分项必须保证有完整的时间来写。再有就是审题时一定要看清楚是单选还是多选。多选题少选、错选、多选通常都不得分所以遇到模棱两可的选项宁可少选也不要乱选。比如一道题问你“哪些操作会导致ANR”选项中“使用okhttp进行网络请求”和“在主线程执行耗时数据库操作”都有可能导致卡顿但从ANR定义来说主线程超过指定时间未处理完输入事件才算触发ANR所以“网络请求本身”并不是直接触发条件关键要判断它是不是发生在主线程且超时。这种题就特别考验审题细致度。最后一点是关于错题整理。笔试复盘不要只把题目和答案抄下来就完事一定要把“我当时为什么选错”的原因写下来。是因为知识点没背到还是选项理解错了还是时间来不及乱蒙的每道错题至少写一句话的错因分析。这个方法听着土但效果出奇的好因为它逼着你去修正思维盲区而不是单纯地积累答案。5. 从笔试看校招爱奇艺这套题给后来者的三点启发5.1 校招笔试正在回归最本质的“内功”考察复盘完这套题最直观的感受是校招笔试重点考察的是知识体系而不是热门框架。这个信号对准备校招的人来说很重要意味着你不能因为简历上写了几个热门库就忽视对Java基础和Android底层原理的复习。现在网上很多校招攻略教你“背面试题”但我始终觉得刷题只是辅助真正决定你能走多远的是你有没有建立一套自己的知识体系。笔试涉及的知识点之间其实是有内在逻辑的Java基础是套路Android四大组件是骨架Handler和Binder是血管内存和性能是体检指标。把它们串起来理解比割裂地背100道题要有用得多。5.2 实战代码能力是被笔试低估的隐形门槛编程题在笔试中占比不低但很多同学对它的重视程度远远不够。我见过太多Java基础选择题能拿高分的人一到了手写代码环节就露馅——不是忘了判空就是缩进乱成一团要么就是只写了一个思路框架没有具体实现。编程题考察的不仅是算法更是你平时写代码的习惯。一个规范的for循环、一个完整的try-catch、一句有意义的注释都能让阅卷人对你的工程素养产生好印象。反过来就算思路对了代码格式乱七八糟阅卷人也很难给高分。所以我的建议很明确平时写代码时就当成在笔试不开自动格式化不用IDE的代码补全强迫自己默写常用代码结构。这种训练看起来很笨但在真正的笔试环境下能给你带来巨大的优势。5.3 心态与节奏把笔试当成一次技术复盘的机会说实话准备笔试的过程比笔试本身更有价值。你现在背的每一个知识点、刷的每一道编程题、整理过的每一个错题都会成为后续面试甚至工作里的“即战力”。我个人在实际操作中的一个体会是笔试前一天不需要再大量刷新题把错题本翻一遍把几个核心机制Handler、Binder、AMS、WMS在脑子里过一遍流程就好。睡个好觉比多刷十道题重要得多。真的到了考场控制好节奏会做的题先拿稳不会的题果断跳过编程题留足时间基本上就不会出太大问题。这套爱奇艺2020校招Android方向笔试题做下来内容从Java集合到Android组件从消息机制到进程通信覆盖面广而且直击本质。我把整套题的核心考点和解题思路都整理成脑图放在公众号里想要原题和答案详解的同学可以按老规矩自取。最后祝正在准备的各位笔试面试一路顺顺利利。
返回列表