公司动态
网易Android校招笔试全解析:从Java基础到Handler与ANR
拿到网易2018校园招聘Android开发工程师笔试卷的那一刻我第一反应不是紧张而是先花了五分钟把整张卷子从头翻到尾。说实话它和我在牛客网上刷过的那些“冲刺模拟题”风格差得挺远没有把分值赌在某一个新框架上也没有刻意堆偏难怪题而是用选择题、问答题、编程题三块内容把Java基础、Android核心机制、工程分析能力依次摆开。做完一遍再回头看你才会意识到这种出题方式其实是在传递一个信号——校招笔试筛的不是谁见过更多新东西而是谁的基本功足够扎实。这篇文章我就把那份笔试卷拆开讲每一类题型到底在考什么、答题时容易忽略哪些细节、以及从这套卷子里能反推出一套怎样的备考节奏。无论你是正在准备Android校招的应届生还是想测一测自己Android地基还牢不牢的开发者都可以对着这份拆解自查一遍。1. 网易校招Android笔试的题型构成与答题节奏1.1 题量、时间与分值90分钟里要完成什么当初那份卷子在线笔试时长约90分钟总分100分从题量设置上明显是卡着“合格线”而不是“满分线”来设计的。粗略估计单选和多选加起来大概在20道左右覆盖Java基础、Android组件、网络、数据结构问答题一般有2到4道考察对机制的理解深度编程题在1到3题之间以基础算法和简单场景设计为主。整套卷子考完你会明显感觉到网易不像在找“背题家”更像在找“一个能上手干活的人”。我复盘下来几个题型的定位其实很清晰选择题用来在短时间内铺开知识面区分“学过”和“见过”问答题用来区分“背过”和“理解”编程题则直接检验手写代码的硬功夫。很多同学把时间全押在算法题上结果选择题丢分丢到心碎这个后面单独讲。答题节奏上建议按“先选择题、再问答题、最后代码题”的顺序推进前两类题会顺手把知识状态激活写代码时思路会更顺。时间分配可以参考选择题40分钟左右问答题30分钟剩下20分钟留给代码题。1.2 从题目分布看出的出题侧重点如果给这份笔试卷做一个粗略的考频统计大概会是下面这个体感考点模块常见题型体感占比Java集合与并发单选/多选20%Android四大组件与消息机制单选/多选/问答25%网络与数据存储单选/问答10%自定义View与事件分发单选/问答10%性能优化ANR/OOM/内存泄漏问答/多选10%数据结构和算法编程题15%设计模式与基础概念单选/问答10%这里有个容易忽略的点很多Android题不会直接考你某个组件而是把网络、异步、存储这些能力揉在一起问。比如“在Service里做网络请求时要注意什么”“如果用Handler做了耗时操作会有什么风险”表面考Service实际考的是异步模型和线程安全。这意味着单纯背组件生命周期是不够的得能把这些知识横向串起来。2. 选择题陷阱上Java基础与并发2.1 集合框架HashMap、ArrayList这些题怎么选才对Java集合这块在选择题里占比很稳定几乎每年必考。网易这份卷子里的相关题目核心落在HashMap、ArrayList、LinkedList这几个高频类上。比如HashMap的底层结构、扩容时机、put流程、以及JDK版本带来的差异。笔试里特别爱考的一个点是HashMap允许key和value为null而Hashtable不允许ConcurrentHashMap也不允许key和value为null。你记住“并发场景下为了规避歧义所以禁止null”就能把这个点串起来。再比如ArrayList和LinkedList的选择题问“随机访问和插入删除的时间复杂度”已经是常规操作了。ArrayList底层是数组随机访问O(1)扩容默认扩到原来的1.5倍LinkedList底层是双向链表插入删除理论上是O(1)但前提是你已经拿到了对应的节点否则查找这个节点的过程本身是O(n)。很多选项就故意把“理论O(1)”写成“任意场景下插入都是O(1)”这种题做题速度快的同学特别容易跳过去。2.2 线程并发volatile、synchronized、线程池的常见考法并发题目的出题套路其实很固定。第一条线是volatile考可见性和禁止指令重排但很多人会误选“volatile保证原子性”。笔试里经典的坑就是一段多线程i的代码问最后结果是多少。i不是原子操作即使加了volatile在多线程下依然会出现覆盖写这就是判断这道题的核心。第二条线是synchronized常考的“可重入”特性也就是同一个线程可以多次获取同一把锁。还有JDK 6之后的锁升级机制无锁、偏向锁、轻量级锁、重量级锁。选择题不一定考这么深但基本会考到“synchronized在方法上和代码块上的区别”“对象锁和类锁的区别”。第三条线是线程池几乎是必考。要能把corePoolSize、maximumPoolSize、workQueue、拒绝策略这四件事的关系讲清提交任务时先判断核心线程是否占满没满就创建核心线程执行满了就进队列队列满了再判断最大线程数最大也满了就走拒绝策略。这个顺序是题目最常见的考察路径我当年就见过一道题把五个参数各写一个描述让人挑出哪一个顺序错了实际就是考这个流程。2.3 JVM内存与GC选择题里最容易靠“背”拿分的模块JVM相关题目的特点是背了就能快速排除错误项不背就只能靠猜。选择题常考内存分区堆、虚拟机栈、本地方法栈、方法区、程序计数器各自存什么。记住几个结论对象实例分配在堆中局部变量和操作数栈在虚拟机栈中静态变量、常量池在方法区JDK 8后元空间实现程序计数器是唯一不会OOM的区域。题目里如果出现“JVM堆是线程私有的”这种表述直接可以排除。垃圾回收那边重点看可达性分析算法和引用类型。强引用、软引用、弱引用、虚引用各自的特点强引用对象不会被GC回收软引用在内存不足时才回收弱引用在下一次GC就会被回收虚引用的唯一作用就是跟踪对象被回收的通知。给一段代码问“哪个对象会被提前回收”的选择题往往就围绕这四个引用展开。刷题的时候把这个表格理清楚这个模块基本就是送分题。3. 选择题陷阱下Android组件与消息机制3.1 Activity生命周期与启动模式面试中的“高频钉子户”Activity相关选择题几乎场场必出现一般会从两个维度切入生命周期执行顺序以及启动模式对任务栈的影响。生命周期顺序很容易考“A启动B那么A和B各自会回调哪些方法”。正确答案是A.onPause - B.onCreate - B.onStart - B.onResume - A.onStop。很多人会漏掉A的onStop在B的onResume之后因为在onResume时A还不可见直到B完成绘制后A才会真正停下来。这种细节题就是在筛选对“可见”和“获得焦点”理解到不到位的人。启动模式这里四种模式的典型区别可以列个表启动模式行为特征典型场景standard每次启动都创建新实例并入栈普通页面singleTop栈顶已存在则复用回调onNewIntent通知栏跳转详情页singleTask栈内已存在则复用清掉其上所有Activity主页singleInstance独占一个任务栈来电界面选择题里最常见的问法是“如果某个Activity设置为singleTask从另外的Activity启动它生命周期会怎样”。关键结论是如果任务栈里已经存在该实例那么不会再次执行onCreate而是回调onNewIntent同时把栈内位于它上方的Activity全部弹出。有一道经典题问“按返回键会退到哪个页面”就是考察这个栈的清理动作。3.2 Service与BroadcastReceiver只背生命周期是不够的Service这道选择题的经典陷阱在两处。第一处是startService和bindService混用时的生命周期。startService多次调用onStartCommand会执行多次但onCreate只执行一次bindService调用多次onBind只执行一次但onUnbind调用时机由是否还有客户端绑定决定。两个一起用的时候必须在stopService和unbindService都执行之后Service才会真正销毁。第二处是onStartCommand的返回值。START_STICKY、START_NOT_STICKY、START_REDELIVER_INTENT三个值几乎每年都有题目出现。START_STICKY是默认值之一线程被系统杀死后会被重建但重建时传入的intent是nullSTART_REDELIVER_INTENT会重新传递最后一次intentSTART_NOT_STICKY则不会重建。选择题如果问“哪个返回值适合下载任务在异常后自动恢复”选START_REDELIVER_INTENT就对了。另外Android 8.0后对后台服务的启动限制也不时成为题干背景记住“后台不能随意startService但前台服务和系统组件调用不受影响”这个结论就行。3.3 Handler消息机制从原理到内存泄漏的完整考法Handler相关题目在选择题里也可以区分“背过原理”和“真正懂”。常考的有三个角度主线程为什么可以直接new Handler、子线程用Handler为什么要先Looper.prepare()、以及sendMessage和post的区别。本质上前两个是一个问题主线程在ActivityThread里已经主动调用了Looper.prepareMainLooper()和Looper.loop()子线程如果没有执行Looper.prepare()就new Handler抛出的异常就是“Cant create handler inside thread that has not called Looper.prepare()”。关于sendMessage和post很多人以为是两套机制其实post的Runnable最终会被封装成Message走的是同一条路径。这个结论选择题里经常反向考问你“post一定比sendMessage先执行吗”答案是不一定因为post只代表消息入队时拥有同一个what真正执行顺序由MessageQueue里的时间戳决定。Handler题目还有一个高频考点是内存泄漏。原因是非静态内部类Handler默认持有外部Activity的引用如果消息还在队列中Activity就无法被回收。标准答法是使用静态内部类WeakReference并在onDestroy里调用removeCallbacksAndMessages。选择题常问“下面哪个方案可以避免Handler内存泄漏”看到“静态类WeakReference”这个组合基本就是正确答案。3.4 数据存储与ContentProvider容易被忽略的隐蔽分数据存储部分的题目不会太多但出现时往往会带一些“题面迷惑”。比如SharedPreferences轻量级键值对适合存少量配置不适合存大量结构化数据。它还有一个不常用但在选择题里很能筛人的点apply是异步落盘commit是同步阻塞新的应用里极力推荐用apply。多进程场景下SP默认不支持并发读写如果要支持多进程需要设置MODE_MULTI_PROCESS但这个标志位在较新版本已经不建议依赖了。选择题里如果问“哪个操作适合放在主线程”那一定不是commit。ContentProvider相对考得少一些但偶尔会结合Application初始化顺序出题。不少人不知道ContentProvider的onCreate执行时机其实早于Application的attachBaseContext和onCreate所以一些第三方SDK初始化会依赖ContentProvider来实现自动初始化。笔试里如果见到“要在应用启动时尽早初始化SDK除了在Application里做还可以怎么做”答案就很明显了。4. 问答题的得分逻辑从原理到分析套路4.1 “子线程为什么不能更新UI”的标准答案长什么样这道题属于问答题里最朴实但最考验表达能力的类型。很多同学能说出“因为会崩溃”但说不出为什么会崩溃。真正的答题链条应该是这样 Android的UI框架不是线程安全的如果允许多个线程同时操作UI会造成视图状态不可预期所以系统规定只有创建视图树的线程也就是主线程才有资格更新该视图具体执行时ViewRootImpl内部通过checkThread方法检查当前线程如果发现不是主线程就直接抛出CalledFromWrongThreadException。题目如果继续追问“那为什么AsyncTask、runOnUiThread、View.post可以在子线程更新UI”答案也不是什么魔法它们本质上都是把动作发送到主线程的消息队列由主线程的Looper取出后再执行。所以要回答这道题除了说明崩溃原因还要带一句“所谓更新UI最终都发生在主线程”这个点能体现出你真的理解了Handler机制。面试官听到这里一般就知道你基础没问题了。4.2 ANR产生原因与排查链路答成“做题模板”更容易得分ANR相关问答题几乎不会直接问你定义更喜欢让你给场景“如果线上反馈App出现卡死和ANR你怎么定位”。这题建议按“类型指认 - 原因分析 - 工具定位 - 修复手段”四步来答。ANR类型一般有三个输入事件5秒内无响应、BroadcastReceiver前台10秒/后台60秒未完成、Service前台20秒/后台200秒未完成。各项官方阈值可以整理成一个表ANR类型触发条件输入事件主线程5秒内未消费完事件BroadcastReceiver前台10秒后台60秒未执行完Service前台20秒后台200秒未执行完造成ANR的根本原因通常是主线程被耗时任务阻塞比如做了网络请求、大文件IO、数据库批量操作、死锁等待等。定位手段在笔试里说三条就够了导出现场日志/data/anr/traces.txt看主线程堆栈、用systrace抓Trace、结合Memory Profiler看主线程当时的CPU和内存状态。修复方向就是让耗时操作离开主线程比如用Executor、协程或WorkManager同时注意锁竞争、减少主线程上的复杂计算。这套模板几乎可以套所有ANR场景。4.3 内存泄漏的分析套路从“场景列举”到“治理闭环”内存泄漏类问答题的得分点在于你是否能从“可能出问题的点”升级到“如何发现并验证”。回答时可以先讲常见泄漏场景静态变量持有Activity、非静态Handler持有Activity、匿名内部类持有外部类引用、BroadcastReceiver未反注册、各种Callback被长期保存、单例对象持有Context。列举完场景之后上升到检测工具LeakCanary、Android Profiler的Memory分析、以及MAT的Dominator Tree。有一个加分的实战细节LeakCanary的底层原理是通过ReferenceQueue配合弱引用在Activity销毁后观察弱引用是否进入引用队列如果没有说明对象仍被强引用持有。这个点很多人在简历里写了“使用LeakCanary排查内存泄漏”但能讲出原理的并不多。笔试问答里如果能把原因、工具、原理、修复方式串成一个闭环基本就是这类题的高分答案。4.4 自定义View的测量与绘制wrap_content为什么总踩坑自定义View如果出现在问答题里最常见的切入点就是onMeasure和MeasureSpec。MeasureSpec有三种模式EXACTLY、AT_MOST、UNSPECIFIED。match_parent或具体dp时是EXACTLYwrap_content时是AT_MOSTListView/ScrollView里的item在某些场景下是UNSPECIFIED。一个几乎必考的知识点是自定义View如果不处理wrap_content默认效果等同于match_parent。原因是getSuggestedMinimumWidth和MeasureSpec的处理如果不自定义View会直接采用父容器给的建议尺寸。完整回答逻辑是先讲measure、layout、draw三大流程再重点说明onMeasure中如何处理AT_MOST模式。处理办法是给wrap_content设定一个默认的最小尺寸比如在onMeasure里判断MeasureSpec.getMode为AT_MOST时取min(建议值、最小宽度)。如果题目还要求画出自定义ViewGroup的流程补充一句“需要重写onLayout来安排子View的位置并重写generateLayoutParams以支持LayoutParams属性”就足够完整了。5. 手写代码题的命题方向与高分模板5.1 链表操作反转链表与快慢指针网易历来在编程题里偏重基础算法链表几乎是最稳的“常驻嘉宾”。反转链表就是个典型的送分题但真到笔试环境不少人还是会在指针交换上卡住。迭代解法核心就是三指针public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode next curr.next; curr.next prev; prev curr; curr next; } return prev; }思路不难但你要在代码里体现出“边界意识”。首先是空链表处理head为null时直接返回null其次要记得在修改curr.next之前把next节点暂存下来不然就断链了。这道题如果笔试要求写递归版本我能给出的建议是紧张状态下优先写迭代思路更线性不容易出错。递归版本虽然代码短但对递归栈的理解要求更高写错了反而丢分。快慢指针类的链表题也很常见比如判断链表是否有环。核心逻辑是快指针每次走两步慢指针每次走一步如果链表有环两者最终会相遇。笔试里如果时间紧张这类题可以先把思路写清楚再写代码不要闷头写完全程才发现方向错了。5.2 字符串与数组两数之和是最常出现的“热身题”编程题如果难到一定程度反而会先放一道简单的“热身题”稳定心态。两数之和就是这种定位。题目要求返回数组中和为目标值的两个下标经典的O(n)写法public int[] twoSum(int[] nums, int target) { MapInteger, Integer map new HashMap(); for (int i 0; i nums.length; i) { int complement target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } return new int[0]; }这道题的考点有两个一是能否想到用哈希表做空间换时间二是边界条件的处理比如数组为空、无解时返回什么。笔试环境里即使你只是写出了双循环暴力解也建议在代码旁边补一句“时间复杂度O(n^2)可以优化为O(n)”这个补充说明有时候比你多写一道题还重要因为它展示了你具备复杂度意识。5.3 二叉树最大深度与层次遍历的两种风格二叉树相关的代码题在Android笔试里不会出太难一般到“求最大深度”和“层次遍历”就顶天了。求最大深度的递归写法非常简洁public int maxDepth(TreeNode root) { if (root null) return 0; return 1 Math.max(maxDepth(root.left), maxDepth(root.right)); }如果题目考层次遍历那么考察重点就是“能否用队列实现BFS”。注意入队顺序是左孩子先入、右孩子后入这样出队顺序才是从左到右。如果要求按层输出还需要在while循环里先取出当前层的size用for循环把这一层的节点都取出来。这种题你不仅要写对还要能讲清“为什么用队列”因为队列的FIFO特性天然匹配层次遍历的顺序。5.4 场景设计题LRU缓存与Android内存缓存思路有些年份的编程题会变体成场景设计比如“实现一个LRU缓存”。这道题在Android方向出现率不低因为它直接关联到LruCache的实现思路而LruCache又是图片加载框架里常见的缓存策略。笔试里最稳妥的写法是利用LinkedHashMap重写removeEldestEntryclass LRUCache extends LinkedHashMapInteger, Integer { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity capacity; } Override protected boolean removeEldestEntry(Map.EntryInteger, Integer eldest) { return size() capacity; } }关键点在于构造方法里的accessOrder参数必须传true这样链表顺序才会按照访问顺序来排列removeEldestEntry返回true时最久未访问的节点就会被移除。答题时如果能补充一句“LruCache内部就是使用LinkHashMap实现LRU策略的”这道题基本就是满分了。手写代码时记得处理key不存在的情况返回-1而不是返回null这两个细节很容易被测试用例抓到。6. 从这份试卷反推备考方法我踩过的坑和调整思路6.1 “只刷LeetCode不背基础”是校招笔试最大的误区我当年备考时最“自信”的一段时期是刷了两百道LeetCode后觉得自己已经稳了。结果做了一份类似的校招卷之后发现选择题里Java集合、Handler这些基础题居然会让我犹豫问答题更是半天组织不出逻辑。这份试卷给我最直接的教训就是算法题决定你的上限基础题决定你的下限。LeetCode刷得再多如果选择题掉进“singleTask会不会走onCreate”这种坑分数照样上不去。后来我备考调整成“算法刷题和基础背诵并行推进”每天固定花一到一个半小时过基础专题效果明显比单纯刷题好得多。6.2 用“讲解式学习”检验自己是不是真懂了备考到后期我发现一个特别好用的检验方法把当天复习的知识点用自己的话讲给身边不懂Android的人听。比如“为什么子线程不能更新UI”如果你能脱离文档、用一句话讲清楚“因为UI框架不是线程安全的ViewRootImpl会检查当前线程非主线程直接抛异常”说明你是真的理解了。如果你讲着讲着开始卡壳、需要翻书就说明这个点还停留在“眼睛会了”阶段。这个方法帮我筛出了很多平时觉得自己会、但一被追问就露馅的知识点建议备考的同学也试一试。6.3 笔试前强制做一次全真模拟在线笔试和面试不一样它有严格的计时、不能切出去查资料、还要在编辑器里“裸写”代码。所以我强烈建议你在正式笔试前找一套往年的试卷按考试的总时长完整模拟一遍。模拟时要注意两点一是所有代码都不可以用IDE的自动补全完全手敲很多人在自动补全写多了以后手写能力退化到自己都惊讶二是要习惯“不会的题先跳过”单选题不要把时间耗死在一道纠结题上等后面大题做完再回头来选。这类模拟能帮你建立属于自己的答题节奏真正进场的时候不会慌。6.4 笔试结束马上错题归档别等到面试前才整理我见过很多同学笔试结束之后就彻底把这张卷子抛到脑后结果面试被问到同一个知识点时又不会了。正确的做法是笔试结束后趁热打铁立刻把错题和不确定的题目整理到自己的错题本上按“题目类型、正确答案、错误原因、对应原理”四栏归档。比如“HashMap的扩容因子为什么是0.75”这种你模棱两可的题随手记下来面试前翻一遍比临时刷十篇面经都有用。还有一个小技巧笔试中遇到完全不会的题不要空着。在线笔试的选择题可以选一个最可能的答案问答题哪怕只知道关键词也要按照“定义 原因 解决方向”的结构写几行代码题写出核心思路和关键代码片段也会有步骤分。校招笔试的分数线通常比想象中低能把会做的都做对、不会的尽量拿步骤分胜算就大了很多。这套2018年的网易笔试卷到今天看虽然具体题目会更新但出题逻辑变化并不大——它始终在强调Android开发者的地基能力。我在帮学弟学妹复盘笔试时最常重复的一句话就是别再纠结自己会不会最新的动画框架先把Activity生命周期、Handler消息机制、HashMap扩容这些“老生常谈”讲到能闭眼画出来的程度。做到这一点不管试卷是哪一年的你都不会被筛掉。