
2019年秋招那会儿我把市面上能刷的Android方向笔试题都过了一遍爱奇艺这套A卷到现在印象最深。题目覆盖面很广四大组件、Handler、View绘制、性能优化、算法都有涉及不是背背八股就能过得去的卷子。这篇文章就以这套A卷为引子把笔试里高频出现的考点、典型答题思路、容易踩的坑完整复盘一遍给准备Android岗位笔试的同学一份可以直接照着复习的清单也适合工作了两三年想回炉基础的朋友。1. 爱奇艺2019秋招Android笔试的定位与考点全景1.1 为什么一张卷子能折射出企业的技术选型很多同学拿到笔试题第一反应是“刷题”但其实出题人有自己的逻辑。爱奇艺这种视频类App客户端日常要面对的是什么是播放器、复杂列表、弱网优化、多任务调度、App启动速度、崩溃治理这些硬骨头。所以笔试不会只考“四大组件是什么”这种背诵题而是把基础知识点包装成场景看你能不能拆解。这套A卷给我的整体感觉是“基础广度优先场景设计压轴”。前面选择题会覆盖Java集合、线程、JVM、Android四大组件、Handler、Service绑定、数据存储这些细碎知识点后面简答题和设计题则是直接把一个真实业务场景丢给你比如“如何设计一个图片加载库”“如何优化冷启动”。也就是说它想考察的是两件事一是你的知识体系是否完整二是你能不能把知识用在工程里。1.2 从题型结构看考察维度以我当时整理的考频来看一张Android校招卷基本可以拆成四大块Java基础、Android基础、性能优化、算法与设计。我拉了一张表方便你们对照自查考点模块常见考察点爱奇艺这类卷子喜欢怎么出Java基础集合、泛型、反射、注解、线程池选择题问ArrayList和LinkedList区别简答线程池核心参数Android基础Activity、Service、Handler、View、存储场景题写生命周期回调顺序自定义View测量流程系统框架AMS、WMS、Binder、动态权限、系统服务问Activity启动流程、IPC原理、为什么广播耗电性能优化内存泄漏、ANR、卡顿、包体积、混淆R8给一段代码找内存泄漏点或问启动优化方案架构与设计MVC/MVP/MVVM、组件化、网络层封装设计一个图片加载库或聊聊Jetpack ViewModel算法链表、字符串、栈、排序、双指针手写代码括号匹配、反转链表、快排边界条件这个分布对后来准备面试也有启发。笔试选择题往往不是单纯记忆而是变着法考你边界条件。比如“下面哪个不会导致内存泄漏”选项里放一个静态变量持有Context、一个Handler发送延迟消息、一个非静态内部类单例如果只背概念不看引用链很容易选错。2. 高频考点逐项拆解拿到题先想清楚这几点2.1 Activity生命周期与启动模式先答顺序再答原因Activity相关题目几乎每家必考爱奇艺也不例外。但直接问“生命周期顺序”太没意思通常会包装成一个操作场景A启动B、按Home、旋转屏幕、按返回分别写出回调顺序。答题建议先记住三组配对再记特殊场景。onPause表示失去焦点但仍可见onStop表示完全不可见两者区别经常被拿出来问。A启动B时回调顺序是A.onPause - B.onCreate - B.onStart - B.onResume - A.onStop。注意onPause在B创建之前执行目的是让前台Activity先让出焦点避免新窗口出现时画面闪跳。旋转屏幕时默认情况Activity会销毁重建顺序是onPause - onStop - onDestroy - onCreate - onStart - onResume。旧实例的onSaveInstanceState会在onStop之前被调用用来保存临时状态。启动模式也常考建议按下面这个框架答standard每次都新建实例任务栈里可以有一堆相同Activity。singleTop如果目标Activity已经位于栈顶则复用栈顶实例并回调onNewIntent否则新建。singleTask目标Activity在栈内唯一若栈中已有实例会把它上面的Activity全部出栈然后回调onNewIntent。singleInstance所在任务栈只放这一个Activity整个系统里只有一个实例常用于来电界面这类全局唯一的场景。还要注意FLAG_ACTIVITY_NEW_TASK、taskAffinity这些外部条件会影响Activity归属的任务栈。比如用ApplicationContext启动一个没有配置FLAG_ACTIVITY_NEW_TASK的Activity会直接崩原因是Activity需要放到一个任务栈里。2.2 Handler、AMS与系统服务不要停留在背概念Handler是Android消息机制的核心也是笔试和面试都绕不开的点。高频问题有这些一个线程能不能创建多个Looper不能。Looper.prepare()会检查sThreadLocal中是否已有Looper重复调用直接抛异常。主线程Looper为什么能一直运行因为Looper.loop()是一个死循环不断从MessageQueue取消息没有消息时通过epoll机制阻塞不会占满CPU。Handler如何切换线程本质是Handler持有Looper的引用你可以在子线程post一个Runnable最终由目标线程的Looper取出消息并执行。为什么不能在子线程更新UI因为ViewRootImpl会检查线程不是UI线程就直接抛异常。根本原因是UI访问不是线程安全的不锁是为了性能。如果你的笔试里有系统框架题AMSActivityManagerService也是爱奇艺这类公司喜欢深挖的点。Activity启动时AMS负责调度任务栈和进程生命周期中间通过Binder与App进程通信。整体链路可以理解成Launcher进程通过Binder告知AMS要启动ActivityAMS根据启动模式决定是复用还是新建任务然后通过ApplicationThread通知App主线程去创建Activity最终由ActivityThread.handleLaunchActivity执行onCreate。这链路里面有个细节AMS是系统进程里的服务App进程是独立的跨进程只能靠Binder。所以“App进程直接new Activity”是错的所有调度都由AMS协调App进程只是被动接收指令。系统服务相关题目还会涉及电话状态监听PhoneStateListener、蓝牙适配、传感器管理这些。2018年之后动态权限模型升级这类题经常以“动态权限申请”的形式出现。答题时记得区分普通权限、危险权限和特殊权限不要上来就说在Manifest里加权限就行运行时还要requestPermissions。2.3 View体系与自定义View测量流程是必答点自定义View题目在笔试里一般不会让你真的写完整控件而是考测量流程和绘制顺序。我建议按照“MeasureSpec - measure/layout/draw三流程 - 常见控件套路”去答。onMeasure中要处理三种模式EXACTLYmatch_parent或固定值MeasureSpec.getSize就是最终尺寸。AT_MOSTwrap_content时要做内容测量否则默认会得到父容器给的size看起来和match_parent一样这是很多人踩过的坑。UNSPECIFIED一般在ScrollView等可滚动容器中出现测量时不要限制子View高度。答题时如果手写框架可以这样写ViewRootImpl.performTraversals - performMeasure: 从DecorView开始逐层分发MeasureSpec - performLayout: 每个View按测量结果布局 - performDraw: 绘制背景、内容、子View、装饰顺序不能错View的绘制顺序是背景 - onDraw内容 - 子View - 装饰foreground。如果自定义View里用了setWillNotDraw(true)且没有设置背景系统会认为你不需要绘制onDraw可能不会被调用。RecyclerView的缓存机制也经常被拿来考View复用和性能需要理解四级缓存mAttachedScrap、mCacheViews、mViewCacheExtension、mRecyclerPool。缓存Key是position或id不是ViewHolder类型。这也解释了一个老生常谈的问题RecyclerView比ListView性能好主要不是因为强制ViewHolder而是因为缓存结构更细支持多级复用和局部刷新。2.4 性能优化与代码混淆R8面试官真正想听什么性能优化这个模块笔试容易出现“给一段代码找问题”和“方案设计”两种题型。内存泄漏是重点中的重点答题时抓住一条主线长生命周期对象持有了短生命周期对象的引用。典型场景要能枚举出来Handler作为非静态内部类隐式持有外部Activity引用延迟消息未移除。静态变量直接持有Activity或View。单例模式持有Context尽量用ApplicationContext。属性动画无限循环没有在onDestroy中取消。数据库游标、IO流没有关闭。启动优化这块要能说清楚冷启动大致做了什么加载Application、创建MainActivity、首帧绘制。方案一般包括避免在Application构造和onCreate里做重活、把非必要初始化放到子线程、使用Jetpack Startup或者异步启动器做任务调度、用启动器对基础库做拓扑排序保证初始化顺序。布局优化就简单一点减少层级、用ConstraintLayout、include/merge/ViewStub。包体积优化会问混淆和收缩。Android 3.4之后R8默认开启替代了ProGuard在gradle里配置release时同时开启minifyEnabled和shrinkResourcesandroid { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } } }面试官在这个点喜欢追问“R8和ProGuard有什么区别”。R8不只是混淆它同时做压缩、优化、混淆和脱糖并且去掉了ProGuard里一些保守策略所以包体积更小、构建耗时更短。最容易翻车的是keep规则缺失反射、注解、Gson、Retrofit、自定义控件等都要保留不然发布后会出现运行时找不到类。规则大致长这样-keep class com.example.model.** { *; } -keepattributes Signature -keepattributes *Annotation* -keep class com.example.api.** { *; }3. 三类实操题的完整复盘与答题模板3.1 生命周期场景题从点开图标到按Home键再恢复这种题在笔试题里出现频率极高考察的不是你会不会背生命周期而是能不能清晰推断每一步的系统行为。场景是这样从桌面点开MainActivity然后按Home键回桌面再点击图标恢复最后按返回退出。完整回调顺序是什么按顺序写出来是首次启动onCreate - onStart - onResume按下HomeonPause - onStop点击桌面图标恢复onRestart - onStart - onResume按返回退出onPause - onStop - onDestroy这里有个细节容易错恢复时没有onCreate只有onRestart因为Activity实例没有被销毁。但如果系统在后台回收了进程那么再次进入时会重新走onCreate。所以答题时最好补充一句这个顺序建立在“实例仍然存活”的前提下。更进一步如果屏幕旋转默认情况下Activity销毁重建顺序是onPause - onSaveInstanceState - onStop - onDestroy - onCreate - onStart - onResume。onSaveInstanceState在onStop之前调用这一点很多资料写得不清楚。3.2 手写线程安全的单例与多线程题目算法与设计题里“手写单例”几乎可以说是保留节目。当时爱奇艺这套卷子就有一道让你写线程安全的单例表面看是代码题实际考的是并发知识。推荐用双重检查锁DCL版本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不为空时直接返回避免每次getInstance都进入synchronized。第二为什么instance要加volatile因为instance new Singleton()不是原子操作底层会分成“分配内存、初始化对象、赋值引用”三步指令重排后可能出现另一个线程拿到一个半初始化的对象。volatile禁止了这种重排序。如果不想写DCL也可以写静态内部类版本public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }这个版本利用类加载机制保证线程安全代码更简洁但面试官可能会追问“类加载阶段不也是多线程安全的吗为什么”答案是JVM保证类的加载过程在同一个类加载器中只会执行一次初始化阶段会有同步锁保护。如果是算法题里的多线程常见的是“两个线程交替打印1到100”。可以用wait/notify也可以用ReentrantLock。笔试时只要能正确写出来就行面试时才会追问“可不可以不用锁”。3.3 网络层设计与HTTPS握手爱奇艺这种视频公司网络层面的题目不会少。如果让设计一个网络请求层通常希望听到OkHttp Retrofit这套组合方案因为它是工程实践里最通用也最易扩展的答案。回答要点可以覆盖统一通过Retrofit接口定义API底层由OkHttp完成网络请求。OkHttp的Dispatcher负责同步/异步任务调度连接池复用TCP连接拦截器链把重试、重定向、缓存、通用Header串起来。可以自定义Interceptor实现统一日志、统一加密、token自动刷新。返回数据用Gson或Moshi反序列化成统一Response模型通过LiveData或Flow上抛到UI层。HTTPS握手也是高频考点。答题时不要把流程写成教科书式的大段文字建议分步骤列出TCP三次握手建立连接。客户端发送ClientHello携带支持的TLS版本、加密套件列表和随机数。服务端返回ServerHello、证书和公钥。客户端校验证书有效性校验证书链、域名、有效期。客户端生成随机预主密钥用服务端公钥加密后发给服务端。双方根据三个随机数生成对称会话密钥。后续通信改用对称加密非对称加密只用于握手阶段。如果笔试里问“HTTPS和HTTP差别”可以补一句HTTPS不是新协议而是HTTP加上TLS/SSL层性能上会比HTTP多出握手开销所以长连接和连接池优化尤为重要。这也回应了视频类App为什么重视弱网优化握手慢一步用户等待时间就上去了。4. 笔试中的高频易错点与排查技巧实录4.1 我踩过的几个坑笔试是踩坑重灾区很多题不是不会而是细节没注意到。下面几个坑是我自己或者身边朋友真实经历过的拿出来分享。第一个坑是生命周期版本差异。网上很多博客把A启动B的onPause/onStop顺序写错了写成了A.onPause - A.onStop - B.onCreate。这个在Android 10之前其实在某些表现上会让人混淆但严格回调顺序仍然是旧的先onPause然后新Activity创建旧Activity才onStop。如果有疑问直接在Android Studio里跑一遍Log验证别信博客。第二个坑是颜色透明度。自定义View或者设置背景色时经常要用到十六进制颜色值比如#80000000表示半透明黑。我一开始总记不住alpha值后来直接用透明度对照表FF完全透明、80差不多半透明、00完全透明。这个看起来是小事但笔试选择题偶尔会考某个十六进制颜色对应的透明度不知道就会丢分。第三个坑是混淆规则漏配。当时朋友做个release包在低版本机型上偶发崩溃打开日志发现是Gson反序列化时找不到字段。原因就是R8把model类里的字段混淆掉又没有配置keep规则。这个故事告诉我们R8/混淆配置不是上线前的收尾工作而是从项目一开始就要写好基线规则。第四个坑是Android 8之后的自适应图标。如果项目targetSdk升到26以上仍然只放一张普通圆形图标部分机型桌面会显示成一个白底方块因为系统会强制按自适应图标规则展示。处理办法是提供adaptive icon的foreground和background或者至少用兼容模式的白底图标。这类题在笔试题里可能不会单独出现但在项目介绍和选择题“关于图标适配”里会考到。4.2 高频问题速查表为了方便临考前快速过一遍我把笔试过程中反复出现、又容易记混的问题整理成了速查表问题答案要点onNewIntent什么时候回调单例启动模式下实例已存在新Intent传入时复用实例并回调Activity A启动BA完全不可见时onStop在哪个时机B.onResume之后A.onStop才会执行为什么不能用ApplicationContext启动一个standard Activity因为Activity需要任务栈ApplicationContext没有栈必须加FLAG_ACTIVITY_NEW_TASKRecyclerView和ListView在缓存上的关键区别RecyclerView有四级缓存结构缓存粒度是ViewHolder支持局部刷新和自定义缓存策略Parcelable和Serializable怎么选Android首选Parcelable性能好Serializable反射实现适合需要持久化到文件或网络的场景Handler内存泄漏的修复套路静态Handler WeakReference并在onDestroy移除消息和回调触发ANR的常见条件输入事件5秒无响应、广播10秒未完成、服务20秒超时时间随版本有调整主线程Looper进入阻塞会不会导致卡顿不会阻塞发生在native层只有新消息到达时才唤醒卡顿通常是积累的待处理任务太多Binder为什么效率高相比传统IPC一次数据拷贝足够配合mmap和内核缓冲区完成通信R8和ProGuard有什么区别R8一体化完成压缩/优化/混淆/脱糖构建更快缩减效果更好但规则配置要注意兼容性这些内容不一定每道题都考但把它们串起来正好覆盖了一套Android笔试的主要得分点。复习时不要只背表格最好每一个点都自己能说出一个两三句话的解释。5. 考后复盘从笔试到面试的进阶准备5.1 笔试测知识面面试看表达爱奇艺这套A卷做下来我最大的感受是笔试过不过取决于你的知识体系是不是立体的。很多人只背了“四大组件”和“Handler”这种零散知识点遇到“Activity启动流程中AMS做了哪些事”就卡住了因为脑子里没有一个完整的图景。笔试之后真正拉开差距的是面试。笔试你只要写出关键步骤就行面试官会顺着你的答案一层层往下问。比如你写了“A启动B时先onPause再onCreate”他立刻追问“为什么这样设计”如果你能答出“为了避免黑屏闪烁先让前台Activity让出焦点系统再创建新页面”这道题就拿到了基础分。再多答一句“onPause里不能做重活否则会影响下一个Activity启动速度”这就能拿到高分。所以备考时不能只刷题要给自己做“阅读源码”的训练。不需要把ActivityThread源码全部背下来但关键类的职责要搞清楚ActivityThread是App主线程入口Instrumentation负责调度Activity创建和生命周期调用AMS是系统侧的调度者ApplicationThread是App进程的回调接口。5.2 建议的复习顺序如果你现在准备秋招我建议的复习顺序是“基础机制 - 项目实践 - 综合设计”不要一上来就追新框架。第一步先把Java基础和Android四大组件吃透尤其是Handler、MessageQueue、Looper这条消息链以及Activity生命周期和启动模式。第二步是拿一个自己做的完整项目把列表加载、图片缓存、网络请求、崩溃日志这些模块从原理层面捋一遍。比如你的项目用了RecyclerView能不能说清楚它为什么高效能不能解释清楚ViewHolder复用的条件能不能在列表滑动卡顿时用Layout Inspector看布局层级。第三步才是看MVVM、Kotlin协程、组件化这些进阶内容。Jetpack ViewModel、LiveData、DataBinding的设计思想要理解不过2019年这套笔试题考得最重的仍然是基础后来面试也一样基础不牢谈架构就是空中楼阁。复习工具方面Android Studio的几个自带工具一定练熟Profiler看内存和CPU、Layout Inspector看布局层级、Logcat过滤崩溃堆栈。如果做线上问题排查还要会用LeakCanary和矩阵这类第三方工具。这些是笔试后阶段的加分项面试讲项目时提一下“我用Layout Inspector发现了一个多余嵌套减少了三层布局”比干巴巴背概念有说服力得多。写到这我自己也回看了当年的复习笔记。秋招是一个压力很大的阶段每套卷子都像一次浓缩的体检让你快速发现哪里还有漏洞。爱奇艺这套A卷之所以让人印象深刻不是因为它特别难而是它把Android工程师应该具备的知识面很完整地串了一遍。如果你现在正在刷这套题不必追求把每道题背下来而是要把题背后连接的源码和原理彻底弄明白。基础扎实了笔试和面试都只是换一种方式的检验而已。