1. 为什么会有“卷二”一场笔试想筛掉的不只是背答案的人1.1 一套笔试为什么要拆成两卷2020年前后的Android校招笔试已经很少有公司愿意只靠一套卷子定生死了。小红书这套题之所以被反复拿出来讨论很大程度是因为“卷二”这个名字本身就说明了一个事实卷一负责海选基础分拿满就能过卷二才是真正拉开差距的地方。我接触过不少准备校招的同学他们对卷二的误解主要有两种。一种是觉得“卷二就是把卷一没考到的知识点再补一遍”于是疯狂刷题背结论另一种是觉得“卷二肯定难到超纲”干脆只复习高频考点遇到开放题就随便写几句。这两种心态到最后都容易吃亏。实际上卷二考察的不是你记住了多少知识点而是你能不能把一个知识点讲清楚“为什么”能不能把多个知识点串成一条完整的逻辑链。1.2 卷二的高频考点与考察逻辑把时间拉回2020年Android主流版本已经到Android 10/11面试官在笔试里想看到的东西其实很一致Java基础与并发、四大组件、Handler与Binder、自定义View与事件分发、性能优化与异常排查。题型通常是一批选择题、几道简答题和一道开放设计题。选择题的迷惑项往往不是凭空编的而是来自真实的开发误区。比如“子线程里new Handler会怎样”“onCreate里拿View宽高为什么是0”“HashMap在并发put时发生了什么”。简答题则更偏向让你画流程Activity启动模式如何影响返回栈、事件分发时Down事件到底先给谁。而最后的开放题通常给你一个内容类App的通用场景比如图片列表卡顿、启动变慢、内存上涨让你给出分析思路和优化方案。1.3 先建立正确心态卷二考的是“为什么”我给准备笔试的人的第一个建议是把“背答案”改成“推因果”。同一个知识点别人只背“Handler会导致内存泄漏”你要能说出“MessageQueue持有MessageMessage持有HandlerHandler持有外部Activity所以Activity无法回收”。这两句话在笔试里的得分差距是明显的。这里也要说明一下我没有办法拿到完整的官方原题下文会基于同类校招笔试题和当时流传出来的考点复盘来展开。你可以把这篇文章当成一份“高频考点拆解答题思路”的参考资料而不是逐字逐句的原题题库。理解了这一点后面的内容才能真正帮你建立答题框架。2. 从考点分布反推笔试官真正想考察的能力2.1 卷二相对卷一的题型难度变化卷二的选择题比卷一更“毒”。它们往往不会问你一个简单的概念而是给一段很短的代码让你判断有没有问题。比如public class Test { private static SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); }问这段代码在多线程环境下是否安全如果不安全是哪个线程安全性问题这种题的恶心之处在于很多同学对SimpleDateFormat的认知是“格式化日期的工具类”根本没想过它内部持有一个Calendar对象多线程并发调用format或parse时会产生状态竞争。这种题背后考察的是“你是否关注过常用类的线程安全性”而不是单一知识点本身。简答题同样不会只要求一句话。问Service就会给两种启动方式混用的场景让你推生命周期问Activity就会问singleTask和taskAffinity混在一起时Activity到底进哪个栈。要适应这种考法你在复习时就要养成一个习惯每学一个机制至少给自己提两个“如果”问题。如果它被另一个机制影响会怎样如果调用顺序换一下会怎样2.2 高频考点版块、出题形式与能力意图考点版块典型出题形式出题意图Java与并发HashMap流程、CAS、锁升级、JMM判断是背结论还是懂原理四大组件启动模式、Service混用、状态保存考察组件生命周期的精确记忆消息与进程通信Handler源码流程、Binder/AIDL考察底层机制和线程模型自定义View与事件分发测量模式、Down事件、滑动冲突考察是否真的写过UI而不是只看过控件性能优化与异常ANR、内存泄漏、启动优化考察线上问题排查思路开放设计题图片缓存、列表卡顿、崩溃治理考察方案完整度和表达能力这张表很值得反复看。如果你准备笔试时发现某个知识点只会“说出名词解释”但不知道它对应表格里的哪一列那大概率在卷二里拿不到分。2.3 出题背后的信号卷二想要的是有工程直觉的人从出题意图能明显看出卷二想筛的不是“题库型选手”而是有工程直觉的人。什么叫工程直觉就是看到一段代码能下意识判断它会有什么运行时的风险线上报了一个卡顿知道从哪几个方向入手排查设计一个功能能说清楚选这个方案的理由和代价。这种能力不是考前突击出来的而是靠平时写代码、看源码、排查问题一点点积累的。所以在复习阶段我强烈建议每个考点都配一个“动手验证”的动作。比如事件分发就在Android Studio里写两个自定义View重写三个方法打印日志把Down、Move、Up的事件流完整看一遍。这套动作看起来慢但比刷十套题都有效因为你的记忆变成了有场景支撑的经验而不是孤立的文字。3. Java/并发/内存卷二里最容易扣分的“基础题”3.1 HashMap、HashTable、ConcurrentHashMap 的“为什么”选择题里出现频率最高的一组对比就是HashMap、HashTable、ConcurrentHashMap。很多人背得住结论但卷二会往深处问一步HashMap的put过程是怎样的扩容时为什么会有并发问题我的回答框架是这样的HashMap底层是数组加链表Java 8之后链表长度超过8且数组长度达到64时会转成红黑树。put时先通过扰动函数拿到哈希值对应的桶下标如果这个位置没有元素就直接放如果有元素就按链表或树的方式插入插入后判断size是否超过threshold超过就resize。resize要重新计算每个节点的位置这个过程中如果有另一个线程同时put可能会造成数据覆盖、元素丢失等问题。比较经典的坑是“HashMap为什么不适合并发”。因为旧版本JDK中并发扩容可能产生环形链表导致get时死循环Java 8改成尾插后环形链表问题缓解但数据覆盖问题依然存在。ConcurrentHashMap在Java 8里用CAS加synchronized锁桶头节点把锁粒度降到很细。如果面试官再深问一层“为什么不用HashTable”你还可以补一句HashTable是对整个方法加synchronized并发一高就全表竞争锁粒度太大。3.2 JMM、volatile 与 synchronized 的锁升级过程要说卷二最爱考的知识点JMM绝对排得上号。选择题喜欢给出一段多线程代码让你判断会不会出现可见性问题或者问“volatile能不能保证原子性”。回答这类题先明确三个概念可见性、有序性、原子性。volatile保证可见性和有序性不保证原子性synchronized则同时保证互斥、可见性和有序性。进阶一点的问法是“synchronized在Java 6之后引入了哪些优化”。网上常说的答案偏向锁、轻量级锁、重量级锁无锁到偏向锁、轻量级锁、重量级锁的升级过程。要注意的是偏向锁在较新的JDK里已经默认废弃但当时笔试以JDK 8为主这部分仍然需要掌握。答题时尽量画出锁升级的路径然后把触发条件说清楚同一线程再次进入时偏向锁竞争不激烈时自旋自旋失败或竞争强烈时膨胀为重量级锁。3.3 强软弱虚引用与内存泄漏的典型场景内存泄漏在笔试题里几乎是必出的而且往往不是单独问而是结合Handler、静态变量、内部类一起考。最容易考的是四种引用类型以及“这段代码为什么会泄漏”。典型的泄漏场景包括静态变量持有ActivityHandler作为非静态内部类持有外部Activity匿名内部类Runnable持有外部对象导致GC无法回收BroadcastReceiver注册了没解绑文件流或数据库游标没有关闭。假设题目给出这样一段代码public class MainActivity extends Activity { private Handler mHandler new Handler() { Override public void handleMessage(Message msg) { // 更新UI } }; }问这会不会造成内存泄漏如何修复我的回答思路是会。因为Handler发送的消息最终会被MessageQueue持有而Message.target指向这个HandlerHandler又持有外部类MainActivity的隐式引用所以只要消息还留在队列里MainActivity就无法被回收。修复方式有两个维度一是在onDestroy里调用removeCallbacksAndMessages(null)把消息清掉二是把Handler定义成静态内部类用WeakReference持有Activity也就是大家常搭的Handler WeakReference模板。3.4 泛型、反射、注解与类加载卷二也会出现Java进阶知识但占比没有前面几块重。泛型主要考察“类型擦除”例如List 和List 在运行时的Class对象是一样的反射则经常和Retrofit、EventBus等框架结合问你为什么运行时能拿到注解、为什么有的注解用CLASS保留策略。这里最值得掌握的是“怎么用反射解决泛型擦除后的类型获取问题”比如Gson的TypeToken就是通过匿名内部类捕获父类泛型参数来拿Type的。类加载器这块2020年前后题目里也常出现问“双亲委派机制是什么”“为什么自定义类加载器要先交给父加载器”。一句话概括避免核心类被重复加载、防止核心API被篡改加载一个类时先沿着父加载器向上查找找不到才由自己加载。如果笔试里真遇到能画出这个从AppClassLoader、PlatformClassLoader到BootstrapClassLoader的查找链路已经能拿大部分分。3.5 快速自查表考点掌握到什么程度常见翻车点HashMap流程能画图 说清为什么线程不安全只答“线程不安全”但说不清原因volatile能说出“不保证原子性”及原因误以为volatile能替代synchronized锁升级会画触发条件只背名称不问触发条件引用类型结合内存泄漏场景使用分不清软引用和弱引用类加载机制双亲委派一句话查链不知道为什么要双亲委派4. 四大组件与消息机制Binder、Handler 连起来问的硬骨头4.1 Activity 启动模式、返回栈与 onNewIntentActivity四种启动模式是卷二必然碰到的。标准、singleTop、singleTask、singleInstance每种的入栈出栈规则要能说清还要能说出对应场景。比如singleTop适合接收推送通知跳转的页面避免重复创建singleTask适合App主页singleInstance适合需要单独占一个任务的场景比如来电界面或桌面快捷方式。很多同学知道四种模式的名字但一问“A启动BB是singleTaskB没有设置taskAffinity会进入哪个栈”就卡壳。答案其实是A所在的任务栈。如果给B设置新的taskAffinity就会在对应Affinity的任务栈里找没有就创建新栈。所以taskAffinity要和启动模式一起理解单独背一个结论容易出错。另一个高频点onNewIntent什么时候调用当启动模式或IntentFlag导致Activity被复用而不是重新创建时系统会走onNewIntent而不是onCreate。需要注意收到新Intent时应该重写onNewIntent并调用setIntent更新Intent对象否则getIntent可能还是旧值。笔试题里经常把这个点包装成“为什么从通知栏点击两次Activity没有重新走onCreate”。4.2 Service 和 BroadcastReceiver 的生命周期陷阱Service这块卷二喜欢对比startService和bindService。startService启动后执行onCreate、onStartCommand、onDestroybindService启动后执行onCreate、onBind、onUnbind、onDestroy。如果既有startService又有bindService必须同时stopService和unbindService才会销毁。Android 8.0之后后台执行startService受到限制所以前台服务要配合Notification展示。这些知识点到现在写业务仍然绕不开。BroadcastReceiver的考察主要集中在动态注册和静态注册的区别。动态注册在代码里registerReceiver必须在onDestroy里unregisterReceiver否则会内存泄漏静态注册通过Manifest声明Android 8.0开始对隐式广播做了限制。答题时要学会用“是否显式广播、是否属于豁免的系统广播”来判断能不能静态注册而不是死记硬背具体广播名。4.3 ContentProvider、FileProvider 与 URI 那些事ContentProvider本身考察不多但它和Binder、跨进程、文件共享连在一起时就是一道综合题。至少要理解ContentProvider是四大组件里唯一天生支持跨进程数据共享的组件生命周期onCreate先走但具体query/insert/update/delete会发生在Binder线程。URI格式是content://authority/path/id比如内容App如果用FileProvider分享图片会在配置文件中声明provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider这里面的坑包括authorities必须与应用包名匹配paths里的路径不能写错否则会抛IllegalArgumentException对外分享时要配合Intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)授权。卷二如果给一段FileProvider配置让你找错多半是在这里设伏。4.4 Binder为什么跨进程通信选它Binder属于性价比极高的考点因为只要理解了它AIDL、ContentProvider、系统服务这些都能串联起来。常见问法是为什么Android不用传统的共享内存或Socket来做跨进程通信而是选择Binder我的答法分三层。第一层性能Binder采用一次拷贝把用户空间数据拷贝到内核空间一次传统管道、Socket需要两次拷贝。第二层安全Binder内核层为每个进程分配UID/PID支持身份校验而传统共享内存机制没有内置权限控制。第三层易用性Binder提供类似面向对象的方法调用开发者写AIDL就能完成跨进程接口调用比手工处理协议简单。如果再深入可以说说mmap映射Binder通信中内核把接收进程的用户空间缓冲区与内核缓冲区通过mmap做了映射所以只需拷贝一次。4.5 Handler、Looper、MessageQueue从源码到内存泄漏Handler是Android里被问得最频繁的机制没有之一。卷二里通常不是单独问而是作为一个链条主线程为什么能一直运行而不退出答主线程的Looper.loop是一个死循环不断从MessageQueue取消息并分发如果队列为空会进入阻塞等待所以不会占满CPU。这也是“主线程死循环却不会卡死”的根本原因。下一步常问为什么子线程不能直接new Handler因为Handler构造时需要通过Looper.myLooper()获取当前线程的Looper而子线程默认没有Looper需要先调用Looper.prepare()创建Looper再调用Looper.loop()启动循环。ThreadLocal在这里起到关键作用每个线程保存自己的Looper互不干扰。再往下同步屏障和IdleHandler也偶尔出现。同步屏障用来拦截同步消息优先处理异步消息常用于UI绘制和ChoreographerIdleHandler则是在消息队列空闲时执行轻量任务比如启动优化里把非必要初始化放到IdleHandler中。这部分如果能在回答时画一条时间线从主线程创建到消息入队、出队、分发基本就稳了。5. 绘制、事件分发与性能优化压轴大题最爱考的“工程题”5.1 自定义View的三板斧measure、layout、draw关于自定义View卷二选择题里最常见的考点就是测量模式。三个模式EXACTLY确定值或match_parent、AT_MOST最大不超过某个值对应wrap_content、UNSPECIFIED未指定ScrollView等场景。如果自定义View没有重写onMeasure使用wrap_content时默认会表现成match_parent的效果这是新手最容易踩的坑。解法是自己在onMeasure里处理AT_MOST时的默认尺寸。onLayout主要处理子View的位置onDraw负责绘制内容。笔试还常问“onDraw里能不能做耗时操作”答案当然是不能。如果某个View频繁invalidateonDraw会被反复调用任何创建对象、IO、复杂计算都应该避免出现在onDraw里。回答这类题时最好再加上一句“绘制耗时和布局层级有关可以用Profile GPU Rendering或GPU调试来定位”这样会显得你不只背了概念。5.2 事件分发机制dispatch、intercept、touch事件分发是卷二简答题的常客。核心是三个方法dispatchTouchEvent负责分发onInterceptTouchEvent负责拦截onTouchEvent负责消费。答题时先交代顺序一个Down事件先传给ViewGroup的dispatchTouchEvent内部先问onInterceptTouchEvent要不要拦截不拦截就按层级往下传给子View子View的onTouchEvent如果返回true后续Move、Up事件继续交给它如果返回false则向上回传给父级处理。选择题经常考“为什么Down事件返回true是关键”。因为事件序列从Down开始Down的返回值决定了整个序列交给谁如果Down没有被消费后续Move和Up可能不会再传给这个View。另一个高频题是滑动冲突横向ScrollView里嵌套纵向RecyclerView应该怎么处理一般思路是重写父容器onInterceptTouchEvent根据横向和纵向的滑动距离差决定是否拦截或者用NestedScrolling机制配合RecyclerView解决。5.3 RecyclerView缓存复用与列表优化列表类App的开放题里RecyclerView缓存是绕不开的。至少要能说出它的四级缓存一个ViewHolder在屏幕内被回收后先进入CachedView容量有限超出容量会进入RecyclerViewPool按ViewType复用还有用于item动画的CacheExtension等。常见的坑是在onBindViewHolder里创建新对象、在getView里做网络请求都会影响滚动流畅度。卷二如果给一个图片列表卡顿的现场让你排查通常可以从四个方向答主线程是否有耗时操作图片是否做了压缩和内存缓存布局是否过度绘制onDraw和onBind中是否频繁创建对象。这四个方向能对应到具体的排查工具比如Systrace看主线程耗时、Memory Profiler看内存分配、Layout Inspector看布局层级。工具链完整答案就有了工程味。5.4 启动优化、ANR与内存优化开放题的“分析框架”2020年前后的笔试题很喜欢把性能优化放在最后一道大题里。比如给出一个App冷启动时间过长让你分析可能原因并给出优化方案。我的答题框架是先明确冷启动阶段做了什么。App启动到首帧大致是Application attach - onCreate - Activity onCreate - onResume - onWindowFocusChanged这些节点再列出可疑点Application里是否做了大量初始化、主线程是否有耗时IO、首屏布局是否过深、启动时是否有网络请求阻塞最后给优化方案延迟初始化、异步线程、减少布局层级、ViewStub加载、启动页做渐变过渡等。ANR的触发条件也要能背熟输入事件5秒内没处理完BroadcastReceiver前台10秒/后台60秒Service前台20秒/后台200秒。出现ANR时看traces文件、Logcat、CPU使用率、线程状态才能定位到具体是主线程锁竞争还是死循环。这部分非常值得在准备笔试时动手复现一次Android Studio的CPU Profiler或火焰图因为有实际工具链支撑的回答在笔试阅卷时明显比背模板更有说服力。6. 一套能落地的卷二备赛清单重点、节奏和临场策略6.1 考点掌握程度分级别在低频冷门里浪费太多时间根据我当时看到的校招真题情况和笔试通过者的反馈可以给这套题划出三个等级。必须脱口而出Activity四种启动模式、生命周期Handler/Looper/MessageQueueService两种启动方式的区别HashMap与ConcurrentHashMap基础内存泄漏的常见场景。理解可推导Binder机制、事件分发、自定义View测量、RecyclerView缓存、ANR触发、JMM和锁升级、ContentProvider和FileProvider。了解即可具体系统版本里的某个广播字段、R8混淆规则、APEX模块这些如果没做过专项不建议花太多时间背因为性价比低。R8这种题即便出现也更多是问“是否开启混淆、proguard文件作用”不需要你背规则细节。6.2 90分钟笔试的时间分配建议我以前建议参加这类笔试的同学做时间控制选择题尽量控制在20分钟内简答题35分钟开放设计题30分钟最后留5分钟检查。卷二的选择题里藏着很多“陷阱”你越纠结越容易错简答题反而是得分大头把因果