网易2018校招Android开发工程师笔试卷,我当年是真刀真枪做过一遍的。那会儿秋招刚开始,手里拿着一堆打印出来的真题,晚上在图书馆一遍遍推演,白天就跑去机房敲代码练手感。现在回头看,这套卷子虽然叫“2018校招”,但它覆盖的知识面、考察的深度,放到今天依然能打,很多大厂Android岗的笔试题都还在用同样的套路出题。这篇文章我就把当年刷这套题的经验、踩过的坑、总结出来的解题思路全部摊开讲,不管是正在准备校招的应届生,还是想跳槽巩固基础的Android开发,都能从里面拿到点实在的东西。
先说结论:网易这套笔试卷,核心就考三块——Java基础与并发、Android四大组件与消息机制、算法与数据结构编码题。附加题偶尔会有网络、操作系统、数据库的内容,但占比不大。整张卷子时间特别紧,我记得好像是90分钟,题量却不少,单选多选加两道编程题,很多人根本做不完。所以这套卷子考验的不只是你会不会,更是你在压力下能不能快速判断、果断取舍。
1. 笔试整体认知与题型分布
1.1 网易笔试题型结构与时间分配
2018年这套卷子的题型大致分四类:单选题、多选题、编程题,以及个别年份会出现的简答题。单选题大概20到30道,覆盖面极广,从Java语法细节到Android组件的使用场景都有;多选题大概10道左右,难度明显上升,选项里经常出现“看似对但其实描述不严谨”的干扰项;编程题一般是两道,一道偏算法,一道偏Android场景模拟(比如设计一个缓存、实现一个生产者消费者模型)。
我当年第一次做这套题的时候,直接栽在时间分配上。前面选择题花太多时间纠结,结果编程题只剩20分钟,第二道题只写了个开头就交了。后来我调整了策略:拿到卷子先花两分钟扫一遍全卷,把编程题大概看一眼,心里有数,然后选择题单题不超过60秒,拿不准的先标记跳过,最后回头再看。这样至少能保证两道编程题都有时间动手。
1.2 考点权重分布与复习优先级
从真题统计来看,考点权重大概是这个分布:Java基础(集合、并发、JVM)约占30%,Android基础(四大组件、Handler、启动模式、布局)约占30%,算法与数据结构约占20%,网络与操作系统约占15%,其他(设计模式、数据库、Git等)约占5%。
这意味着复习的优先级其实非常明确:Java基础和Android基础是绝对的基本盘,这两块只要吃透了,选择题的保底分就到手了。算法题决定你能不能进下一轮面试,网易的算法题不算特别变态,基本在LeetCode中等难度以内,但要求你在笔试环境里手写出能跑通的代码,这就很考验基本功了。
注意:多选题是重灾区。Android的多选题经常考察“以下哪些说法正确”,四个选项里往往有三个看起来都对。我建议多选题当成判断题来做,每个选项逐字抠,尤其是“一定”“必须”“所有”这类绝对化表述,八成为错。
2. Java与Android核心基础高频考点
2.1 Java集合与HashMap源码细节
网易的笔试卷子特别喜欢考HashMap,而且是那种“源码级”的考法。我记得有一道多选,问的是HashMap在JDK 1.7和1.8之间有哪些变化,选项里包含了“头插法改尾插法”“引入红黑树”“resize时是否重新计算hash”“初始容量和负载因子”等。如果你只停留在会用HashMap.put/get的阶段,这种题基本靠蒙。
这里我建议认真理解几个核心点。第一,HashMap的put流程:计算key的hash值,通过扰动函数减少碰撞,定位到桶的位置;如果桶为空直接放,如果桶不为空遍历链表或红黑树找相同key,有就覆盖,没有就插入。第二,扩容机制:默认初始容量16,负载因子0.75,当元素个数超过容量乘以负载因子时触发扩容,扩容为原来的两倍。JDK 1.7是全部重新rehash,1.8做了优化,利用元素hash值高位是否变化,将链表拆成高低位两条链,避免大量重算。第三,为什么线程不安全:JDK 1.7在多线程同时put并触发扩容时,头插法可能导致环形链表,get时出现死循环;1.8解决了这个问题,但多线程下put仍可能覆盖丢失数据。
与之配套的还有ConcurrentHashMap。网易对并发容器的考察次数也不少,核心考点包括:JDK 1.7的Segment分段锁设计、1.8改用CAS加synchronized锁头节点、size()是如何在不锁整个表的情况下估算的(1.8用CounterCell和sumCount),以及为什么读操作不需要加锁(Node的val和next用volatile修饰)。这些内容笔试时会以选择题形式出现,但面试环节也经常追问,提前整理好没有坏处。
2.2 JVM内存区域与GC机制
Android开发虽然和标准JVM有区别(Android用的是ART/Dalvik,早期还有独立的堆栈模型),但校招笔试对JVM的考察仍然很主流,因为这是Java开发者的通用基础。网易2018年的卷子里就有一道关于内存区域的单选,涉及堆、虚拟机栈、本地方法栈、方法区和程序计数器各自的职责。
复习JVM时,不要死记硬背,我建议用一个“代码运行时发生了什么”的方式来串联。比如你new了一个对象:类信息加载到方法区,对象的实例数据分配在堆中,方法里的局部变量存到栈帧里,如果你在方法里new了一个大对象,可能触发Minor GC或Major GC。GC算法方面,重点理解可达性分析(GC Roots),以及强引用、软引用、弱引用、虚引用在Android中的典型应用场景——比如图片缓存用LruCache内部是强引用加LinkedHashMap,而系统针对内存紧张时的回收优先级,就涉及引用的不同级别。
网易比较爱考的一个点:“强引用置为null之后,GC一定会立刻回收它吗?”答案是“不保证,要看GC何时触发”。很多人一看到置为null就觉得万事大吉,实际上置null只是让对象变成“可回收”状态,什么时候回收由GC决定。Android里用WeakReference加ReferenceQueue做内存泄漏检测,也是利用了GC后弱引用会被放入队列的特性。
2.3 线程、并发与线程池
并发编程是Java基础里的重头戏,笔试出现频率极高。网易的卷子里,至少会有两三道题涉及线程同步、锁、线程池参数。其中我最推荐彻底搞懂的是线程池。
线程池的考察点是标准化的:核心线程数、最大线程数、空闲存活时间、工作队列、拒绝策略,这五要素必须背得滚瓜烂熟。执行流程也要能讲清楚:当提交一个任务时,如果当前线程数小于核心线程数,创建新线程执行;如果大于等于核心线程数且队列未满,放入队列;如果队列满了且当前线程数小于最大线程数,创建新线程;如果线程数已经达到最大,执行拒绝策略。网易可能会给你一组具体参数,然后问“当第N个任务到来时会发生什么”,这种题就是考执行流程,别记混。
Synchronized和ReentrantLock的区别也是高频考点。先说结论:两者都是可重入锁,但synchronized是JVM层面的关键字,自动加锁释放锁,非公平;ReentrantLock是API层面的实现,支持公平锁、可中断、可超时、可绑定多个Condition条件。这么多年开发下来,我的感受是:JDK 1.6之后synchronized经过锁升级(偏向锁→轻量级锁→重量级锁)优化,大多数场景性能已经不输ReentrantLock,所以笔试如果你是答“两者性能谁更好”,要说明“分场景,现代JDK下差距不大”。
ThreadLocal在Android里尤其重要,因为Looper就是通过ThreadLocal来保证每个线程只有一个Looper实例的。笔试考ThreadLocal通常是这么考的:ThreadLocal的底层结构是什么?为什么ThreadLocalMap的key是弱引用?会有什么问题?答案主线是:每个Thread内部有一个ThreadLocalMap,以ThreadLocal为key、以你set的对象为value;key用弱引用是为了避免ThreadLocal对象无法被GC,但value是强引用,所以如果ThreadLocal被回收而value还在,就会出现key为null但value不为null的脏数据。Android的Handler机制里,主线程Looper生命周期和进程一样长,所以一般没有泄漏问题;但如果你在子线程用ThreadLocal并且忘记remove,就要小心内存泄漏了。
2.4 Android四大组件与启动模式
Android组件的考察在整套卷子里占了相当大的比重。Activity、Service、BroadcastReceiver、ContentProvider,这四大组件在网易的选择题里几乎都会出现。最容易丢分的是各种“场景判断”题,比如“Activity A启动B,B设置singleTask,请问A和B的生命周期回调顺序”。
对启动模式,我总结了一套自己的记忆方法。Standard是每次都新建实例,少数场景会用,比如普通页面跳转;singleTop是栈顶复用,适合接收推送通知后跳转的页面,避免连续点击通知导致页面堆叠;singleTask是栈内复用,适合App主页,保证整个App只有一个主页实例,且会清理掉它上面的所有Activity;singleInstance是独立任务栈,适合需要全局唯一的页面,比如来电界面、系统级别的弹窗。要特别注意singleTask启动已有实例时的回调顺序:会先调用现有实例的onNewIntent,然后onRestart、onStart、onResume,而原来栈内它上面的Activity会被销毁。
除了启动模式,Activity生命周期在异常情况下的表现也是易考点。比如屏幕旋转时Activity会被销毁重建,onSaveInstanceState在onPause之后onStop之前调用,恢复数据在onRestoreInstanceState或onCreate的savedInstanceState里取。网易考过一道题,问“在onPause里执行耗时操作是否合理”,答案肯定是不合理,因为A的onPause执行完才会让B显示,如果耗时过长会影响页面切换体验,正确的做法是onStop或更晚阶段处理。
Service方面,笔试题比较喜欢考启动方式和绑定方式的区别。startService启动的Service与启动者无绑定关系,即使启动者销毁,Service还能继续在后台运行;bindService则绑定生命周期,绑定者销毁时如果没有任何绑定关系,Service会自动销毁。同时要了解onStartCommand的返回值,START_STICKY、START_NOT_STICKY、START_REDELIVER_INTENT的区别,网易出现过把STICKY和REDELIVER_INTENT混在一起考的题目。
BroadcastReceiver和ContentProvider相对考得少,但ContentProvider有一个必知必会点:ContentProvider的启动与App进程的关系。如果App进程没有启动,通过ContentProvider访问数据时,系统会先创建目标App的进程,然后实例化ContentProvider,再执行query等操作。这个知识点在考“ContentProvider与Application的onCreate谁先执行”时就会用上。另外,Android 8.0之后对隐式广播做了限制,很多静态注册的广播不再能通过隐式Intent收到,这套题虽然是2018年的,但正好赶上这个变化,我用的是Android官方适配说明里的内容,读者要留意版本差异。
2.5 Handler消息机制与Looper
Handler这一块,可以说是网易Android笔试卷中的“必考题”,而且是一考就考好几道的那种。我从真题里总结,核心考点集中在五个方向:Handler/Looper/MessageQueue三者关系、主线程为什么不会因Looper死循环而ANR、MessageQueue的阻塞与唤醒机制、同步屏障与异步消息、Handler内存泄漏与正确写法。
先说三者关系。每个线程只要调用了Looper.prepare,就会创建一个唯一的Looper,Looper构造时会创建MessageQueue。Handler在创建时从当前线程取出Looper,所以Handler默认绑定的是“创建它的线程”。Handler通过sendMessage把Message放进MessageQueue,Looper通过loop方法不断从队列取消息,最终回调Handler的dispatchMessage和handleMessage。记住一个口诀:Handler负责发送和处理,Looper负责循环取消息,MessageQueue负责存储。
主线程的Looper死循环为什么不会卡死主线程,这个问题几乎每次面试和笔试都会遇到。答案要从两个层面讲。第一,Looper.loop是一个for无限循环,当队列没有消息时,MessageQueue的next方法会调用nativePollOnce阻塞当前线程,这时候主线程是休眠状态,不消耗CPU资源,所以不会造成卡顿。第二,Android所有UI绘制、事件分发、四大组件切换,本质上都是通过向主线程MessageQueue发消息来驱动的,也就是说,主线程的工作方式就是“消息循环”,如果没有这个循环,系统反而无法工作。可以类比成一家餐厅只有一个服务员,他不能离开餐厅,没客人时就在柜台趴着休息,一旦有客人进来,他能马上响应。
MessageQueue的阻塞与唤醒底层用的是Linux的epoll机制。nativePollOnce传入超时时间,如果超时返回或者有新的消息写入队列,就会通过nativeWake唤醒。这个知识点顺带解释了为什么Handler.postDelayed是“精准的延时”而不是“开个线程sleep”——延时消息并不是真的在时间到了才放进队列,而是放进队列后计算好时间点,到时候从队头取出。如果队头是一个还没到时间的延时消息,next就会计算剩余时间并进入epoll等待,有新的消息进来时会重新计算唤醒时间。
关于Handler内存泄漏,笔试和面试都爱问。原因很明确:非静态内部类Handler默认持有外部Activity的强引用,如果消息在队列里延迟执行,而Activity已经finish,Activity就无法被GC回收。解决方案是:使用静态内部类继承Handler,然后持有Activity的弱引用,在onDestroy时移除所有消息和回调。注意移除的时候要分别调用removeCallbacksAndMessages(null)和removeMessages,两者针对的对象不同,前者移除所有Runnable和Message,后者只移除指定what的消息。
2.6 Context相关与Android资源管理
Context是Android开发里最基础也最容易讲不清楚的概念。网易的试卷里出现过“Context的继承结构是什么”这类题。Activity、Service、Application都间接继承自Context,但它们的Context类型不同:Activity和Service继承ContextWrapper,而ContextWrapper内部又持有一个ContextImpl实例,真正干活的是ContextImpl。
笔试喜欢考的辅助场景是“getApplicationContext和Activity.this有什么区别,什么时候能用哪个”。比如弹Toast可以用ApplicationContext,但启动一个Activity就必须用Activity的Context(或者在新任务栈里加FLAG_ACTIVITY_NEW_TASK)。还有一个容易忽略的点:LayoutInflater.from(context)和context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)最终拿到的是同一个实例,但它在不同Context下使用时的主题资源会有所不同,这跟ContextImpl里mThemeResource有关系。
3. 算法编程题思路与手写代码技巧
3.1 网易笔试算法题高频题型与解题模板
网易Android岗的算法题,从历年情况来看,集中在数组处理、字符串处理、链表操作和简单动态规划上。2018年这套卷子里有两道题,我记得一道是“判断一个字符串能否通过调整字符顺序变成回文串”,另一道是“实现一个简单的LRU缓存,支持get和put操作”。
第一道题核心思路是用哈希表统计每个字符出现次数,然后判断奇数次的字符个数是否大于1。大于1说明无法构成回文串,等于0或1则可以。这类题目的变体还包括“构造回文串并输出字典序最小的结果”,那就需要进一步处理排序和双端队列了。第二道题LRU缓存就更有意思了,它既能考算法和数据结构,又能体现实际开发中的资源淘汰策略,Android里的LruCache也是据此设计的。笔试时用LinkedHashMap来实现是最快的,重写removeEldestEntry方法即可,但如果面试官追问底层原理,你必须知道哈希表加双向链表的组合方案。
针对高频题型,我建议准备几个代码模板:
- 双指针模板:处理数组有序、链表环判断、回文串题目。
- 滑动窗口模板:处理子串、子数组问题。
- 单调栈模板:处理下一个更大元素类型。
- 递归转迭代模板:二叉树遍历、链表反转。
这些模板不需要背很多,会套就行。网易的笔试算法部分,核心目的是筛选出“基本功扎实、能写出可运行代码”的候选人,不会刻意出偏题怪题。
3.2 笔试环境下的输入输出与时间管理
很多人平时刷题用LeetCode,习惯了直接写函数体,但网易的笔试系统是要求自己处理输入输出的。我记得当时用的是牛客网或者其他在线笔试平台,输入是标准输入,输出是标准输出,第一行可能是测试用例组数,然后每组数据单独一行。这个差距导致很多LeetCode刷得很6的同学在笔试时直接懵了,写出来的代码没处理输入输出,一分拿不到。
我强烈建议在校招季之前,专门花一周时间练习“从标准输入读取、解析、输出”的能力。Java写笔试题目时,推荐用BufferedReader加StringTokenizer或者split来处理,不要用Scanner,因为Scanner在读取大量数据时性能明显不足。如果输入是整数,直接Integer.parseInt处理;如果输入包含字符串,注意可能带有空格或者空行,要做好trim处理。输出时用StringBuilder把结果拼接好,最终一次性打印,避免多次调用System.out.println,否则数据量大的时候会拖慢程序,极端情况下可能被判超时。
编程题的阅卷逻辑通常是“多组数据都通过才算对”,所以边界条件一定要想清楚。比如输入数组为空的场景、只有一个元素的场景、字符串长度为负数的不可能情况、两个整数相加可能溢出的情况。写出这些边界条件的判断,既能提高通过率,也在向阅卷系统展示你思路严密。
3.3 手写代码的考察点与评分标准
笔试系统一般不会人工看你的代码,只看输出结果。但部分校招批次在编程题之后还会有问答,或者结合你的代码进行追问。所以代码本身的风格仍然有影响。
手写代码时,变量命名要语义化,比如用left、right、index,不要用a、b、c这种无意义的名字;逻辑要尽量精简,避免层层嵌套的if;关键的算法步骤可以加注释,但不要为了注释而注释。网易这种体量的公司,笔试筛选的是硬实力,代码规范上不会卡得太死,但在几乎相同的解法之间,干净整洁的代码更容易在后续面试环节被面试官问到时给你加分。
提示:编程题如果题目没有明确要求,优先选择你最有把握的算法,而不是最优算法。笔试环境下时间有限,暴力解法如果能通过就跑暴力,然后留时间做第二道题。每一分都拿到才是最重要的。
4. 网络、操作系统与数据库基础
4.1 TCP/IP与HTTP网络的Android开发
Android开发对网络的要求并不低,因为App的核心功能基本都是靠网络完成的。网易笔试卷对网络的考察主要集中在TCP和HTTP。TCP的三次握手、四次挥手几乎是必考题,但笔试不会让你画流程图,而是用选择题考察“某一状态下发生了什么”。比如“TCP连接建立过程中,客户端发送SYN之后所处的状态是什么”,答案是SYN_SENT,服务器端收到SYN后变成SYN_RCVD,然后双方进入ESTABLISHED。
HTTP方面,网易2018年的题里涉及了HTTP和HTTPS的区别、HTTP的请求方法以及状态码。记住一个核心区别:HTTPS在HTTP和TCP之间加了一层TLS/SSL加密协议,握手过程需要交换证书、协商密钥,所以连接建立速度比HTTP慢,但数据是加密的。状态码要重点记:200表示成功、301永久重定向、302临时重定向、304未修改(可用于缓存协商)、400客户端错误、401未认证、403禁止访问、404资源不存在、500服务端内部错误、502网关错误、503服务暂不可用。
Android网络开发还有一个容易被笔试考到的点:OkHttp的拦截器机制和连接池复用。虽然这套题是2018年的,当时OkHttp已经是主流,但笔试很少直接考框架内部原理,更多还是考HTTP协议本身。所以备考时先把协议层搞懂,框架层面试时再深挖。
4.2 进程、线程与Android的进程模型
操作系统进程与线程的题目,在网易卷子里会以一种“Android特色”的方式出现。比如“Android中进程的优先级分为几个等级,哪个级别的进程最容易被系统杀死”。这里涉及Android的进程回收机制:前台进程、可见进程、服务进程、后台进程、空进程,回收优先级从低到高。空进程会最先被回收,所以很多App坚持做“进程保活”本质上是在和服务系统拼优先级。
Android的IPC(进程间通信)也是高频知识点。Android系统里最核心的IPC方式就是Binder,笔试喜欢考“一次Binder通信大概需要几次内存拷贝”以及“Binder相对于传统IPC的优势”。答案是:一次拷贝,传统IPC通常是两次。原因在于Binder通过内核映射区实现了数据共享,发送方数据先拷贝到内核空间,接收方再映射读取,不需要额外从内核拷贝到接收方用户空间。这部分如果觉得抽象,可以类比成两个人传文件:传统方式是先把文件拷到U盘(内核),再把U盘插到接收方机器上拷过去,两次拷贝;Binder方式是直接把文件放到共享网盘,两个人同时访问同一个文件,只需要一次写入过程。
4.3 SQLite与数据库迁移
数据库在笔试中占比不高,但出现在多选题中的概率还是有的。主要知识点包括:SQLite支持的数据类型(NULL、INTEGER、REAL、TEXT、BLOB)、主键与自增主键、索引的作用与代价、事务的ACID特性。
网易考过这样一道题:SQLite中哪些语句会触发隐式事务。答案是把多个写操作放在一个事务里能大幅提升性能,因为每条SQL语句默认都会自动开启和提交事务,而事务的磁盘I/O消耗很大。在实际开发中,批量插入几百条数据时,如果不手动开启事务,可能会因为频繁写盘导致主线程卡顿,所以Room等框架都会默认帮你在事务中执行批量操作。
关于数据库升级,笔试问“数据库版本从1升到2,onUpgrade方法的参数是什么意思”的概率很高。oldVersion和newVersion分别指的是旧数据库版本号和新数据库版本号,实际开发中每次表结构变化都要在onUpgrade里写对应的迁移逻辑,比如判断oldVersion小于2时执行ALTER TABLE等。
5. 性能优化与架构设计题目
5.1 内存泄漏常见场景与检测手段
前面在Handler部分已经讲了Handler导致的内存泄漏,但网易的卷子对内存泄漏的整体考察远不止这一种。还有一些典型场景经常出现在多选里:Activity被静态集合强引用、匿名内部类持有外部引用、资源(Cursor、IO流)未关闭、单例模式持有Activity的Context、ListView适配器里的ViewHolder没有正确复用(这题比较老,但在掌握原理上有价值)、Bitmap未回收等。
笔试如果问到“如何检测内存泄漏”,标准回答路线是:先用Android Studio的Memory Profiler观察内存抖动和堆内存趋势,再用LeakCanary做自动化检测,定位到具体泄漏引用链。LeakCanary的原理要理解:通过Application注册ActivityLifecycleCallbacks监控Activity的onDestroy,然后利用弱引用和ReferenceQueue判断对象是否被回收,如果迟迟没有回收,就手动触发一次GC并dump堆内存,最后通过HaHa库解析堆栈找出强引用链。
这里我要强调一个常被忽略的点:泄漏并不一定发生在Activity上,还可能是View、Bitmap、自定义View里的Drawable,甚至是Fragment。检测手段都一样,但修复方案不同。笔试填选项时,看到“仅靠LeakCanary就一定能解决所有泄漏”这种绝对化表述,直接排除。
5.2 布局性能与过度绘制优化
Android的UI渲染性能是校招笔试关注的重点之一。网易出现过“如何减少过度绘制”“哪些操作会导致掉帧”这类选择或判断。核心认知是:屏幕每个像素点绘制多次就是过度绘制,打开的开发者选项里的“显示布局边界”可以查看,颜色越红,说明绘制层数越多。
优化手段包括:减少不必要的背景绘制(尤其是父布局和子布局同时设置背景色)、用ConstraintLayout减少布局层级、使用include和merge标签复用布局、ViewStub延迟加载不需要立即显示的视图、RecyclerView复用机制正确使用(不要在onBindViewHolder中创建新对象)。
笔试里经常混淆的还有invalidate和requestLayout。invalidate只会触发重绘(onDraw),不会重新测量和布局;requestLayout会触发measure和layout,如果视图的尺寸或位置发生变化才应该调用。理解这个区别对性能优化很有帮助,因为频繁的requestLayout意味着大量measure,这在复杂布局里是很昂贵的操作。
5.3 图片加载与三级缓存原理
图片加载框架是Android笔试的常客,尤其是Glide。网易2018年的卷子虽然没有直接考“Glide的源码流程”,但考了“三级缓存策略”“内存缓存和磁盘缓存的区别”“为什么RecyclerView滑动时Glide能加载得更流畅”。
三级缓存指的是内存缓存、磁盘缓存、网络加载。读取顺序:先从内存读,读不到再从磁盘读,还读不到就发网络请求。写缓存顺序:网络图片先写入磁盘,再解码到内存缓存,供下次快速读取。Glide默认用LruCache做内存缓存,用DiskLruCache做磁盘缓存。
Glide的一个重要特色是绑定生命周期:通过RequestManager监听Activity或Fragment的生命周期,在页面不可见时暂停加载,可见时恢复加载。这可以大幅节省资源。笔试考到“Glide和Picasso的对比”时,常见考点是:Glide按Activity生命周期加载、支持GIF、磁盘缓存策略更灵活,而Picasso体积更小,但只缓存全尺寸图片。此外用Glide加载图片时,如果图片URL没变,默认情况下是不会重新加载的,需要用到signature或设置diskCacheStrategy。
5.4 MVC、MVP与MVVM,以及代码规范
架构模式的考察在笔试试卷里通常以选择题或简答题出现。我记得网易有一道题是“MVP模式相对于MVC的优势是什么”,四个选项里正确答案是“将UI逻辑和业务逻辑分离,View层更轻,更易单元测试”,干扰项包括“开发速度最快”“完全没有内存泄漏问题”等。
MVC在Android里的痛点:Activity同时承担View和Controller的职责,代码一多就变成“上帝类”,难以维护和测试。MVP把业务逻辑抽到Presenter,Activity/Fragment只负责View的展示和事件转发,View和Presenter通过接口通信,因此Presenter可以脱离Android环境做单元测试。MVVM则是在MVP基础上引入数据驱动,View和ViewModel之间通过LiveData或DataBinding双向绑定,进一步减少手动控制逻辑。
笔试不会要求你写一个完整的MVP框架,但可能会让你阅读一段代码,判断它属于哪种架构,或者指出这种架构存在什么问题。我的建议是把三种架构的核心关系图记清楚,并把“Presenter或ViewModel在Activity销毁后持有的View引用会造成泄漏”这个坑记牢。MVVM的解决方式是LiveData感知生命周期,但即使如此,在ViewModel里持有Activity的Context仍然是被禁止的。
6. 备考路线、刷题方法与笔试避坑
6.1 从零开始三个月的备考时间线
结合我对这套题和整个秋招的复习经验,如果现在还有三个月时间,我建议这样分阶段准备:
第一个月,主攻Java基础和Android基础。Java方面把集合源码、并发、JVM内存与GC过一遍,配合剑指Offer的经典题练习;Android方面按四大组件、Handler、启动模式、消息机制、布局与绘制的顺序吃透。这个月不用急着刷笔试真题,先把知识点系统化。
第二个月,开始刷真题和LeetCode。LeetCode以数组、链表、字符串、二叉树、动态规划为主,每天两到三道,复习一遍比开新题更重要。同时每周至少安排一次完整的笔试模拟,控制在90分钟,锻炼时间分配和心态。同时注意整理错题,把每次笔试的错题按知识点归类,找到自己最薄弱的模块重点补。
第三个月,回归基础和查漏补缺。网易这类公司笔试风格稳定,考来考去还是那些知识,但你不知道它会从哪个细节切入。这时候把所有笔记和错题重新过一遍,重点看那些“易混淆”“容易记错”的点。比如Activity的四个启动模式对应的栈变化、HashMap在并发环境下的表现、Handler消息队列的阻塞唤醒机制,这些高频细节值得反复背。
6.2 高效的刷题与复盘方法
我见过很多同学刷题只追求数量,一天刷十道,半个月后回头一看全忘光了。刷题的正确方式应该是“三遍法”。第一遍独立做,做不出来就看题解,看懂后合上题解重新写一遍;第二遍隔一天后重新做,检验自己是不是真的会了;第三遍一周后再做,速度和边界条件都要过关。这个方法看着慢,实际效率极高,能把一道题的解题思路内化成自己的。
笔试真题的复盘比刷题更重要。每套卷子做完,不要只对完答案就扔,要花至少两倍的时间去分析。选择题里每一个错误选项,都要搞清楚它错在哪里,是概念混淆,还是场景不匹配,还是绝对化表述。编程题即使跑对了,也看看官方题解里的更优解法,想一想自己的解法有没有隐藏的边界问题。我当时准备了一个笔记本,专门记录“为什么错”,到后期翻看时帮助特别大。
6.3 笔试实战中的避坑技巧
最后说几个实战中容易踩的坑,这些全是我亲身经历。
第一,提前检查笔试环境。校招笔试一般用牛客、赛码等在线平台,有些平台要求用特定浏览器,有些要开启摄像头监控。提前一天登录测试环境,确认摄像头、麦克风、网络都没有问题。临时换电脑是很致命的,因为在线编程的体验很难在短时间内适应。
第二,注意输入输出的坑。在线笔试要求从标准输入读数据,不要写死测试用例。有些题是一组数据,有些题是多组数据,一定要仔细读题。输出格式也要严格,比如题目要求每个结果占一行,就不要多打印空行,也不要输出多余的文字提示。
第三,选择题不要空着。网易的笔试选择题一般没有倒扣分机制,所以不管会做不会做都要选一个。多选题如果实在拿不准,宁可少选也不要多选,因为多选漏选通常都算错,少选可能还能拿一点分。
第四,拿到卷子先分配时间。前面已经强调过,再重复一次:先扫全卷,标记编程题,选择题快做,编程题要确保有时间写。编程题即使最后没做完,写出思路和关键代码也能拿到部分分,白卷就彻底没戏了。
第五,不要在代码里暴露出真实的个人习惯或公司信息。笔试代码的注释、变量名用正常命名就好,别写跟项目相关的敏感内容,一方面是不专业,另一方面也可能被认为是在使用外部资源,存在违规风险。
这些经验是我在2018年那场秋招里,实打实踩坑踩出来的。网易的这套卷子难度中等偏上,覆盖面特别完整,我自己做完之后最大的感受是:你永远不知道试卷会考哪个细节,所以唯一保险的办法就是把基础打牢,把原理吃透,而不是指望背一堆“标准答案”。现在再回头看,那段时间高强度刷题、复盘、写总结的经历,不仅仅帮我拿到了几个offer,也把我对Android整个体系的理解拔高了一个层次。这套题准备过之后,后面面对其他公司的笔试题,心里就踏实很多了。