公司动态

爱奇艺2020校招Android笔试题复盘:Handler、内存与并发全解析

📅 2026/9/1 22:36:24
爱奇艺2020校招Android笔试题复盘:Handler、内存与并发全解析
这份爱奇艺2020校招Android笔试题放在今天回头看依然是一张含金量很高的“体检表”。它不考那些浮于表面的API调用而是紧扣Android开发者在日常碰到的真实痛点从系统机制到性能优化从内存模型到并发策略几乎每一道题都能在后续的面试追问里延伸出大量话题。这两天我把它重新梳理了一遍结合我带团队做项目时的复盘经验把其中涉及的考点、容易翻车的细节以及我从题目反推出来的一套备战思路一起整理出来。不管你是准备春招秋招还是想自查一下基础功底这份拆解应该都能帮上忙。1. 这套卷子到底在考什么题型分布与出题逻辑先别急着钻到具体题目里我建议拿到任何一套笔试题第一步都是先“俯视”整张卷子看清它的考点分布和出题意图。爱奇艺这套题虽然叫“第二场”但它的结构基本代表了视频类大厂对Android候选人的核心期望既要懂语言基础和底层机制又要能处理实际App运行中的性能与稳定性问题。从考点维度大致可以分成四块Java与Android核心机制比如HashMap原理、Handler消息机制、进程与线程模型。这类题占比最大是筛人的第一道门槛。内存与性能优化包括内存泄漏场景识别、ANR成因分析、布局优化手段。这和爱奇艺本身重视频播放、重页面的业务特性高度相关。异步与并发AsyncTask的缺陷、线程池参数设计、IntentService与Service的区别。凡是涉及多线程的题都值得多写几句因为这是线上问题的高发区。系统特性与组件细节如Activity启动模式、进程优先级、SharedPreferences的适用边界。这一块考察的是是否有线上稳定性意识而不只是会用。这类笔试题有一个共同特点题型本身不复杂但几乎每道题都可以作为面试深挖的引子。比如HashMap那题如果你只答数组加链表面试官下一句大概率会问“红黑树什么时候转换为什么阈值是8”所以复盘这套题时我的建议是不要满足于“会选正确答案”而是要把每个选项背后的原理和边界条件都理清。另外从出题逻辑看这份卷子非常看重候选人对Android系统运行时的理解而不是单纯记忆API。比如Handler机制题面也许只是问你Looper在哪个线程创建但实际想考察的是你是否理解“主线程为什么不能阻塞”“MessageQueue如何被唤醒”这些环环相扣的问题。这些点没想透即使卷面答对了后续面试也很难扛住追问。2. Handler消息机制一道题就能牵出整个主线程运行模型Handler这题在笔试题里几乎是“必考明星”爱奇艺这套也不例外。它考得直白但背后能牵出的知识链非常长。我建议把它当成一个小体系来梳理而不是孤零零记结论。2.1 核心链路Looper、MessageQueue、Handler各自扮演的角色先理顺三个角色Looper负责从MessageQueue里不断取消息是消息循环的引擎。一个线程默认没有Looper主线程比较特殊系统在启动时通过Looper.prepareMainLooper()给它准备好了。MessageQueue消息存放的队列核心是enqueueMessage()和next()。它不是无限循环空转没消息时会通过epoll机制阻塞避免CPU空耗。Handler消息的发送者和处理者。sendMessage()最终会调用enqueueMessage()dispatchMessage()则会回调handleMessage()。这三者的配合顺序是Handler发送消息到MessageQueueLooper循环取出消息并回调Handler的handleMessage。注意消息处理是在Looper所在线程执行的所以如果你在子线程创建了Handler那handleMessage就运行在子线程这点容易混淆。2.2 最容易踩坑的考点一个线程能创建几个Looper很多人在简历里写“熟悉Handler机制”但一追问到这个点就含糊了。答案是一个线程只能有一个Looper。原因在于Looper.prepare()里有一句if (sThreadLocal.get() ! null) throw new RuntimeException(Only one Looper may be created per thread)。这个设计的意图很好理解如果允许一个线程创建多个Looper消息循环就会混乱不知道该由哪个队列来派发消息。ThreadLocal在这里的价值是让每个线程各自持有自己的Looper实例互不干扰。考题如果换个问法比如“Looper怎么保证线程唯一性”答出ThreadLocal基本就到位了。2.3 从题目延伸到实战主线程为什么不能做耗时操作提到Handler几乎必然要延伸到ANR问题。主线程Looper一旦被耗时任务阻塞超过一定时间Activity输入事件5秒、BroadcastReceiver 10秒等就会触发ANR。这里的本质是主线程既是UI绘制线程又是消息循环线程你拖住了它用户的所有操作都会卡住。但这不意味着主线程就绝对不能做任何耗时操作。正确的姿势是把重活放到子线程通过Handler切回主线程更新UI。比如视频App里获取播放地址网络请求在子线程执行拿到结果后再mHandler.post()切回主线程更新播放器状态。这里有个小经验如果你担心Handler回调里任务太重可以配合Choreographer的帧回调机制来判断是否掉帧而不是盲目优化。2.4 送分题背后的隐藏追问内存泄漏是怎么产生的Handler的内存泄漏是老生常谈但笔试里往往只考表象面试才会深挖。泄漏的核心机制是MessageQueue持有Message的引用Message.target又持有Handler对象如果Handler是内部非静态类它就隐式持有外部Activity的引用。如果消息延迟处理而Activity已经销毁外部引用就一直无法释放。常见解法有几种使用静态内部类Handler通过弱引用指向Activity。在onDestroy()里移除消息mHandler.removeCallbacksAndMessages(null)。能用View.post()就不要自定义Handler。这里最容易被忽略的是移除消息也不是万能的。如果消息已经入队并且正在被处理removeCallbacksAndMessages只能移除尚未执行的消息正在执行的那条无法取消。所以在设计时尽量避免发送无法取消的长延迟消息这才是根治思路。3. 内存与性能优化视频类App面试卷里的隐形主线爱奇艺的业务特性决定了它的Android笔试题会很重视性能和稳定性。这一块题目多但都不偏属于只要平时有线上问题排查经验就能答好的范畴。我重点挑几个高频点展开。3.1 内存泄漏的经典场景别只背答案要学会“找源头”笔试常考“哪些场景会导致内存泄漏”选项无非是静态变量持有Activity、Handler延迟消息、匿名内部类持有外部引用、注册没反注册、单例持有Context等。这些答案大家都会背但面试官真正想听的是你有没有判断泄漏的系统性方法。我的建议是把“泄漏源头”抽象成一句话**只要一个对象的生命周期被另一个生命周期更长的对象持有就可能泄漏。**顺着这句话去套很多题目都能秒解场景被持有对象持有者生命周期更长泄漏原因静态View引用Activity静态变量应用级Activity销毁后仍被静态持有HandlerActivity/内部类MessageQueue中的Messagehandler持有外部类引用监听器注册Activity/Service系统服务/单例忘记反注册线程/异步任务ActivityThread/Runnable任务长期存活持有外部引用针对不同场景修复手段也不同静态变量置空、动态注册一定配对反注册、异步任务用弱引用或生命周期感知组件。另外LeakCanary在开发期是个好辅助但要记住它只能提醒你“哪里可能泄漏”真正判断还是得结合Memory Profiler看GC后对象是否仍被引用别过度依赖某一个工具。3.2 ANR成因分析从“卡了”到“定位到主线程在做什么”笔试里ANR题目多是问“哪个场景会触发ANR”答案基本是四类输入事件5秒未处理完成BroadcastReceiver前台10秒、后台60秒Service前台20秒、后台200秒ContentProvider在发布进程时超时。但真正有区分度的点是如何在线上定位ANR的原因。常规做法是在/data/anr/traces.txt里抓线程堆栈看主线程阻塞在哪。也可以实现Application的Thread.UncaughtExceptionHandler或者接入第三方的APM系统。实际定位时我会按这个顺序排查主线程是否在执行SharedPreferences的apply()或commit()高频IO。主线程上是否做了网络请求、图片加载、大文件读取。是否存在死锁例如主线程等子线程锁而子线程也在等主线程释放资源。是否存在Binder调用长时间无响应的情况比如系统服务异常。有一种很容易忽略的情况主线程持有某个锁而另一个线程持有锁后进入慢操作导致主线程阻塞等待。这类ANR从traces里往往能看到主线程处于Waiting状态而不是Executing状态。如果遇到这种情况单纯优化主线程代码没用要顺着锁的持有链去排查。3.3 布局优化的核心思路为什么老是让“减少层级但不牺牲功能”布局优化题像“如何提升布局渲染性能”这类答案绕不开include、merge、ViewStub、减少嵌套层次、用ConstraintLayout替代多层LinearLayout。这些都对但我想补充一个更底层的思考方式布局的渲染成本测量布局绘制三阶段任何优化都是围绕减少这三次遍历的耗时。ConstraintLayout之所以被推荐是因为它在复杂布局里能用一次测量遍历解决相对约束而传统的多层LinearLayout嵌套会导致measure被多次调用。ViewStub的精髓则是把不常出现的布局比如网络错误提示、新手引导延迟到inflate才加载减少启动时的渲染负担。一个从项目里总结出来的注意点不要过度使用merge。虽然它可以减少一层布局但它对根布局有要求必须是某个特定容器如果include进来的merge布局搭配错了父容器反而会让约束处理更复杂。性能优化到位的前提是先通过Layout Inspector看清到底哪些层级是真正冗余的再动手改不要为了减少层级而减少层级。3.4 图片与列表优化视频App场景下的隐性考点爱奇艺这类业务图片加载和列表滑动是用户高频体验区笔试题里多少会有相关选项。图片优化最核心的是大图采样BitmapFactory.Options里设置inSampleSize按需采样避免直接加载原图导致内存暴涨。inJustDecodeBounds先读宽高再根据目标尺寸计算采样率这一步几乎是所有图片加载框架的底层逻辑。列表优化的关键是复用与异步化RecyclerView的ViewHolder复用机制、notifyItemChanged的局部刷新、图片加载的错位处理。这里有个高频面试追问图片加载回调时怎么避免错位如果你听都没听过说明还停留在“会用框架”阶段。实际上框架通常通过给ImageView设置唯一tag或URL在回调时校验是否还是同一个item来解决自己要实现的话也一样。4. 异步与并发笔试中最容易“代码写得出来、理论讲不透”的板块Android开发避不开多线程这块题目既是高频考点也是很多人丢分的重灾区。原因很简单项目里能用框架解决问题但不一定能清楚说明线程池参数为什么这么配置、AsyncTask为什么被废弃。下面我按知识点拆开讲。4.1 AsyncTask的消亡史为什么官方废弃了它2020年这套题里还有AsyncTask的身影但到了现在它已经被官方标记为废弃。了解它的缺陷反而更能看出Android异步技术演进的逻辑。AsyncTask的问题主要集中在这几点生命周期不同步Activity旋转重建AsyncTask内部的线程还在跑回调回来时可能已经操作了已销毁的View。串行与并行切换的坑默认是串行执行是在API 11之后改的很多老项目升级后莫名其妙变慢改用并行又容易受线程池上限影响。内存泄漏风险内部非静态类持有外部Activity引用。回调丢失进程被系统回收后正在执行的异步任务状态无法恢复。官方推荐的替代方案是Executors、HandlerThread配合线程池或者配合LiveData、协程。我个人现在的习惯是小任务用CoroutineScope中等任务用WorkManager保证可恢复只有极短的操作才直接开线程。笔试如果问AsyncTask除了答它的基本用法一定要点出废弃原因和替代方案这在阅卷者眼里是“见过真实项目”的信号。4.2 线程池参数临界值和队列策略不是背出来的线程池是Android笔试和面试都绕不开的硬核题。核心参数无非是corePoolSize、maximumPoolSize、keepAliveTime、workQueue、ThreadFactory、RejectedExecutionHandler。但真正的分水岭在于你能不能根据业务场景说出每个参数为什么这么设。举个例子一个处理网络请求回调的线程池new ThreadPoolExecutor( 4, // 核心线程数通常取CPU核数1或固定业务并发数 8, // 最大线程数避免无限创建线程拖垮内存 30, TimeUnit.SECONDS, // 非核心线程空闲30秒回收 new LinkedBlockingQueue(64), // 有界队列防止任务无限堆积 new NamedThreadFactory(network-callback), new ThreadPoolExecutor.CallerRunsPolicy() );CallerRunsPolicy是我比较偏好的拒绝策略当任务满时不丢弃任务而是让提交线程通常是主线程来执行从而放慢生产速度。这个策略对视频类的“用户主动刷新”场景很友好——宁愿短暂卡顿一下也不能把用户请求悄悄丢掉。笔试时如果能写出类似分析比单纯列参数高一个层次。4.3 线程、进程与组件分不清这些概念题很容易选错有一类笔试题会混着考Activity默认是哪个进程、Service是前台还是后台、BroadcastReceiver能不能在子线程注册。这类题看上去简单但概念不清就容易翻车。关键点同一App的四大组件默认运行在同一进程但可以通过android:process指定不同进程。Service的onStartCommand默认运行在主线程所以耗时操作要另开线程否则会拖慢主线程甚至ANR。BroadcastReceiver的onReceive也运行在主线程且生命周期很短不能在里面做耗时操作更不能用它启动一个需要长时间运行的线程goAsync()有限制。ContentProvider的onCreate发生在Application之前所以不适合做重初始化。很多线上崩溃和“主线程卡顿”问题本质都是开发者把“组件运行在哪个线程”搞混了。笔试题的价值就在这里它逼你把这些基础边界搞清楚而不是等到线上事故再排查。4.4 进程优先级为什么“后台被杀了”不一定是Bug进程优先级是Android系统资源管理的核心逻辑也是笔试题里的常客。它的本质是系统内存不足时按照进程重要性从低到高逐个回收。优先级从高到低大概排五级优先级类型典型场景1最高前台进程正在交互的Activity、前台Service2可见进程onPause但UI仍可见比如被对话框部分遮挡3服务进程已启动的Service比如后台播放音乐4后台进程用户不可见的Activity已被onStop5空进程无任何活跃组件的进程缓存不保留这个优先级表不光是知识题它还指导了实际开发思路如果你需要后台任务不容易被杀死就要尽量提升进程优先级比如使用前台Service并显示通知反之如果业务允许就别滥用前台服务因为高优先级进程对系统内存压力更大Google对它的限制也越来越多。提示一套题里如果同时出现“进程优先级”和“ANR”它们是有内在关联的。后台进程CPU调度资源受限反而更容易在恢复时发生执行超时。答题时把这两点联系起来会显得对系统机制的理解更立体。5. Activity与组件细节那些“看起来简单一改就崩”的隐藏考点Activity这块是Android笔试的基础题大本营但它绝不是送分题。launchMode、onNewIntent、Configuration变化、SharedPreferences的适用场景每一处都是真实项目中踩过坑才能答得完整的知识点。5.1 启动模式不止会背standard/singleTop/singleTask/singleInstance四种启动模式大家都能说出来但笔试和面试更爱考的是组合场景下会发生什么。最常见的追问是singleTop如果栈顶已是该Activity实例复用而不新建并回调onNewIntent但栈顶不是它时依然会新建实例。singleTask如果栈里已有该Activity实例会把其上的Activity全部出栈让它回到栈顶。这就是“首页”常用的模式避免用户点返回键一层层退出。singleInstance独立任务栈整个系统只有它一个实例。场景极少一般用于需要与外部App共享的界面比如来电全屏界面。这里有一个常见误区以为singleTask能省去onNewIntent的处理。实际上当singleTask复用了已有实例onCreate不会再次调用数据更新必须在onNewIntent里做。很多线上bug就是因为这个回调没被正确处理导致点击通知栏跳转详情页时页面显示的还是旧数据。另外从项目实践来看taskAffinity是容易被忽略的兄弟知识点。它决定了singleTask要寻找哪个任务栈如果没设置默认和包名一致。笔试题如果出“设置了singleTask但怎么不进指定栈”这类题往往就是考这个。5.2 onSaveInstanceState与Fragment状态恢复横竖屏切换的完整链路横竖屏切换相关的题几乎是校招笔试题的固定配置。它的完整链路是这样的屏幕旋转 → Activity销毁重建 → 系统在销毁前调用onSaveInstanceState保存UI状态 → 重建时在onCreate里通过Bundle恢复。常见错点onSaveInstanceState只在非用户主动销毁时调用。点击返回键或finish()时不会调用因为它没必要保存。Fragment的状态恢复涉及FragmentManager自动保存和恢复如果你在onActivityCreated里不加判断地重复添加Fragment很容易出现重叠。android:configChanges只是绕过系统重建代码里自行处理onConfigurationChanged。不建议为省事全局设置因为大量资源语言、字体、深色模式变化都会走这个回调处理不全会带来新缺陷。在实际项目里我的做法是界面状态尽量放进ViewModel通过SavedStateHandle持久化关键数据视图相关的瞬时状态才用onSaveInstanceState。这样横竖屏切换既不会丢数据代码也更清晰。5.3 SharedPreferences低频小数据是对的高频大数据是坑SharedPreferences是笔试里的经典题爱奇艺的卷子里也常出现它的选项。它的本质是XML文件读写每次commit是同步写磁盘apply是异步写磁盘但会阻塞主线程的队列写入。很多人以为apply完全无感但实际上它会把等待写磁盘的任务放到一个QueuedWork队列在onPause/onStop时会等待队列完成数据量大时一样会卡。针对高频写入场景更稳妥的方案是使用DataStore基于协程和Flow异步且支持事务。或者自己设计内存缓存定期批量落盘。不要往SharedPreferences里塞大JSON、大集合它设计的初衷就是轻量偏好设置。笔试题如果问“SharedPreferences适合什么场景”答“低频、小数据量”是基本盘能补一句“高频写入会带来主线程IO风险和ANR隐患”就显出对线上的理解了。5.4 四大组件补充ContentProvider与Application的边界问题Four大组件的基础考察往往落在启动顺序和生命周期上。尤其需要注意的是Application.onCreate()在四大组件创建之前执行是所有组件的基础。ContentProvider.onCreate()的执行时机比Application.onCreate()还要早。因此绝不能在ContentProvider.onCreate()里依赖Application的初始化数据否则会出现空指针或初始化顺序不一致的问题。这类边界问题笔试可能只是选择题但面试里你可能被要求“讲一个你在项目里遇到的初始化顺序问题”。如果平时没有这套知识框架临场很难讲得清楚。所以每次复盘笔试题时我都建议把每个知识点延伸成一个小故事准备1-2个真实案例。6. 从题目反推的知识短板按图索骥建立自己的笔试复习链路做完一套题最重要的工作不是对答案而是反过来检查自己“不知道什么”。我建议把错题和模棱两可的题分别归入到几个知识板块然后针对每个板块做一次深度复盘。6.1 建立“错题驱动”的知识图谱拿爱奇艺这套题为例如果Handler的题错了那么需要补的不仅仅是Handler本身而是一整棵知识树消息机制Looper、MessageQueue、Handler、ThreadLocal内存问题内部类引用、泄漏场景、LeakCanary原理异步演进AsyncTask → Executor → 协程/WorkManagerANR主线程阻塞、traces分析、Binder锁这个知识树的好处是未来面试无论怎么追问你都有话可聊不会出现“只背了结论原理一问三不知”。6.2 真题之外的延伸把每一个选项都变成面试题我复盘题目时有个习惯把每个选项都变成一道面试题自问自答。比如一道关于Activity启动模式的题我会追问onNewIntent里如果不清除旧数据会有什么表现结合Intent.FLAG_ACTIVITY_CLEAR_TOP和singleTop有何区别singleTask和taskAffinity搭配时栈的选择逻辑是什么这种“一题三问”的训练方式比刷十套新题更高效。很多校招笔试的题目是为了筛出“知道的人”而面试是为了筛出“理解的人”。只有平时把知识链条打通才能两头都稳。6.3 实战项目与笔试知识如何互相印证最后说点实际的。很多同学会把“笔试知识”和“项目经验”当成两件事这是很可惜的。其实笔试的每一类题都能在项目里找到对应物Handler题目 → 你项目里的CountDownTimer轮询、点赞防抖、HandlerThread做串行任务。内存泄漏题 → 你查过的LeakCanary堆栈、Memory Profiler的GC记录。布局优化题 → 你对某个列表页做的ConstraintLayout重构、ViewStub延迟加载。多线程题 → 你的图片加载库、网络库的线程池配置。把这些经历串起来笔试就不再是“背题”而是对你日常工程的系统性检验。爱奇艺这套2020年的题今天回看考的还是这些基本功这说明Android对核心基础的要求一直很稳定。把这一套题吃透并顺着知识树往外扩三轮你的笔试和面试地基就扎实了。7. 复盘过后我想单独聊聊答题策略上的几个实际体会知识储备之外答题策略也是能拉开差距的地方。很多候选人不是不会而是把时间耗在个别题上导致后面的简答或编程题没时间写。这里分享几条我实际带人复盘时总结出来的经验。7.1 先易后难但别跳过计算题和读代码题笔试卷子通常有选择题、判断题、简答题、编程题。我的建议是先花2分钟扫一遍全局标记出自己最有把握的题。选择题控制在每题1分钟内超过就标记跳过不要卡壳。编程题或读代码题先看输入输出再想边界条件最后写主体逻辑。遇到不会的简答题哪怕不确定也要写出分析思路而不是留空白。阅卷老师看到你从“什么是Looper”开始推理会比看到空题给分高得多。7.2 写方案题时遵循“结论先行、风险并列、方案补全”爱奇艺这类大厂笔试除了客观题往往还会有一两道“设计/方案题”比如“如何设计一个图片加载库”“如何优化一个列表”。这类题没有标准答案但高分答案有共同特征先一句话给出整体方案比如“采用三级缓存生命周期感知线程池调度”。再分点说清每一层的作用、选型理由、边界情况。主动提出可能的风险比如“内存不足时怎么办”“弱引用的坑是什么”。最后补充一个实际应用场景来印证方案可行性。这套结构本质上就是技术方案评审的简化版。平时在项目里多参与设计讨论答题时自然会有节奏。7.3 笔试后的24小时是知识吸收的黄金时间我的个人经验是笔试结束后24小时内一定要完成一次错题复盘。这个时间点你对题目场景和当时的思路还保有记忆一旦拖过三天就会变成“对答案”效果大打折扣。复盘时别只改答案建立一份“错题-知识点-项目印证”的三栏笔记错题/模糊题涉及知识点项目印证或示例Handler泄漏那题内部类持有外部引用之前在首页用Handler延迟跳转旋转屏幕后崩溃ANR那题主线程IO上线后用户反馈打开设置页卡trace显示SP写入耗时启动模式那题onNewIntent未清除旧数据通知栏点击详情页时数据不刷新三栏笔记写下来你就会发现笔试题和真实项目之间的距离其实并没有想象中那么远。反过来这也会帮你把项目经历讲得更扎实因为这些知识已经有了真实场景作为锚点。8. 回看这份卷子的最终体会Android校招考察的还是“系统性思维”把爱奇艺2020校招Android方向笔试题第二场完整过一遍我的整体感受是这套卷子的考察点并没有偏向某一类“偏题怪题”而是非常务实地围绕Android应用开发的核心链路在考。它要求你既懂语言和系统的底层机制又能从内存、并发、组件、性能等多个维度去审视一个App的可靠性。对于正在备战校招的同学我有个明确的建议不要只盯着“面经”和“题库”背答案而是把每一道题当成一次向系统底层检查的机会。比如你答对了“主线程Looper从哪来”能不能接住“为什么主线程需要Looper阻塞而非死循环”你答对了“线程池有哪些参数”能不能说清“视频App加载弹幕的线程池该怎么配”这些延伸才是阅卷者和面试官真正想看到的素质。作为一路从移动开发走过来的人我深知校招季的时间压力。但Android这套技术栈它的底层机制几十年来并没有大变过变的多是上层的框架和工具。把Handler、线程池、内存模型、组件生命周期这些根扎稳了不管是做业务还是转底层你都会有底气。这套题的价值不在于“做过”而在于“借它把自己从会用的层面往上推一层到懂原理的层面”——这一层恰恰是校招最值得投入时间去磨的地方。