从2017京东校招看Android面试:主观题考点与答题思路

发布时间:2026/8/30 16:42:11
从2017京东校招看Android面试:主观题考点与答题思路 最近翻旧电脑里的面试笔记翻出一份2017年京东校招Android方向的主观题汇总。那年我还在一线做Android开发从笔试、面试到帮部门整理题库都经历过一遍对这些题的感情挺复杂。今天回看最大的感受是2017年前后Android校招的考察方式正好到了一个转折点——面试官不再满足于“某个API你用过没有”而是反复追问“为什么这么设计”“你遇到问题怎么定位”“这个方案有什么代价”。主观题就是这种考察倾向的集中体现。这篇文章就以当年的题目为索引把几大高频方向逐类拆解讲清楚每道题背后的考察点、高分答题思路和容易翻车的细节。无论你是正准备校招的应届生还是工作几年想跳槽的Android工程师这套梳理都会对你有帮助。1. 2017年那会儿Android面试到底在考什么1.1 当时的技术栈Java为主、Kotlin刚冒头2017年的Android生态放在今天看很有时代感。Android 8.0刚刚发布后台服务限制、自适应图标这些新特性让不少老项目开始焦虑兼容成本Kotlin 1.2刚发布没多久虽然社区讨论热度在起来但绝大多数团队的线上代码还是Java。网络层基本是Retrofit 2加OkHttp的天下异步任务被RxJava 2.x拿下了半壁江山图片加载则是Glide和Fresco各占一片阵地Fresco在图片巨大、内存敏感的场景里优势明显Glide以轻量和生命周期绑定收获了大量粉丝。移动架构上MVP已经成为团队标配的讨论话题很多项目已经在用MVP重构旧代码。那一年Google在I/O大会上推出了ViewModel和LiveData把MVVM从理论推向了工程落地不少技术团队开始认真评估“要不要从MVP迁到MVVM”。组件化、插件化、热修复更像是一场军备竞赛Tinker、Sophix、Robust各显神通面试题里自然少不了“你了解插件化吗”这种问题。这个技术背景下校招考点的分布其实很明确。Java基础、Android四大组件、Handler、性能优化、架构演进这些是硬核主干。但如果你只看面试书去背答案很容易发现2017年的面试官已经开始追问“那底层是怎么实现的”“线上出问题你会怎么排查”光记结论已经不够用了。1.2 为什么笔试里开始塞主观题客观题适合大规模筛选但劣势也很明显它可以轻松筛掉完全不会的人却筛不掉背题库的人。一个候选人可以靠刷题记住“LruCache是什么”“ANR有哪几种”但你要是在面试里追问“为什么用LruCache而不是FIFO淘汰一个元素的时候要做什么”很多人就露馅了。主观题的价值恰恰在这里。它不给选项让你在一个开放问题里自由组织答案考察的是三件客观题测不出来的事第一有没有一套属于自己的方法论。比如“设计一个图片加载库”你是否能按链路从加载、解码、缓存、显示完整拆开。第二有没有真实的项目经验或者动手实验的经验。用词里的细节感骗不了人做过和背过是两种完全不同的表达状态。第三能不能在表达里展现逻辑和取舍。主观题没有标准答案面试官真正想看的是你面对不确定问题时是否具备工程判断力。京东2017年这轮Android校招的主观题从我接触到的题库和面经反馈来看大概集中在四类架构设计、性能稳定性、系统机制、以及开放性设计。下面我按这四类一个一个拆。2. 架构与设计题不是让你写代码是让你拆链路架构设计题在主观题里最考验内功。很多同学拿到“从零设计一个图片加载库”这种题就懵了觉得要么无从下手要么只能背几个名词。其实这类题的答题节奏很重要与其堆名词不如沿着一条核心链路把问题讲完整。2.1 图片加载库从一张图到整条加载链路“从零设计一个图片加载库”当年被问到的概率非常高。很多人一上来就答“三级缓存内存、磁盘、网络”这句话没毛病但它只是骨架不填血肉的话面试官没法判断你是真懂还是只会背口诀。我建议的回答顺序是先定义核心流程再逐层补细节。一张图片从加载到显示本质上要经过“图片源的获取→解码与裁剪→缓存策略→线程调度→显示回调”这几步。其中图片源有三个层次内存缓存、磁盘缓存、网络。你可以顺着这个流程把架构拆成几个模块比如负责发起请求和调度资源的Loader、负责缓存淘汰策略的Cache、负责Bitmap采样与复用的Decoder、负责管理子线程与主线程回调的Dispatcher、负责最终显示到ImageView的Display层。接下来必须讲关键细节。比如怎么避免OOM这是图片库绕不开的问题。你需要提到BitmapFactory.Options里的inJustDecodeBounds它能让解码器先只读图片的宽高信息而不生成Bitmap方便你计算采样比例。然后用inSampleSize做采样缩小处理大图时还要考虑inBitmap做内存复用把已经不再使用的Bitmap内存块交给新的解码使用。这里有个知识点容易混淆采样和有损压缩是两回事采样是减少像素数量质量压缩是调整编码体积图片库做缩略图时主要靠采样。缓存策略也不能只讲LruCache的名字。你得说清为什么内存缓存用LruCache而不是普通Map——LruCache基于LinkedHashMap的accessOrder机制能在每次访问后把最近使用的条目放到队尾当缓存满时优先淘汰最久没被访问的条目。磁盘缓存一般参考DiskLruCache多维护了一个journal文件来记录访问和删除操作。能把这个机制讲清楚面试官会认为你确实研究过缓存实现而不只是知道依赖里有个Glide。还有一个隐藏加分点生命周期绑定。Glide为什么能感知Activity和Fragment的生命周期因为它会向当前Activity的FragmentManager注入一个透明的SupportRequestManagerFragment利用Fragment生命周期回调来暂停和恢复图片请求。这个设计在2017年算是个“进阶彩蛋”说出来了整道题的答案质感会明显不一样。2.2 MVP和MVVM别急着站队先给对比维度“MVP和MVVM的区别项目里你会选哪个”是当年架构题的另一座大山。注意2017年大家讨论的不是Jetpack Compose也不是Flutter而是最朴素的架构模式选择问题。但正因为朴素回答时才更要小心。很多人容易一上来就表态“MVVM更好因为它数据驱动”。这种站队式回答在面试官眼里其实很危险因为你暴露了自己缺乏对比意识。更聪明的做法是先拉出几个对比维度把两种方案放在维度上比较最后再落到实际项目约束里给出选择。第一个维度是职责划分。MVP里的Presenter是控制器持有View接口手动管理页面逻辑坏处是接口数量爆炸每多一个页面交互就要多维护一组View接口。MVVM里ViewModel对外暴露LiveData或ObservableFieldUI层通过观察者模式自动更新接口少了但数据驱动的心智模型需要团队适应。第二个维度是可测试性。MVP的Presenter可以在纯JVM环境里做单元测试但需要mock大量View接口MVVM的ViewModel天然不依赖Activity测试更轻。第三个维度是生命周期安全。这也是当年Architecture Components刚推出时最大的卖点LiveData能感知生命周期Activity销毁后自动停止派发数据不会出现“页面关了回调还更新UI导致崩溃”的老大难问题。我这里建议你千万不要说“无脑选MVVM”。正确的落点是如果是复杂页面、交互频繁、需要大量单元测试我会选MVVM如果是一个快速迭代的小模块团队又对数据驱动不熟悉那MVP甚至MVC都完全够用。关键不是哪个架构更先进而是团队能不能形成统一的约定。这种“没有银弹”的态度比单纯吹某个模式要可信得多。2.3 组件化与路由解耦后模块之间怎么说话组件化在2017年同样是高频话题对校招生来说偏难但答好了非常出彩。主观题通常这样问“如果你们App要做组件化改造模块之间怎么通信为什么用路由而不是直接startActivity”你要先点出问题背景模块化拆分后模块A要跳转模块B的页面但两者不能形成编译期依赖所以不能直接写“startActivity(new Intent(this, BActivity.class))”。这个问题的标准解法是路由。路由的核心是“把页面跳转抽象成路径字符串”用一个运行期注册表保存“路径到页面类”的映射关系跳转时通过路径找到对应Activity同时支持参数注入、拦截器、降级策略。你可以提一下ARouter也可以说你自己实现过一个简单Router注册表用Map存路径和Builder的对应关系navigation方法负责统一入口拦截器可以在登录校验、埋点上报这些场景里做统一处理降级策略则负责在找不到页面时跳转到WebView或给用户一个友好提示。只答路由还不够完整组件化通信还有一个经典方向是接口下沉把公共接口定义下沉到底层基础库具体实现由各业务模块完成并注册到服务管理器里调用方通过接口获取服务实例。这本质上是一个服务定位器2017年的AppJoint、ServiceManager都是这个思路。这道题最容易踩的坑是把组件化讲成“把代码按功能分模块”就结束了。你要多讲“边界划分”“资源冲突”“重复依赖治理”“组件单独调试开关怎么做”。单靠一个路由解决不了所有问题工程治理才是组件化真正的挑战。面试时能说清这些说明你真的在项目里踩过坑。3. 性能与稳定性题答案先要有定位思维性能优化题几乎每年必考但很多同学在这类主观题上丢分是因为答案太像教科书——列一堆优化手段却看不出你实际解决问题的过程。面试官想听的是“现象→定位→根因→修复→验证”的完整闭环而不是“我们可以用LeakCanary”这种一句话方案。3.1 内存泄漏Handler作为经典入口如果问“你遇到过哪些内存泄漏怎么解决的”先别急着把七个场景全背出来。挑一个最有代表性的场景把它从头到尾讲透效果远好于蜻蜓点水。Handler就是最典型的一个。为什么Handler容易泄漏因为非静态内部类持有外部类的隐式引用。使用时你经常在Activity里new一个Handler然后发送一个延迟消息到MessageQueue。问题是MessageQueue里的Message通过target字段持有HandlerHandler又隐式持有Activity这就在系统还没处理这条消息时把Activity和整个View树都钉在了内存里导致Activity无法被回收。怎么定位LeakCanary的原理你可以提一嘴它在Activity.onDestroy之后用弱引用观察Activity对象通过ReferenceQueue判断对象是否真的进入回收流程如果没回收就主动触发GC并分析引用链最终把“谁持有这个Activity导致它无法释放”的路径显示出来。你能讲清楚这个原理面试官就知道你用过LeakCanary而不只是集成过。怎么修复两个方案配合使用一是把Handler声明为静态内部类并对外部Activity使用弱引用二是在Activity.onDestroy时调用handler.removeCallbacksAndMessages(null)把队列里所有未执行的消息清掉。这里有个细节值得补充静态内部类为什么相对安全是因为它不再隐式持有外部类弱引用是兜底真正的关键是取消那些还没执行的消息不然弱引用也救不了已经被消息队列持有的target链路。除了Handler内存泄漏还有一些高频场景单例传入Activity当作ApplicationContext使用单例生命周期长一直持有Activity导致页面无法回收注册了系统服务或者广播没有在onDestroy里注销网络请求回调持有Activity比如OkHttp回调是匿名内部类在页面销毁后线程仍执行回调自定义View中动画未停止等。每个都能用“泄漏路径→修复手段”这个结构去答比单纯罗列场景要有说服力。3.2 ANR阈值只是入场券定位才是正题“什么是ANR如何定位”这道题很多同学以为背完“Activity 5秒、BroadcastReceiver 10秒、Service 20秒”就过关了结果面试官立刻追问“那线上碰到ANR你怎么查”这就卡住了。我对这道题的理解是ANR的本质是系统通过超时机制兜底判断主线程是否长时间无法响应用户操作。Activity输入事件分发超时、广播处理超时、Service执行超时是三类典型场景数值可以记忆但更要理解超时背后的意图——主线程被某件事阻塞太久了必须有一个机制让系统“弹窗”而不是无限等下去。定位手段要按工具链来回答。拿到logcat之后去读系统输出的traces信息这个文件在不同版本上路径不一样但通常可以在/data/anr目录下找到或者通过bugreport导出。重点看主线程main的栈顶函数卡在哪卡在某个IPC等待就要看Binder调用在等谁卡在锁等待就要看锁被哪个线程持有卡在SharedPreferences的apply或者文件读取说明主线程做了耗时IO。能顺着栈定位到具体线程基本就找到了根因。常见的ANR根因也要准备主线程里做了文件读写或数据库操作、广播回调里执行耗时任务、主线程排队等待一个长时间运行的锁、Binder调用调用远程服务超时。如果面试官接着问预防措施你可以说用StrictMode在开发阶段发现主线程IO对耗时可预见的任务做埋点比如在Choreographer回调里统计主线程消息执行时长项目里规范网络、数据库操作必须走子线程用HandlerThread、AsyncTask或协程2017年还没有协程可以说线程池。这道题还有个加分思路把ANR和卡顿放在一条线上理解。主线程消息执行时间过长先是掉帧用户感知为“卡”如果继续恶化到系统判定超时就会弹出ANR。这样回答就不只是介绍ANR而是把系统机制关联起来显得格局更大。3.3 卡顿别把性能优化八个方向全倒出来“App滑动不流畅你会怎么排查”这是一道非常典型的开放型主观题。它的陷阱在于很多人会把“卡顿优化”答成“性能优化大全”把启动、内存、电量、网络全堆上来。面试官问的是卡顿你就应该围绕帧渲染和主线程这条主线展开。第一层是理解卡顿的本质。屏幕每16.6ms刷新一帧如果一帧的生产时间超过这个阈值就会掉帧掉帧多了用户体感就是卡顿。所以排查思路的第一步是确认掉帧发生的位置是主线程CPU占用过高导致的逻辑计算耗时还是布局绘制太慢还是GC频繁导致线程挂起。第二层是使用工具验证。我建议的回答主线是先用Systrace抓一段trace看主线程在每一帧里执行了哪些耗时Task区分是CPU调度问题还是代码问题再用TraceView或CPU Profiler对可疑方法做采样分析。如果你是现在准备面试可以说Perfetto但2017年面试时Systrace还是主力能说一句“我抓过Systrace看主线程有一段长时间执行了JSON解析”会非常加分。比这更重要的是“先有假设再上工具”的思维而不是打开工具盲目点来点去。第三层是具体优化手段这层不必贪多挑两三条和你经验最匹配的展开就够了。比如布局优化减少层级堆叠用ConstraintLayout减少嵌套用merge合并根节点用ViewStub延迟加载不常显示的模块。比如内存抖动主线程频繁创建小对象会让GC频繁触发GC的stop-the-world会挂起线程导致帧来不及渲染监控到GC密集后要定位是哪里在循环里创建了对象。又比如过度绘制打开开发者选项的“显示布局边界”看是不是有多层背景色叠在一起避免在父布局和子View同时设置同色背景。这里分享一个我自己踩过的坑当年我遇到一个页面列表滑动首帧明显掉帧第一反应是去优化列表Adapter里的item xml层级结果优化半天效果不大。后来用Systrace一看掉帧点根本不在layout而在主线程等一个锁锁是线程池里一个图片下载任务在前面占着。所以卡顿优化工具定位一定优于凭感觉猜。这个经验讲给面试官听他会觉得你是个真正解决过线上问题的人。4. 系统机制题主观题更爱问底层为什么系统机制题在校招里本来就必考但主观题的考法比客观题狠得多。客观题可能只是“Looper在哪创建”主观题会直接说“聊聊Handler机制顺便解释为什么主线程的Looper是死循环却不会ANR”。答得好的人一定对源码有概念而不只是背过面经。4.1 Handler消息机制死循环和epoll的相遇Handler这套机制由Handler、Looper、MessageQueue三个类组成。Handler负责发送和接收消息Looper负责循环取消息MessageQueue是消息的存储队列。你可以这样理解Handler.sendMessage把Message按时间顺序插入队列Looper.loop()用一个死循环不断从队列里取消息取到就交给Handler的dispatchMessage处理处理完再取下一条。关键在MessageQueue.next()这个方法的阻塞机制。当队列里暂时没有消息时next()不会让线程空转而是会进入阻塞状态如果有延迟消息就计算一个距离执行时间还剩多久的阻塞时间如果没有任何消息就无限阻塞等待被唤醒。它内部调用的nativePollOnce在Linux层使用的是epoll机制。也就是说主线程在空闲时是让出CPU陷入睡眠的不是占着CPU疯狂空转这就是为什么主线程Looper死循环不会导致CPU耗尽。顺着这个逻辑往下推ANR的真正原因就不是“Looper是死循环”而是“loop()抽到了一条消息但这条消息的处理耗时长到超时”。比如你主线程执行了一个10秒的SharedPreferences读取队列里后续的触摸事件没法及时被取出处理系统发现输入事件长时间没有回调才判定ANR。把“死循环让出CPU”和“消息处理超时导致ANR”这两件事分清这道题基本就是满分答案。如果想再进一步加分可以补充两个源码细节。一个是Message在取出处理后会把msg.next重置成null避免消息复用时链表串链另一个是Looper在退出时消息队列的next()会返回null来终止循环。这些细节不需要背源码行号只要能说出来面试官会觉得你确实读过。4.2 Activity、Window、View一套完整的事件链条“Activity、Window、View三者的关系”和“一次触摸事件是怎么分发的”经常合在一起考因为它们本来就是同一套体系。你不需要分开答可以一起讲。核心概念是这样的Activity负责生命周期和业务交接但它本身不画界面。真正承载界面的抽象是Window具体实现是PhoneWindowPhoneWindow里有一个DecorView它是整个视图树的根ViewViewRootImpl则负责把View树和WindowManagerService对接驱动measure、layout、draw这套流程。简单说Activity是控制器Window是窗口抽象DecorView是根布局ViewRootImpl是连接系统服务和View树的桥梁。事件分发可以顺着责任链讲手指按下时事件先到Activity转交给Window再传到DecorView然后从ViewGroup开始一层层向下分发给子View。这里涉及三个方法dispatchTouchEvent负责分发onInterceptTouchEvent是ViewGroup独有的拦截方法onTouchEvent负责真正消费事件。如果子View的onTouchEvent返回false事件就会回溯到父ViewGroup的onTouchEvent再不行就传回Activity的onTouchEvent这条链叫责任链。还有个细节很关键整个事件序列DOWN、MOVE、UP并不是每次分发都从顶层重新走一遍。DOWN事件确定了事件的目标View后后续MOVE和UP会优先发给同一个View除非中途被父ViewGroup拦截。这说明你理解事件序列的处理逻辑而不是单独背了某个方法。另外可以补一句ViewGroup的onInterceptTouchEvent不一定每次都要拦截只在需要抢占事件时才返回true比如列表滑动和item点击的冲突处理这也是面试里常延伸的场景。4.3 Binder和并发从原理到典型的Android场景Binder是Android系统机制里的大头但校招主观题一般不会直接问“Binder在内核里怎么实现”更多是“说说你对Binder的理解”“为什么Android选Binder而不是Socket或共享内存”。我的回答习惯是把Binder理解成一个跨进程的“快递员加共享仓库”模型。发送方要传数据时先把数据从自己的用户空间拷贝到内核空间接收方通过内存映射机制共享这块内核空间省去了一次接收方侧的额外拷贝所以Binder跨进程只需要“一次真实拷贝”。和Socket、管道、共享内存相比Binder在传输性能、安全校验、稳定性之间取了一个平衡。特别是身份标识机制系统可以识别调用方UID用于权限校验这是Binder在系统服务里大面积使用的重要理由。2017年的面试里你不一定需要把内核细节讲多深但“一次拷贝”和“安全校验”这两个关键词必须有。并发部分高频主观题包括“volatile和synchronized的区别”“在多线程里怎么保证数据一致”。很多人只记住“volatile保证可见性不保证原子性”但缺少Android场景的绑定。我建议这样答volatile主要用于状态标志、DCL单例的场景它通过禁止指令重排来避免“半初始化对象被其他线程拿到”synchronized用于临界区保护同时提供可见性和原子性但代价是锁竞争可能带来阻塞。在Android里还要知道有些组件天生自带串行队列比如HandlerThread和IntentService它们通过单线程消息队列把并发请求串行化反而避开了锁的问题。能在答案里体现“不是所有并发问题都要上锁”的工程判断这道题就有区别度了。5. 主观题怎么答才能让面试官记住你看完了这么多道题你会发现主观题虽然千变万化但答题结构其实可以训练。最后这一章聊聊这些年我总结的主观题表达经验同样适用于社招。5.1 三段式结构定义、设计、边界遇到任何主观题我建议心里按“定义与背景→方案与设计→边界与取舍”三段来组织。这不是僵化的模板而是一个稳定输出的逻辑框架。第一段用一两句话精准定义问题。比如问“什么是内存泄漏”你可以说“生命周期较短的对象的引用被生命周期更长的对象持有导致该对象无法被GC回收这就是泄漏”。定义越准确越能呈现你的知识边界。第二段展开你的方案或设计。这里最忌讳想到哪说到哪一定要沿主线走。问图片加载库就按加载链路讲问卡顿就按“掉帧→工具定位→优化手段”讲问组件化就按“解耦→通信→工程治理”讲。主线清晰面试官才能跟得上你的思路。第三段聊边界和取舍。要么说“这个方案在什么情况下不适用”要么说“如果继续优化我会从哪个方向切入”。无论哪句都能体现出你不是在背题。我见过一个候选人讲MVP讲得头头是道从职责划分到接口设计都非常流畅但面试官问“那MVVM比它多了什么”他明显卡住。这种尴尬就来自只准备了方案的优点没有准备对比和边界。主观题的开放提问不是让你证明“某个答案是对的”而是让你展示一套“在复杂环境下怎么做决策”的思考框架。5.2 这几类答题姿势属于送命题我从面试官视角替大家总结了几种特别影响主观题分数的行为希望你们有则改之。第一类是背概念但答非所问。比如问“你为什么在项目里用MVP”回答“MVP由Model、View、Presenter组成把逻辑分离了”。这句话每个字都对但压根没回答“为什么”。面试官真正想听的是当时项目面临什么问题MVP帮你解决了什么具体痛点比如Activity代码膨胀、单元测试难写还是多人协作时代码边界混乱。第二类是大量使用模棱两可的词。“好像是”“我记得应该是”“可能就是”这些话会极大削弱可信度。哪怕你对细节只有六成把握也应该用肯定语气说“它的做法是……”再补充“具体实现细节我记不太清但原理上应该是……”。第三类是纯理论没有例子。你说自己有优化经验一定要跟一个项目例子哪怕是一个非常小的点“当时我们列表卡顿我在onBindViewHolder里发现创建了太多临时对象改成复用后明显改善。”有具体项目背景的表述可信度会高很多。第四类是遇到不会的问题直接沉默。你完全可以坦然说“这个点我确实没有深入过但根据我的理解它可能和……有关我下去会补一下。”这比硬编一个答案体面太多。这些技巧看起来都很简单但真到面试高压状态下很容易忘。我的建议是在面试前找朋友做一次模拟问答专门针对主观题练习强迫自己在五分钟内把“定义—设计—边界”说完整。练熟了面试时才能自然表达。这些年我陆陆续续参与过几次校招评审再回头看2017年京东这套Android主观题最大的感触是很多问题到现在都不过时。技术栈从Java迁到了Kotlin架构从MVP演进到MVVM再到Compose但面试官考察的核心始终没变——你说你会那你到底理解到什么程度你拿什么证据证明。如果你正在准备Android面试与其刷一百道题不如挑十道最典型的主观题每一道都用“原理加实践加边界”完整过一遍再找个人模拟讲一遍。能讲清楚才算是真的会了。