拓冰建站拓冰建站
首页 / 资讯中心 / 正文

2018春招360 Android笔试客观题复盘:考点、原理与面试价值

这几天整理旧电脑里的面试资料又翻到了2018年春招做过的360 Android开发笔试题。老实说看到这套题时我愣了一下因为当年觉得“偏”的不少题目放到现在反而成了基础中的基础。这套客观题是360公司2018年春招Android开发工程师岗位的笔试题合集以单选题和多选题为主覆盖Java基础、Android四大组件、Handler、性能优化、系统源码、版本适配等模块。对于准备Android面试的应届生、做技术校招出题的老手、以及工作两三年想查漏补缺的开发者都有很强的参考价值。下面我按当年题型还原了一些典型考法并把每个考点背后的原理和坑位一起拆开讲。1. 先看这套题考什么整体考点分布与出题逻辑1.1 客观题为什么难很多人以为“客观题”就是背概念选A还是选B全看记忆力。真做了这套题你会发现出题人根本不满足于让你判断“对不对”而是喜欢在边界条件和组合场景上做文章。比如同样考Activity生命周期直接问“A启动BA执行了哪些回调”不算难难的是给你一段代码B是半透明主题A在B启动后到底有没有执行onStop为什么这种题一旦脱离场景背再多生命周期口诀也容易翻车。所以看这套客观题不能只看题目本身要看它背后的考察逻辑第一你是否理解Android框架的运行时行为第二你能不能区分“概念正确”和“在真实Android系统里正确”之间的差异第三你面对多选时能不能把每个选项当作独立的知识点去验证。这也是为什么很多人单选能做对多选却拿不准因为多选本身就是“每个选项都是判断题”。1.2 考点分布与分值占比我把这套题按模块大致分了一下比例不一定精确但能体现当年出题的重点分布知识模块大致占比典型出题方式Java基础、集合与并发15%HashMap原理、volatile、线程安全集合Android四大组件25%生命周期组合、启动模式、Service绑定Handler、线程与异步15%Looper消息队列、AsyncTask、HandlerThread内存、卡顿与ANR20%内存泄漏场景、布局优化、ANR触发条件系统源码与底层原理15%AMS启动流程、Binder、类加载、进程优先级工程化与版本适配10%Gradle、混淆、权限、Android 8.0行为变更从这个分布可以看出Android基础组件和性能优化是绝对的重头戏加起来接近一半。这个逻辑到今天也没变校招看重基础社招看重解决线上问题的能力而性能、内存、并发这些问题最能反映一个开发者的系统理解水平。1.3 出题逻辑边界与组合这套题里有一个反复出现的套路就是“单个知识点你会组合起来你未必会”。典型例子是启动模式taskAffinity一起考或者Service生命周期进程优先级一起考。还有一种套路是“改条件”把普通的Activity换成singleInstance把普通广播换成有序广播把主线程操作换到子线程让你判断结果是否改变。应对这种题只有一个办法不要停留在结论层要把每个结论的适用条件搞清楚。比如“不能在子线程更新UI”这句话很多人背得滚瓜烂熟但题目问为什么不能或者问是否所有情况都一定崩溃就有人答不上来。实际上这是ViewRootImpl的checkThread机制在起作用而且View没有附加到Window之前子线程更新UI不一定立刻报错。这种边界意识才是客观题真正要选拔的东西。2. 四大组件几乎每道题都在挖坑2.1 Activity生命周期与启动模式Activity是Android笔试的常青树这套题里至少有四分之一的题目和它有关。生命周期题最经典的是“Activity A启动B两个Activity的回调顺序”。正确答案是A.onPause - B.onCreate - B.onStart - B.onResume - A.onStop。注意B.onResume执行完之后A.onStop才执行不是A先完全停下来再创建B。我第一次做这道题就选错了因为直觉上“A被盖住应该先onStop”才合理但系统的窗口切换顺序决定了必须先让旧Activity暂停才能给新Activity让出焦点。启动模式也是必考singleTop、singleTask、singleInstance这几个概念看起来简单组合起来就很容易错。举个例子题目问如果一个singleTask的Activity指定了和默认不同的taskAffinity并且已经存在一个对应Affinity的任务栈那么再次启动它会怎么处理答案是复用对应任务栈并把栈内位于它之上的Activity全部出栈同时回调onNewIntent。有人会把singleTask和singleInstance搞混以为它会单独创建一个新栈。实际上singleTask只是“尽量复用taskAffinity对应的任务栈”不是一定新建栈。而singleInstance才是更强硬地要求自己独立存在于一个任务栈中。这里推荐大家亲手用adb shell dumpsys activity activities看一遍真实栈信息比看十篇文章都管用。2.2 Service、广播与ContentProviderService在2018年的笔试题里还是重头戏。主要考两种启动方式的生命周期startService启动后onCreate - onStartCommand - onDestroybindService启动后onCreate - onBind - onUnbind - onDestroy。难点在组合先startService再bindService最后只unbindService不stopServiceService会不会销毁答案是不会必须start和stop配对、bind和unbind配对所有条件都满足后Service才会走onDestroy。这种题对应的真实场景就是音乐播放器、定位服务这类长时间运行的后台任务如果你解绑了但忘记stopService一直在后台跑很容易被系统认为耗电。广播这部分的题也很有时代特征。Android 8.0开始限制隐式广播很多需要在Manifest里静态注册的广播都失效了只有特定系统广播还能用。所以2018年春招的题目正好卡在这个时间点上会问“下列哪个广播在8.0之后仍能静态注册监听”比如屏幕亮灭、开机广播、时区变化等。到了今天这道题还要再加上Android 14对动态广播和导出行为的限制。ContentProvider考点更多集中在跨进程数据共享上比如CRUD方法运行在哪个线程。很多人不知道ContentProvider的增删改查并不自动跑在子线程而是运行在Binder线程池中。也就是说如果你的Provider实现里直接做耗时操作会阻塞调用方的Binder线程进而影响调用方所在进程。这个点放到现在做跨模块SDK设计时依然重要。2.3 这里容易错的三个判断多选里经常出现一些“看起来对其实错”的选项我把印象深的三个写出来第一“Service默认运行在主线程所以不能在Service里做耗时操作。”这个说法是对的。Service本身不是线程它默认跑在所在进程的主线程。很多人会误以为Service自带子线程这是初学者最容易踩的坑。第二“BroadcastReceiver的onReceive执行完后系统就认为广播处理结束。”这句话只说对了一半。如果onReceive里没有调用goAsync那么返回后系统确实会回收但如果你调用了goAsync并在异步任务完成后调用finish系统会等finish后才判定结束。所以更准确的说法是“默认同步执行结束后即释放但可以通过goAsync延长处理时间”。第三“Activity被销毁后通过bindService建立的连接会自动解除。”这个说法错误。Activity销毁不等于自动解绑必须主动调用unbindService否则会抛LeakedServiceConnection异常甚至造成Service无法停止。很多内存泄漏问题就是这么来的。3. Handler与多线程客观题里的“分水岭”3.1 Handler机制常考细节Handler机制是Android笔试里区分度高的一组题。它本身不复杂就是一个生产者-消费者模型Handler发送Message到MessageQueueLooper不断从队列中取出消息并交给target分发。但客观题很喜欢在细节上做文章比如“Handler.postDelayed一定能保证延迟指定时间后执行吗”答案是不能。postDelayed只是把消息按时间排序插入MessageQueue如果当前队列前面有耗时任务或者主线程正在执行长操作实际执行时间就会晚于预期。明白了这个你也就理解了为什么很多人都说Handler不适合做精确计时。还有一道印象深刻的题在子线程中new Handler()会发生什么答案是抛RuntimeException因为子线程默认没有Looper必须先调用Looper.prepare()创建Looper再调用Looper.loop()启动消息循环。这是Handler机制的基本规则放到现在看依然没变。另一个常考点是HandlerThread它的本质就是一个内部有Looper的Thread适合处理串行任务比如顺序读写数据库、后台轮询等。如果面试题里出现“哪个类的内部包含Looper且可以循环处理消息”答案就是HandlerThread。3.2 AsyncTask与线程池2018年的题大量涉及AsyncTask因为那时候它还是官方推荐的异步组件但很多人不知道它的坑。AsyncTask有三个泛型参数Params、Progress、Result对应的是入参、进度、返回值。它内部的doInBackground跑在子线程onPreExecute、onProgressUpdate、onPostExecute都跑在主线程。笔试题常问AsyncTask在Activity旋转后为什么会内存泄漏因为AsyncTask作为非静态内部类持有外部Activity的引用如果任务还没执行完Activity被销毁了这个引用就断不掉。到了今天AsyncTask已经被官方废弃但这类题的底层逻辑没有变考察的其实是“异步任务与组件生命周期不一致”的问题换成协程、RxJava也一样成立。线程池也是必考尤其喜欢问ThreadPoolExecutor的参数含义和处理流程。核心问题是当线程池中的线程数达到corePoolSize后新任务会怎么处理正确答案是先把任务放入阻塞队列而不是立即创建新线程只有当队列也满了才会继续创建线程直到线程数达到maximumPoolSize如果线程数已经到上限队列也满了就触发拒绝策略。这个流程很多人在背参数时容易搞反但一旦理解了“核心线程优先复用、队列兜底、线程扩容、拒绝兜底”的设计思路就非常顺。对比来说CachedThreadPool的特点是核心线程数为0、最大线程数是Integer.MAX_VALUE、队列是SynchronousQueue所以它适合大量短小任务但不适合承载突发峰值否则线程数会失控。3.3 Binder与跨进程通信Binder是Android底层通信的灵魂也是客观题里比较容易死记硬背的部分。常考的点有几个第一一次Binder调用发生了几次数据拷贝传统IPC通常是两次拷贝Binder只拷贝一次因为内核采用内存映射方式把数据映射到接收方进程。第二为什么选Binder而不是Linux自带的管道、共享内存、Socket常见的标准答案是安全、性能、易用。Binder为每个进程分配UID内核层可以校验身份所以比传统IPC更安全同时一次拷贝在性能上也有优势。第三AIDL中in、out、inout分别表示什么方向这个要熟到条件反射in表示只能从调用方传入服务端out表示服务端能修改并返回给调用方inout表示双向传递。实际项目里尽量少用inout因为它的实现开销更高还会多出写回操作。还有一道多选让选“哪些属于Binder驱动的场景”Activity启动、Service绑定、ContentProvider访问、BroadcastReceiver的注册与发送都和Binder有关。本质上只要是跨进程或跨系统服务通信基本都走Binder。搞懂这一点你再看四大组件时就会有“原来是同一套底层机制”的豁然开朗。4. 内存、卡顿与ANR性能题是拉分项4.1 内存泄漏与OOM性能题里内存泄漏是高频考点。典型的问法是“下列哪些场景会导致内存泄漏”然后给出一组代码场景。我印象最深的一道多选四个选项分别是非静态内部类Handler持有Activity引用、单例持有Context、在onDestroy中正确反注册广播、静态变量引用View。正确答案是除了“反注册广播”之外其他三个都会泄漏。这里要注意静态变量引用View是很隐蔽的泄漏因为View本身持有Context如果View被静态变量长期持有等于把整个Activity上下文都钉在内存里Activity销毁后无法回收。为什么Handler会导致泄漏因为Handler发送的Message会持有Handler引用Handler作为非静态内部类又持有外部Activity当Message还在队列里等待处理时这条引用链就断不开。解决思路是在onDestroy里removeCallbacksAndMessages(null)并把内部类定义为静态类用WeakReference持有Activity。这类题在2018年很常见放到今天依然是面试官喜欢问的“基本功”。OOM则更偏向结果判断比如“一下哪种操作最容易直接导致OOM”一次性加载超清大图、在循环里拼接String、线程池无限创建线程、大数组缓存。看起来是四个独立问题本质都是短时间内消耗大量内存或资源。4.2 布局渲染与卡顿优化布局优化的客观题一般不会让你写代码而是考include、merge、ViewStub三者的使用场景。include用于复用布局merge用于减少嵌套层级通常跟在include后面使用或者作为根布局是FrameLayout且不需要添加额外布局属性时的替代ViewStub用于延迟加载适合那些一开始不需要显示、后续可能出现的View。题里可能会说“ViewStub一旦visible就会被inflate而且之后不能再重复设置可见性”这个细节很容易错。因为ViewStub执行inflate后自身会被替换成真正的目标布局原来的ViewStub节点其实已经不在View树里了。卡顿方面客观题更愿意考“掉帧”的核心逻辑。屏幕上每一帧都需要在16.6ms内完成测量、布局、绘制并把缓冲区提交给系统只要主线程中任何一个阶段超时就会掉帧。常见的卡顿原因包括布局层级过深、主线程做磁盘IO、频繁GC、动画执行期间同时加载资源等。有些人会把“布局层级深”和“过度绘制”混在一起其实前者增加测量和布局时间后者增加绘制时间两者都会引起卡顿但优化手段不同。减少层级用ConstraintLayout或merge减少过度绘制用clipRect、去掉重复背景。4.3 ANR的判定和排查ANR题几乎是每套Android笔试题的标配。常考的是触发场景输入事件5秒内没被处理完BroadcastReceiver前台10秒/后台60秒没执行完Service前台20秒/后台200秒没执行完ContentProvider某个操作超时也可能触发ANR。注意这里的超时时间在不同版本里有微调但笔试里的经典答案基本不会变。题目还会问“ANR发生后去哪里查日志”。正确答案是/data/anr/traces.txt或者通过adb shell dumpsys activity processes查看CPU使用率和线程栈。现在用Android Studio的Logcat也能直接看到ANR的“ANR in XXX”日志配合CPU Profiler定位主线程卡点更直观。这类题表面上考概念实际上考你有没有真正处理过线上ANR。我记得自己第一次定位ANR就是因为在BroadcastReceiver里直接做了网络请求当时数据量不大没在意结果上线后低端机频繁报ANR最后硬着头皮看了traces.txt才定位到问题。所以客观题里反复考的“主线程不能做耗时操作”从来不是一句空话。5. 系统底层原理与版本适配要不要背源码5.1 AMS/ActivityThread启动流程这套题里有一类让很多人头疼的源码题比如“Activity从startActivity到onCreate经历的完整过程是什么”。通常需要列出startActivity调用Instrumentation.execStartActivity再通过Binder请求AMSAMS完成任务栈校验、进程是否存在检查如果App进程不存在先由Zygote fork新进程并创建ActivityThread然后由ApplicationThread回调到App进程最后通过H Handler把消息分发给ActivityThread最终执行Activity的onCreate。这个流程里每一个参与者都有自己明确的职责Instrumentation是监控入口AMS是系统侧调度中心ActivityThread是App进程的入口ApplicationThread是双方通信的Binder实现。这种题要不要死背我的建议是不要背而是画一条“调用链”把“系统侧”和“App侧”分开。系统侧AMS、ActivityTaskManager、ZygoteApp侧ActivityThread、ApplicationThread、H。只要抓住“所有跨进程交互都走Binder所有进程内回调都走Handler”这条主线流程就能顺下来。笔试如果考到一般是选择“下列哪一步发生在App进程”之类的判断题能分清两侧就够用。5.2 类加载与进程优先级类加载这块常考的是双亲委派模型和Android特有的PathClassLoader、DexClassLoader。题目会问双亲委派的好处是什么两个核心答案避免核心类被重复加载保证同一个类在全系统中只有一份防止自定义类覆盖系统核心类。Android里PathClassLoader通常用来加载系统已经安装的APK中的类DexClassLoader可以从外部dex或APK中加载类这也是热修复、插件化的基础。2018年插件化正火所以这类题出现频率不低。进程优先级也是客观题爱考的“送分题”但很多人会记混。Android进程优先级从高到低是前台进程、可见进程、服务进程、后台进程、空进程。题目会问“以下哪种进程最不容易被系统杀死”或者给场景判断属于哪个级别。比如正在显示Dialog的Activity属于前台进程被Dialog遮住但仍可见的属于可见进程只有后台不可见才属于后台进程。到了Android 8.0之后后台执行限制越来越严格纯粹靠后台Service保活已经很难所以现在的面试更偏向考“为什么不能保活”而不是“怎么做保活”。5.3 Android 8.0版本适配考点因为是2018年春招Android 8.0的自然成为当年的新考点。通知渠道就是高频题8.0之后所有通知必须指定NotificationChannel否则通知不显示。还有后台执行限制当应用处于后台时系统会限制其创建后台Service此时调用startService会抛IllegalStateException必须改用startForegroundService并在5秒内调用startForeground。这个点直到今天还在用只是又叠加了Android 12的ForegroundServiceStartNotAllowedException等新限制。另一个常考的是隐式广播限制。Android 8.0开始绝大多数隐式广播都不允许在Manifest中静态注册只有系统发出的特定广播仍然允许。题目一般会问“下列哪类广播不受影响”答案通常是开机广播、屏幕状态、电量变化这类系统级广播。放到今天Android 14又进一步收紧动态广播的exported声明但核心思想没变减少后台被随意唤起保护用户电量、流量和隐私。做客观题时如果能顺着版本演进的“主线”去理解就不会觉得这些适配题只是单纯考记忆。6. 这套题放到今天还能怎么用6.1 哪些知识点还通用哪些已经过时如果把2018年的客观题原封不动拿来做今天的笔试题大概有六成基础题仍然适用比如Activity启动模式、Handler机制、Binder、线程池、内存泄漏、布局优化。但也有一些内容已经过时AsyncTask被废弃处理异步任务的首选方案变成了协程或rx进程保活的黑科技基本失效国内外ROM对后台限制非常严格Android版本适配答案也变了8.0不是边界12、13、14的隐私和安全变更才是新重点。所以看这套题更推荐把它当成“查漏清单”而不是“真题库”。看到一题不会先不要去背答案而是找到对应的官方文档或系统源码把概念背后的机制弄懂。比如看到Activity启动模式就顺手把dumpsys activity activities用一遍看到Handler就写一个HandlerThread小demo验证消息顺序。笔试没过多久就会忘但亲手验证过的机制会留很久。6.2 用“真题复盘”的方法刷题我后来带新人时会让他们用三种方式刷这类客观题。第一遍直接做限定40分钟模拟真实笔试节奏第二遍合上答案只给选项不给正确项把每个选项改成判断题解释为什么对、为什么错第三遍把题目和项目经验关联起来比如“我的App里哪里用到了singleTask”“这个内存泄漏场景我在线上见过没有”。很多人在第二遍会卡住因为解释“为什么错”比判断“对不对”难得多而笔试和面试真正考察的恰恰是后者。我还习惯把错题整理成一张表左边是题目中间是正确答案和一句话解释右边是对应的源码路径或官方文档。这样复盘时扫一眼就能回忆起完整链路。比如“startService后不调用stopService进程会不会被杀”这类题表面是生命周期实际上是进程优先级与后台限制的交叉点把它单独记一列效率会高很多。6.3 出题人/面试官视角的建议后来我参与过一些校招笔试出题也面过不少候选人再回头看这套360的客观题最大的感受是一套好的客观题不是要让所有人都会做而是要让“真懂”和“会背”之间拉开差距。所以如果你是在准备笔试不要只刷题库要多问自己“为什么”。比如同样考Binder为什么选它而不是共享内存同样考ANR为什么超时阈值是5秒、10秒、20秒而不是任意数字这些“为什么”背后才是Android开发者的核心竞争力。我现在面试时也习惯用几个经典客观题当开场比如“Activity A启动BA会先执行onPause还是B先执行onResume”并不是真要用这个题筛人而是想看候选人有没有把Android当成一套有运行逻辑的系统来理解而不是一堆API的堆砌。一个人如果能从场景、机制、源码三个层面把一道选择题讲清楚那他的基础一定不差。最后再分享一个小技巧碰到“多选拿不准”的题可以先在草稿纸上把每个选项单独写成判断题分别写“成立条件”和“不成立的反例”。反例越具体说明你对这个考点的理解越扎实。当年我刷完这套题后把每个错题都补了一个反例后来再遇到同类题基本不会再错。这套复盘方法我到现在都还在用。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门