前几天整理硬盘翻出一份2018年网易Android开发实习生的笔试回忆版忍不住又从头到尾过了一遍。网易当年的题不算难但筛人效果非常好——它不堆偏题专挑你平时写项目一定会碰、但又未必认真想过的底子来考。Activity生命周期、Handler消息机制、HashMap、线程池、内存泄漏、链表反转基本就是你Android路上的第一波“地基验收”。这篇复盘我按当年考场上的实际印象把选择题、简答题、编程题三条线全部还原了一遍每题补了答案也补了出题意图。无论你是正在准备大厂Android实习岗位的学生还是刚工作一两年想回炉的初级工程师用一晚上把这份题过完收获一定比刷十套模拟题更扎实。1. 出题思路与当年考场的考察维度1.1 网易笔试题型的整体风格2018年这场实习生笔试题量看起来不大但时间很紧。常规配置是选择题加简答题加两道编程题选择题大概二十道左右覆盖Java集合、并发、Android四大组件、自定义View和网络协议简答题每年会换着角度问Handler机制或者Activity启动流程编程题不会太偏竞赛多数是链表、树、字符串和智力题轮换。先说结论网易这套题不是选“谁背的书多”而是选“谁真的写过代码、真的琢磨过系统底层”。比如它问你HashMap在什么条件下转红黑树不是要你背出那行if而是看你有没有在项目里遇到过链表过长导致的性能抖动。再比如它问SharedPreferences能不能跨进程直接暴露你对Android数据持久化边界的理解。从题型结构看笔试更接近一个筛子用来过滤掉连基础知识都没形成体系的人。具体比例上选择题是基本盘简答题是分水岭编程题决定上限。当年很多人在刷题网站上练了三个月算法结果输在了Handler和Binder这类系统机制题上原因就是练题方向跑偏了。1.2 分数结构和备考优先级一张卷子想要过线选择题的正确率是基本盘这部分必须稳简答题是分水岭因为答案写得好不好面试官一眼就能看出来你有没有做过项目编程题则是决定上限能完整AC一道基本就进入下一轮。我当时认识的几个通过笔试的同学共性很清楚选择题丢分不超过三道简答题不空题、能把流程清晰画出来编程题至少完整AC一道、另一道暴力拿部分分。这个策略放到现在依然适用大厂笔试看重的是你在有限时间里的稳定输出不是某一题突然爆发。如果把题型结构整理成一张表当年的考察重点大概是这样题型题量大致核心考察点单选题18-20道Java集合与并发、Activity/Service、ContentProvider、网络协议简答题2道Handler消息机制、Activity启动流程或内存优化方案编程题2道链表/数组算法、线程安全的单例或常见数据结构操作备考优先级上我的建议是先把Java集合和并发题刷到条件反射再花整块时间啃透Handler和Binder最后每天保持一道算法题的手感。选择题靠刷题简答题靠理解编程题靠平时积累三者缺一个都容易翻车。2. 选择题精讲Java功底与Android四大组件高频点2.1 Java集合与并发的高频考察我记得当年有一道题是这样的关于HashMap下列说法正确的是A. HashMap线程不安全 B. Java 8中链表长度达到8就会转红黑树 C. 默认负载因子是0.75 D. 扩容时会将数组长度翻倍。选项里最容易被拿来当陷阱的是B因为它漏了一个前提——链表长度达到8且数组长度已经不小于64时才转红黑树。如果数组长度还没到64即使链表再长HashMap也是先扩容而不是直接转树。这道题背后考的是你对HashMap真实运行机制的理解。put一个key时先通过hash值扰动再跟数组长度减一做位与运算得到桶下标发生哈希冲突就挂链表读写频繁的场景里链表过长会导致get退化成O(n)所以Java 8引入了红黑树来兜底。但树化本身有开销所以源码里同时限制了数组最小长度。2.2 线程池参数与拒绝策略另一道经典的题是给你一堆线程池参数问核心线程满了之后新任务进哪。很多人会漏掉workQueue这一步。正确流程是核心线程数没满就先创建核心线程执行任务核心线程满了任务进阻塞队列队列满了才创建非核心线程非核心线程也满才触发拒绝策略。当年有同学在这里选错是因为把“队列满了”和“线程满了”的先后顺序搞反。线程池里这几个参数整理成表会清晰很多参数作用corePoolSize核心线程数即使空闲也通常保留maximumPoolSize线程池允许创建的最大线程数keepAliveTime非核心线程空闲存活时间workQueue核心线程满后新任务进入的阻塞队列threadFactory创建线程的工厂可用于自定义线程名RejectedExecutionHandler队列和最大线程都满时执行拒绝策略拒绝策略里需要重点记住AbortPolicy直接抛异常、CallerRunsPolicy调用方线程执行、DiscardOldestPolicy丢弃最老的任务、DiscardPolicy丢弃当前任务四种。面试追问时经常让你结合实际业务选一种比如你希望降级而不是崩溃就不能用默认的AbortPolicy。2.3 四大组件生命周期与启动模式Activity的启动模式在笔试里几乎年年有。那一年考的是singleTask和singleTop的区别。要注意singleTop是目标Activity已经在栈顶时复用而singleTask会清掉目标Activity之上的所有栈内Activity并且会回调onNewIntent而不是重新执行onCreate。接着它还会带一道Service相关的送分题startService和bindService分别能走哪些生命周期。startService走onCreate、onStartCommand、onDestroybindService走onCreate、onBind、onUnbind、onDestroy。如果你写的是只调用了startService的代码那么Activity销毁时Service并不会跟着销毁必须调用stopService或stopSelf。ContentProvider在选择题里一般考的是URI结构比如content://com.example.provider/user/1问你各部分分别代表什么。com.example.provider是authorityuser是路径段1是ID。这里可以顺便提一句后来Android N开始应用之间共享文件要依赖FileProvider它会生成一个content://的Uri给外部访问而不是直接暴露file://路径这正好是上面URI结构理解的一个实际延伸。3. 简答题深度拆解Handler、Binder与启动流程3.1 为什么主线程的Looper在死循环却不卡死网易简答题特别爱问Handler机制。常见表述是“请描述Handler、Looper、MessageQueue三者关系以及为什么在主线程中调用Looper.loop()不会导致ANR。”标准回答要抓住三条线。第一Looper是消息循环的引擎MessageQueue是消息的仓库Handler是投递和处理消息的入口。每个线程只能有一个Looper靠ThreadLocal隔离。主线程的Looper由ActivityThread在main方法里通过Looper.prepareMainLooper()创建。第二Looper.loop()确实是一个死循环但它在没消息时调用nativePollOnce进入epoll等待把线程挂起并不会占用CPU。第三ANR不是因为主线程死循环而是因为主线程在处理某个消息时超过时间限制没有完成比如输入事件分发超过5秒。回答这类题目有技巧先上来一句话概括“生产者-消费者模型”再分别讲三个角色最后解释为什么不卡死。结构清晰比堆术语重要面试官在简答题里真正想看的是你有没有把机制串成一条线。3.2 Binder机制为什么比传统IPC快另一道高频简答题是“为什么跨进程通信选用Binder”。很多人只会答“性能好、安全”这不够。需要展开传统IPC管道、Socket、消息队列通常需要两次拷贝发送方copy_from_user到内核态再从内核态copy_to_user到接收方。Binder利用mmap把内核态和接收方共享同一块物理内存只需要一次拷贝这是性能核心。安全方面Binder为每个应用分配了UID/PID内核层就能做身份校验而传统IPC用全局标识或者靠上层协议校验容易伪造。这道题如果能在最后补一句“所以我们平时说的AIDL就是为了生成Binder接口的壳”就很加分。补充一点Binder不是只有性能优势才被选中它还有一个设计上的考量Android系统里进程数量多传统IPC的Socket句柄和管道句柄在频繁创建销毁时开销很大Binder通过全局的Binder驱动统一管理稳定性和扩展性都更好。答到这里整道题的维度就比单纯背两句结论高出不少。3.3 Activity启动流程的答题框架启动Activity是四大组件里最经典的追踪题。建议回答时按角色划分三层应用内、系统服务、应用内回调。应用内发起startActivity走到Instrumentation通过Binder把请求发给AMSAMS做权限检查和任务栈处理必要时先拉起进程进程创建后通过ActivityThread的HHandler把消息切回主线程然后走performLaunchActivity最终触达onCreate。笔试答题不需要写源码行号但需要把这个链路写完整。我见过很多同学只写到“AMS处理了一下”就没了这个粒度不够。至少要提到ApplicationThread和ActivityThread.H这是理解整个启动流程的关键补位。如果你能再往下多说一句“启动过程是跨进程的所以AMS要借助Binder通信而ActivityThread.H是应用侧的主线程Handler负责把消息从Binder线程切回主线程”这道题基本就满分了。面试官要的不是你背出一长串方法名而是你有没有真正理解“跨进程主线程”这条主轴。4. 内存、性能与Android 8.0新特性那些事4.1 内存泄漏定位从Handler泄漏到匿名内部类那年的简答题里有一道很务实给出一个场景Activity里用Handler不断发送延时消息退到后台之后会发生什么。答案就是内存泄漏。非静态内部类Handler默认持有外部Activity的强引用消息队列里如果还有未执行的消息这个引用关系就断不掉GC怎么回收都回收不了Activity。修复方案有三层。第一层把Handler定义成静态内部类用WeakReference持有Activity第二层在onDestroy里removeCallbacksAndMessages(null)清空消息第三层现在更推荐用Lifecycle框架或者协程从生命周期上避免这种写法出现。笔试作答时前两层基本就够用了。这道题考察的不只是Handler而是你对“内部类持有外部类引用”这条Java规律的敏感度。类似的场景还有匿名Runnable、AsyncTask、静态View持有Context排查思路其实是共通的看到非静态内部类就先想一想它的生命周期是不是可能比外部类更长。4.2 ANR和卡顿的排查思路关于ANR选择题考察的是触发场景简答题考察的是怎么排查。触发场景记住四大类输入事件5秒没处理完BroadcastReceiver前台10秒后台60秒超时Service前台20秒后台200秒超时以及ContentProvider超时。排查思路用工具说话Android Studio自带的Profiler的CPU和Memory曲线能快速看主线程活动火焰图能看出函数调用热点日志里搜“ANR in”关键字systrace能看系统级调度。笔试题不会让你真的去抓systrace但它会考你知道这个排查链路。我当时在简答题里写了“先看logcat里的ANR in再用dumpsys activity processes查状态最后用Profiler分析”这属于按实际开发流程写面试官很认可。4.3 Android 8.0对开发的硬性约束2018年的笔试正好卡在Android 8.0API 26普及期所以它对通知、后台执行、隐式广播都出了题。通知必须指定NotificationChannel否则通知不显示后台Service在应用处于后台时有限制不能用startService随意启动后台服务要借助JobScheduler同时很多隐式广播都被禁了清单文件里再也无法静态注册这类广播。这里想提醒一下现在的读者做这套题的时候要把知识更新到Android 10以上的分区存储和通知权限但底层逻辑没有变——系统为了卡顿、省电和隐私一直在收紧后台能力。你答旧题时如果能顺带提到“新版又加了什么限制”反而会给面试官留下持续追踪版本更新的印象。5. 编程题实战链表、单例和TopK的考场解法5.1 反转链表迭代和递归都要会编程题第一道常客是反转单链表。我当时在考场上用的是迭代几分钟写完代码健壮性也好。给一个参考写法public ListNode reverseList(ListNode head) { ListNode prev null; ListNode cur head; while (cur ! null) { ListNode next cur.next; cur.next prev; prev cur; cur next; } return prev; }关键是先把next存下来再改指针顺序记牢。递归版本适合用来加分把问题拆成“当前节点之后的子链表已经反转再把头节点接到子链表末尾”。写这道题时最忌讳一上来就递归递归写不好还会爆栈考场里先保证迭代版稳过有时间再补递归。5.2 双重检查锁单例为什么volatile不能省另一道和Android开发强相关的手写题写一个线程安全的单例。很多人会写双重检查锁但容易漏掉volatile。原因是创建对象不是原子操作虚拟机里分三步分配内存、初始化对象、把引用指向内存。重排序后可能出现其他线程拿到半初始化的对象。volatile通过内存屏障禁止重排序从JSR-133开始DCL加volatile才是完全正确的写法。这里也可以写一个内部静态类的写法它天然线程安全靠类加载机制保证单例代码更少public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }5.3 找第K大的数堆和快选的取舍笔试压轴题每年都换有一年考的是找数组中第K大的数。这道题有两个方向用小顶堆维护当前最大的K个数堆顶就是第K大或者用快速选择平均O(n)。写堆更稳不容易被极端用例干掉。public int findKthLargest(int[] nums, int k) { PriorityQueueInteger minHeap new PriorityQueue(); for (int num : nums) { minHeap.offer(num); if (minHeap.size() k) { minHeap.poll(); } } return minHeap.peek(); }最后提醒一句笔试编程题一定要先想清楚边界条件空数组、k等于1、k等于数组长度、数组中有大量重复元素。这些用例在判题系统里基本都是标配不是刁难你。当年有同学AC了主流程却因为没处理k超过数组长度而挂掉实在可惜。6. 考生容易踩的坑和备战工具箱6.1 当年最典型的丢分原因我回访了那批同学丢分原因集中在三个地方。第一个是选择题读题太快把“不正确的一项”看成“正确的一项”这种丢分最可惜第二个是简答题只写结论不写推导比如Handler机制直接写“Looper在死循环但不卡死”完全不给原因第三个是编程题拿到题就写不先想清楚数据范围结果超时后再回头重构时间就没了。所以我在整理这份复盘的时候反复强调“先审题、再列点、最后落码”的顺序。选择题圈出否定词简答题先写总起句再写分点编程题先确认边界再动键盘。这三条能直接提升你通过笔试的概率。6.2 开发环境与工具链的合理预备备考过程里别忘了把工具链准备好。Android Studio版本和AGP版本是有对应关系的AGP 8.0以上要求相对较新的Studio版本如果你还在用老版本创建工程时会直接卡在gradle配置上。R8和ProGuard的开启方式、签名配置、flavor配置这些也应该早点跑通很多简答题会问到APK瘦身和混淆配置你要是真的在工程里做过一遍答题完全是降维打击。这里建议考前用一台配置还行的电脑把Android Studio、SDK Platform和模拟器都装好至少完整跑一个带依赖的Demo工程。笔试不会考IDE操作但你对构建流程的熟悉程度会潜移默化反映在答题质量上。手里有一把趁手的工具比临时背一堆名词踏实得多。6.3 面试官追问时的加分思路笔试结束只是个开始如果笔试通过了面试官经常会拿着你笔试里的简答题继续追问。比如你写Handler他会问一个线程能有几个Looper子线程里可以直接new Handler吗你写Binder他会问Binder线程池和主线程有什么关系你写单例他会问enum实现单例有什么问题应对追问的核心是别背答案把原理彻底理解。我当时被追问“主线程Looper和子线程Looper的区别”时现场画了一个消息循环的草图从prepare、loop到quit的退出方式都讲了一遍这比背三条结论更能体现功底。追问环节还有一个容易被忽视的加分点主动说出“如果换成Kotlin协程这个问题还可以这样规避”。比如Handler泄漏在协程里可以用viewModelScope来管理生命周期。这会让面试官觉得你不是只会应付笔试而是真的在跟进新技术。7. 写在最后的一点个人体会现在回头看这份2018年的网易Android实习生笔试题最有价值的地方不是题目本身而是它暴露了一个事实大厂选人从来不是看你会不会某个冷门API而是看你有没有建立一套底层知识框架。Java集合、并发、消息机制、进程通信、性能优化这几条主线直到今天也依然是Android面试的核心。我个人的建议是拿到这套题之后别只刷一遍就扔把每道题对应的知识点整理成自己的“错题本”过一个月再重新做一遍顺手把Android 8.0、10、14上的新变化标注上去。这样不管笔试题目怎么变你手里那套知识树都是新的。如果你也在准备实习或者校招不妨这周六抽两个小时关掉手机把这份复盘从头到尾过一遍然后动手把两道编程题在本地跑通。踩一次坑比看十篇攻略都管用。