
每年校招季都会收到类似的问题金山办公2020校招Android开发工程师笔试题二到底考了什么难度如何先说我的结论——这套笔试如果只靠背Android基础八股大概率过不了。金山办公在移动端的产品是WPS Office和金山文档用户量级大、功能链路长、兼容机型杂。它在校招笔试里真正想筛出来的不是会背知识点的人而是有完整工程思维、能在这套庞大代码库中落地做事的人。所以笔试题二往往不是单独一份“Android知识问答”而是一套围绕“如何做一个稳定流畅的办公App”展开的综合考察。下面我从出题人的角度拆一遍这份考卷每一类题在考什么、考官期待什么层次的回答、如果现在重新准备我会有哪些重点。1. 笔试题二的考查地图先知道公司在找谁再谈答题1.1 从WPS的业务痛点反推考点很多人刷了大量Android面试题笔试分数依然不高原因很简单你用“背诵”的方式准备而笔试用“场景”的方式出题。金山办公不是培训机构它出题一定是从WPS实际的开发痛点里来的。WPS在Android上要面对几大难题老旧机型的内存限制、文档解析的耗时任务、多端云同步的状态一致性、以及大量系统版本和ROM的兼容适配。所以你在笔试题二里看到的那几类题目表面上分散实际上都指向同一个核心能力——在复杂工程里做技术决策。题目类型典型考点对应WPS真实场景基础组件题Activity启动模式、Handler机制从通知栏/分享入口快速进入文档编辑页避免任务栈混乱Framework源码题Binder流程、AMS启动过程分析系统崩溃、适配不同ROM的进程模型性能优化题内存泄漏定位、启动耗时灰度测试中Crash率、启动速度优化算法/Coding题LRU缓存、字符串处理、多线程合并文档页缓存、文件名校验、分片下载合并这样一看就明白这不是一张拼记忆力的卷子。比如Activity部分如果只答“四种启动模式分别是什么”只能拿基础分如果能结合具体交互场景说明“为什么这个场景用singleTask而另一个场景用singleTop”就能把这道题答出区分度。1.2 这三类题目分别定位你的什么能力拿我实际拆过的一套题来说通常包含三个梯度第一梯度覆盖Java基础和Android四大组件这类题考得非常细比如某个回调的触发顺序、某个方法在哪个线程执行目的是筛掉基础不牢的人第二梯度上升到系统机制开始问Handler原理、Binder通信、AMS启动流程这决定了你能不能进第二轮面试第三梯度是开放性的系统设计和性能问题比如“如何设计一个文档分享功能”“如何优化一个反复卡顿的列表”这类题没有标准答案但最能反映候选人有没有真实的项目经验。基础题占60%中等题占30%难题占10%。笔试题二也一样如果基础题全对、附加题空白依然有机会进面试但如果基础题都有大片空白那就很危险。工具型App的团队尤其看重基础功扎实、没有明显短板的同学因为办公软件的生命周期长你写的代码是要维护三五年甚至更久的一个基础概念模糊的人进项目留下的坑会让整个团队买单。1.3 平衡“不会”和“会而不深”准备阶段有个误区把时间全花在研究罕见源码细节上反而最常见的启动模式、进程优先级没答好。我自己刷题的习惯是先做一遍“裸做”不看任何资料限定90分钟模拟真实笔试环境。做完后对完答案再把每道错题对应的知识点回到源码里重新过一遍。笔试考的不只是知识存量还有临场时间分配。哪个题先做、哪个题最后做这本身就是能力后面我会单独讲答题策略。2. 四大组件与消息机制基础题答成什么样才算“稳”2.1 Activity启动模式背定义之外还要能讲“业务场景”四种启动模式standard、singleTop、singleTask、singleInstance这是Android面试题里的常客。但笔试要拿高分只写定义不够。考官看到“standard每次启动都会创建新实例singleTop如果栈顶是自身则复用并回调onNewIntent”这只是背诵。更进一步的回答是知道singleTask要配taskAffinity使用知道singleInstance是全局唯一且独立Task知道Flag组合FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP和singleTask的关系。在办公场景里最典型的考题是用户在微信里收到一个朋友分享的WPS文档链接点击后要打开文档如果用户之前已经打开过一篇文档现在App被切到后台Task还在。这时候你希望打开新文档时回到同一个编辑页栈而不是叠出几十个Activity。做法是给编辑页设置singleTask taskAffinity然后在onNewIntent里更新文档内容。如果你能答出这一点就比你背出四种模式再配一个表格要强得多。还有一个很容易被忽略的点早期项目里launchMode如果设计不好在分屏、桌面快捷方式这些入口多了之后很容易乱。你要是能在笔试题的简答题里额外提一句“我处理过入口较多场景下的栈复用问题”面试官会记住你的。2.2 Handler消息机制从“怎么用”延伸到“怎么工作”Handler也是校招必考但很多人答到“Handler用于子线程通知主线程更新UI”就停了。笔试真正想看的层次是Looper.prepare()为线程创建Looper并保存在ThreadLocal中loop()进入死循环不断从MessageQueue取消息MessageQueue本质是一个按执行时间排序的链表当没有消息时next()会调用epoll阻塞避免主线程空转主线程Looper是ActivityThread在main()方法里通过Looper.prepareMainLooper()创建的。如果笔试考到代码输出题比如“在子线程直接new Handler会怎样”答案就是抛出RuntimeExceptionCant create handler inside thread that has not called Looper.prepare()。如果你能在答案里补充一句“要用HandlerThread或者显式调用Looper.prepare()”这题就拿稳了。进一步加分项是提到MessageQueue里的同步屏障与异步消息机制这解释了为什么UI绘制、事件分发在这些场景下可以优先于普通消息执行。办公App里Handler最常见的用途是文档解析任务的线程切换后台线程解析完docx后通过Handler回到主线程更新UI。但我在真实项目里很少直接用裸Handler而是封装成协程或者RxJava。笔试时候如果你能讲清楚“Handler的底层原理”和“业务中我会用协程避免回调地狱”这两层说明你既有基础又不僵化。2.3 Service与进程模型文档导出不能写在Activity里第四个考点大概率是Service相关。如果题目问“后台导出PPT时用户按Home键退出任务为什么中断了”这就是在考Activity销毁和Service生命周期之间的区别。正确的设计思路是把耗时导出任务放到Service里用startForegroundService启动前台服务配合进度通知。Android 8.0对后台服务有限制Android 12之后前台服务启动限制更严所以工程上现在更多人用WorkManager跑这类任务。回答Service题目的一个加分角度是能够讲清楚startService和bindService同时存在时系统如何计数什么时候onDestroy。因为文档App里“上传云文档”这样的任务往往是start和bind并存的start保证任务在后台继续跑bind用于获取进度。这个知识点和进程优先级也有关系系统内存不足杀进程时前台服务进程比普通服务进程更晚被回收。云文档上传如果被系统杀掉用户第一反应是投诉“文档传丢了”所以这种问题不是纯理论是真实会遇到的线上问题。2.4 BroadcastReceiver老知识点新限制广播这块比较容易出“判断对错”的题。Android 8.0之后大部分隐式广播不支持静态注册Android 14更是收紧了动态广播的导出要求。所以现代Android开发中模块间通信已经很少直接依赖全局广播。像WPS这种按模块拆分的App内部通信更多用的是事件总线或接口回调跨进程才走Binder或ContentProvider。笔试如果问“App内全局通知用BroadcastReceiver还是LocalBroadcast/Flow”答“优先用进程内通信方案避免全局广播泄漏和性能问题”更符合工程语境。当然系统广播开机、网络变化、充电依然要监听所以动态/静态注册的适用场景要分清楚。这一题很容易被当成纯记忆题但它其实在考察你对Android版本演进的理解。3. Binder与Framework把“系统启动流程”讲到别人答不出的深度3.1 Binder一次拷贝的魔力Binder是Android跨进程通信的核心。笔试考Binder常见问法是“为什么Android用Binder而不是Socket/共享内存/管道”。多数人能答出来“因为Binder只需要一次拷贝而Socket需要两次”但这还不够深。更好的回答是Binder基于mmap实现内核态与用户态共享内存发送方把数据从用户空间拷贝到内核空间接收方通过mmap映射直接读到数据省去一次拷贝。同时Binder在传输数据时携带UID/PID信息由内核辅助做身份校验安全性好于Socket。再加上每个进程维护一个Binder线程池调用时通过ServiceManager进行服务路由。如果笔试里要求“简述一次进程间调用流程”我会这样写客户端通过Proxy调用transact()将Parcel数据打包向/dev/binder写BC_TRANSACTION命令binder驱动处理事务找到目标进程的binder线程并唤醒目标进程的BBinder通过onTransact()分发到Stub实现执行完后把结果写回驱动客户端从reply中读回结果。整个过程对调用方是同步的但底层是异步加阻塞等待。能写出这一步说明你真的读过AOSP源码或者系统学习过Binder机制。我个人的感受是Binder题是最能拉开考生水平的题。同样一道题有人答两行定义有人能讲清楚内存拷贝路径。面试官一眼就能看出谁是真的理解谁是背的。如果时间有限Binder值得你花一周去啃因为它不只在笔试里有用后续面试追问和实际开发排查问题都会用到。3.2 Activity启动流程从点击图标到界面可见这是Framework面试题里的另一个高频题。“点击桌面图标启动一个Activity完整流程是什么”如果只答“AMS收到请求、创建Activity、回调onCreate”只能算及格。一个完整的思路是Launcher通过Instrumentation.startActivitySync向AMS发送启动请求AMS确认进程存在后如果进程不存在就请求Zygote fork新进程。新进程初始化ActivityThread创建Application然后加载目标Activity依次执行attach、onCreate、onStart、onResume。窗口部分由ViewRootImpl和WMS配合经过measure/layout/draw之后SurfaceFlinger合成显示到屏幕。注意一点Android 10之后ActivityTaskManager从AMS拆分出来职责有调整。如果笔试题考到新版本变化你能提一句“ATMS专门管Activity与TaskAMS管进程和生命周期”这又是加分项。办公App面试官考启动流程其实是想知道你能不能回答“为什么你的App冷启动慢”以及“启动慢该怎么查”。因为WPS这种体量的AppApplication初始化可能涉及推送、数据库、日志、网络、图片库、云同步等十几个模块如果没有启动顺序控制冷启动会跑上好几秒。所以现在看Framework题应该带着性能意识去学每个系统机制背后的耗时点在哪。3.3 WMS与多窗口适配为什么办公App特别关注分屏不一定每套题都会考但金山办公这种办公软件在平板上很重视多窗口。平板横屏、分屏、自由窗口下Activity要正确处理配置变更。如果笔试题里有“旋转屏幕时Activity如何保持编辑状态”你需要答出不重建Activity用onConfigurationChanged与resources.oldConfig判断尺寸变化但要注意targetSdk 13后必须声明screenSize和smallestScreenSize或者用ViewModel保存文档状态。WMS/SurfaceFlinger层面的知识在笔试中出现频率低一些但如果你能答出“窗口的measure/layout/draw最终交由SurfaceFlinger合成所以滑动卡顿要从渲染线程找原因”面试官会觉得你不只会写业务。文档类App经常要做“两页并排”的阅读模式这本质上决定了同一时刻有一个以上的页面在渲染对内存和渲染管线要求很高。这些题目背后的工程问题比“说出窗口类型有哪些”更有价值。3.4 ANR办公App最容易踩的坑Framework题里还会带一个ANR问题。笔试问“什么是ANR如何避免”往往是想看你对主线程模型的认知。ANR的本质是系统在特定超时时间内没有收到输入事件、广播或服务执行完成的响应。避免ANR的原则非常朴素主线程不能做耗时操作。但在办公App里这条原则很容易被打破比如在启动时读取一个大文件、在点击事件里解析Excel、在onCreate里加载本地字体库。我在真实项目中遇到过一个同事在Activity.onCreate里直接读取SD卡上的PDF元数据低端机上直接ANR。后来我们统一了规范所有文件I/O必须走独立线程操作大文件前先弹加载框解析任务设置超时和取消机制。这个经验可以在笔试题里当例子写比单纯背ANR触发条件有价值得多。答题时建议按“触发原因—排查路径—解决手段—后续预防”四层展开既完整又体现工程经验。4. 性能优化专项泄露、卡顿、启动耗时办公App的生存战4.1 内存泄漏Handler之外还有哪些“沉默杀手”最常见的笔试题是“写出一个内存泄漏的例子并说明如何避免”。很多人会答Handler持有Activity导致Activity无法回收这当然对但你可以答得更有项目感。比如一个静态单例的下载管理器把Activity对象放在回调列表里用户反复进入文档页面后Activity泄漏内存持续上涨。排查方式用LeakCanary观察泄漏堆栈然后改成ApplicationContext 弱引用回调 onDestroy时移除注册。检测内存抖动用Memory Profiler看Allocation记录。如果你能在笔试里写出“我经历过一次内存优化把应用从XX MB降到XX MB”这种数据化描述比空谈知识点更有说服力。我建议每个人都要整理一个自己参与过的性能优化案例包括背景、定位方式、根因、修复方案、上线后数据。这套结构化描述在校招面试里几乎一定用得上。还有一个很容易漏的知识点WebView内存泄漏。办公App里经常要加载在线帮助文档或网页WebView使用不当会让整个页面Context无法释放。标准做法是单独进程承载WebView在不需要时直接销毁进程。这个方案虽然简单粗暴但在工程上是极其有效的。4.2 卡顿与列表优化文档列表滑不动怎么办卡顿题的典型问法是一个列表加载1000条文档记录滑动时掉帧怎么优化第一层答案是RecyclerView复用ViewHolder、减少Item布局层级、避免在onBindViewHolder里做耗时操作。第二层答案是图片缩略图要用采样压缩加载不要直接加载原图列表里的日期格式化要缓存不要在getView里每次new SimpleDateFormat文本高亮功能别用多个Spannable在大量Item中频繁创建。更底层一点的思路用Systrace/Perfetto抓trace定位掉帧是在主线程measure/layout还是在draw阶段还是图片解码导致CPU峰值。办公App的文档列表滑动卡顿往往不是布局问题而是缩略图解码太慢。方案是在子线程预解码当前屏附近的缩略图用LRU缓存住解码限制尺寸为屏幕大小的1.5倍即可。能说出这种“由线下面到线上面”的排查路径说明你真的处理过卡顿。提示答卡顿优化题时建议把“布局层级问题”和“图片解码问题”分开说因为这两类问题的定位工具和修复手段完全不同。混在一起说会让面试官觉得你只有一个模糊印象。4.3 启动速度Application里应该做什么、不做什么启动优化几乎是每个大厂必考。笔试题可能是“如何优化App启动时间”也可能是“描述Application中应该做什么不应该做什么”。核心原则是Application启动阶段只做必须做的事其他全部延后。必须做的是崩溃捕获、Router表注册、核心基础库初始化。可以延后的是云信令连接、下载任务恢复、埋点上报、主题资源预加载。这里有一个常常被忽略的点ContentProvider也是启动耗时大户。App启动时系统会实例化所有注册的ContentProvider并执行onCreate如果你的App集成了多个SDK每个SDK都通过ContentProvider初始化启动链路就会显著变长。使用AndroidX Startup库或者自研启动框架后把初始化任务做成有向无环图按依赖关系并行执行同时统计每个任务的耗时。我在项目里做过一次启动优化把一串串行任务改成DAG并行后冷启动时间从2.1秒降到1.4秒效果非常可观。笔试题如果问“启动时间怎么测量”你可以答线上用自定义埋点记录onCreate开始到首页onResume结束线下用adb shell am start -W拿到系统级数据二者结合。能答出工具和指标就是加分项。4.4 缓存与图片LRU在办公App里的真实形态性能相关还会考LRU。LinkedHashMap的accessOrder为true时每次get会移动节点到链表尾部removeEldestEntry返回true时移除最久未使用。手写LRU一般要求用HashMap双向链表。笔试时可以直接写一段简洁的实现class LRUCacheK, V extends LinkedHashMapK, V { private final int maxSize; public LRUCache(int maxSize) { super(16, 0.75f, true); this.maxSize maxSize; } Override protected boolean removeEldestEntry(Map.EntryK, V eldest) { return size() maxSize; } }但面试官可能追问它线程安全吗答案是不安全。如果要线程安全可以加synchronized或者用ConcurrentLinkedHashMap。在WPS文档预览的场景里我们缓存的是“最近打开的文档页”和“缩略图”key是文档id页码value是解码后的Bitmap或转换后的文档数据。内存缓存之外还要有DiskLruCache因为文档重新打开时如果能从磁盘缓存命中打开速度会快很多。答缓存题时最好把“内存缓存”“磁盘缓存”“网络拉取”三层都说到这才是完整的缓存设计思维。5. 算法与Coding括号、LRU、字符串处理都在模拟办公软件的日常5.1 手写LRU Cache这道题几乎必考前面讲缓存时已经给了代码。笔试题里如果让手写我会直接写LinkedHashMap实现再补充一句“也可以用双向链表HashMap实现底层细节”。实际工程中Glide、DiskLruCache都在用LRU策略。文档类App里LRU适合做“最近打开的文档列表”只保留最近20个、“图片缩略图”只保留当前屏幕附近30张。因为你不能把所有文档内容都放内存而用户总是倾向于来回切换最近打开的文档LRU正好命中这个局部性原理。如果还要求手写双向链表版需要在节点里维护prev和next指针HashMap中value存节点引用。get时把节点移到链表头部put时如果key已存在则更新值并移到头部不存在则插入头部如果容量超限就移除链表尾部节点。整个过程要特别注意空指针因为这是考代码完整度的主要地方。5.2 字符串与文件名校验送分题为何总有人丢分笔试题常见的字符串题包括“判断括号是否匹配”“压缩字符串”“合并两个有序字符串数组”。看起来是LeetCode简单题但放在办公App里字符串处理对应的是文档名的合法性校验、XML转义、路径拼接、文件协议解析。比如文档名里不能包含/\:*?|这些字符跨平台同步时Windows和Android规则还不一样。回答字符串题时除了给出正确代码还要主动说边界情况空字符串、极长字符串、只有前括号没有后括号。校招笔试踩分点很大一部分在边界条件处理上。很多同学代码主体写对了但忘了判断stack.isEmpty()就pop这是最可惜的丢分。一个习惯是写代码时先把边界条件列出来再开始写逻辑。这样代码会显得更规范也更容易通过线上OJ的隐藏测试用例。5.3 多线程任务合并下载分片和文档解析的协作如果笔试考“多个线程各自下载文档的一部分全部完成后合并成一个文件”这是个很好的综合题。答案是CountDownLatch倒计数等待所有线程完成或CompletableFuture.allOf().thenAccept()聚合结果。下面给一段伪代码int totalParts 8; CountDownLatch latch new CountDownLatch(totalParts); ExecutorService pool Executors.newFixedThreadPool(4); for (int i 0; i totalParts; i) { int partIndex i; pool.execute(() - { try { // 下载第partIndex个分片并写入临时文件 } finally { latch.countDown(); } }); } latch.await(30, TimeUnit.SECONDS); // 所有分片就绪后按分片序号排序合并输出注意合并时要按分片序号排序不能按完成顺序合并等待要有超时时间防止下载线程崩溃导致主线程永久阻塞。工程上更推荐的方案是CompletableFuture或协程因为CountDownLatch使用后不能重置多次任务要重新new而CompletableFuture的编排能力更强。笔试先答出CountDownLatch已经很稳能对比两者差异就是锦上添花。5.4 线上笔试的做题顺序先画草稿再动键盘在线笔试的时间管理很关键。我看到很多同学的错误是一上来就写代码写到一半发现思路错了又删掉重写。我的建议是先读题两遍在纸上写出关键数据结构、输入输出示例、时间复杂度然后先写一个暴力解法保证有分再优化到最优解法。代码写完一定要在编辑器里编译执行几次至少保证样例能过。真实笔试里因为一个英文拼写导致编译不过的情况太多了。如果题目要求从标准输入读取数据最先写好Scanner和输出模板能省下一点心理压力。6. 复盘这份笔试题最值得补的是两块“软实力”6.1 基础题求稳源码题求深项目题求真实如果你现在才开始准备Android校招时间分配上我建议操作系统/网络/Java基础占30%Android四大组件和消息机制占30%性能优化与常见开源库占20%算法占20%。按这个比例笔试题二里的基础题至少不会失分。源码题Binder、启动流程可以放后面因为它是拉开差距的地方性价比需要自己权衡。如果时间只有两周宁可把Handler和Activity启动模式彻底搞透也不要走马观花把AMS、WMS、Binder全看一遍。面试官追问两个问题就知道你到底会不会。6.2 源码阅读不要贪多从一条链路完整读起我见过很多同学收藏了一堆源码分析文章结果一篇没读完。我的经验是只读一条完整链路读透。比如从启动Activity开始跟踪到ActivityThread.performLaunchActivity再把onCreate最终走到哪里搞清楚。一条链路下来Zygote、AMS、Handler、反射、ClassLoader这些点就都串起来了。这种串联记忆非常牢固比一个知识点一个知识点孤立背诵强得多。读源码的方式也有讲究。不要只看博客要对着源码看。Android Studio可以下载对应的Android SDK源码直接点进去看方法实现。第一遍看完会忘这是正常的第二遍再梳理时序第三遍自己能画出来基本就内化了。笔试前我建议至少把“Activity启动”和“Handler消息循环”这两条链路完整走一遍这是笔试题二问得最频繁的两个话题。6.3 笔试后的复盘比笔试本身更重要笔试题二考完就算了不是。面试官很可能拿着你的笔试卷子追问。所以考后一定要回顾自己哪些