ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android笔试核心考点:从Handler到自定义View的进阶指南

Android笔试核心考点:从Handler到自定义View的进阶指南 一提到“货拉拉2018秋招Android工程师笔试题卷三B”这个标题很多人的第一反应是去搜题目原文、背答案但作为经历过多个招聘季、也参加过不少笔试出题工作的老Android开发我想先泼一盆冷水这套题目本身的价值远不如它背后暴露的考点逻辑值钱。2018年秋招是个分水岭那时候移动互联网红利还在Android岗位的笔试已经从单纯考API背诵转向了对原理理解、源码追踪和工程思维的全面考察。说白了面试官想看到的不是一个会写if-else的人而是一个遇到问题能顺着代码往下追、能说清楚“为什么”的人。这篇文章我会结合Android面试题里最常出现的几个核心模块——组件、消息机制、并发、自定义View、性能优化逐个拆解这套笔试题以及同类题目背后的考察重点和作答思路。无论你是正在准备校招的应届生还是想跳槽的初中级开发按这条线去准备比盲目刷题有效得多。1. 笔试题整体设计与考点分布1.1 一道笔试题想筛选什么样的人2018年是一个很有意思的时间点。那一年Kotlin已经宣布成为Android官方语言Google对Android Studio的投入明显加大组件化、热修复、插件化这些方案在各大厂已经落地成型。在这个背景下笔试题目已经不满足于“四大组件是什么”“Activity生命周期有哪些”这种送分题了而是开始往“组合拳”方向出一个场景题里同时涉及生命周期、内存、异步和UI刷新看你能否把零散知识点串成一条完整链路。我记得当时很多同学拿到卷子第一反应是“这题我会”落笔却发现每个选项都模棱两可。原因很简单题目考察的不是记忆而是辨析。比如同样的场景Activity切到后台再切回来onSaveInstanceState的调用时机和恢复时机分别是什么onRestart和onStart到底谁先谁后这些细节在开发中平时用不到但面试官就是要靠它们过滤掉“只看过入门教程”的简历。所以这套笔试题的本质是通过有限的选择题和简答题快速判断候选人的知识体系是否成网。知识点之间是孤立的还是联通的决定了一个人能不能独立扛起模块开发。理解了这一点你就知道备考的重点不是背题而是建立知识之间的因果链。1.2 高频考点分布与出题权重结合我对2018年前后多家互联网公司Android笔试题的观察再对照货拉拉这套卷三B的出题风格高频考点大致可以分为四层。第一层是基础组件与系统机制占比最高大概在40%左右。这一层覆盖Activity启动模式、Fragment生命周期、Service两种启动方式、BroadcastReceiver的注册方式、ContentProvider的底层原理。为什么会占这么高因为这是Android开发的底盘任何项目都绕不开。第二层是异步与消息机制占比大概20%。Handler、Looper、MessageQueue三者之间的关系是必考项延伸出去就是ThreadLocal的作用、IdleHandler的执行时机、同步屏障的原理。第三层是UI与自定义View占比15%到20%。考察点集中在measure/layout/draw流程、事件分发机制、自定义属性定义偶尔会结合invalidate和requestLayout的异同来出题。第四层是性能优化与内存管理占比15%左右。包括ANR的触发条件、常见内存泄漏场景、Bitmap内存计算、布局层级优化这些点。还有一个容易被忽略的隐藏考点代码阅读能力。卷子里经常给一段真实代码问你输出结果或者指出问题。这层考察的是你能不能读懂别人的代码以及有没有代码洁癖。我后面会专门展开讲这部分。2. 核心知识点精讲与高分作答逻辑2.1 Activity启动模式别只会背四种模式Activity的四种启动模式——standard、singleTop、singleTask、singleInstance——几乎是Android笔试题的必考题但大多数人的准备方式只是背下概念这恰恰是丢分点。货拉拉这套卷子在启动模式上至少出了两道题一道是考返回栈的变化一道是结合Intent Flag出组合题。先说standard模式。它是最普通的一种每次启动Activity都会创建新的实例放进启动它的那个任务栈里。注意这里有个细节很多人不知道standard模式的Activity在启动时会调用被启动者的onCreate而不会复用已有实例哪怕栈顶就是它自己。这就引出一个高频场景题“如果从Activity A跳转到A连续跳三次按下返回键会发生什么”答案是三次onCreate三次压栈按三次返回依次出栈。如果你答成“复用栈顶实例”说明你对standard模式的理解是错的。再说singleTop。它和standard的区别在于如果栈顶已经存在该Activity的实例则复用栈顶实例并回调它的onNewIntent方法如果不在栈顶行为与standard一致。这里考试喜欢考一个陷阱场景从A跳BsingleTop再从B跳A此时A在栈顶吗不在因为栈顶是B。所以即便A设置成singleTop也不会复用而是再创建一个新的A实例。很多人在这里栽跟头就是因为只记住了“栈顶复用”这个结论没有真正理解“栈顶”是动态变化的状态。singleTask和singleInstance是重灾区。singleTask的核心是“栈内复用”目标任务栈里如果已有该Activity的实例就把它上面的所有Activity全部出栈复用这个实例并回调onNewIntent。这个特性因为会影响返回栈里的其他Activity所以经常被用来做App主页但使用时要非常小心因为它会破坏用户预期的返回栈结构。singleInstance则更极端它所在的Activity会独占一个任务栈这个栈里只有它一个实例。这种模式极少用一般只在系统来电界面、闹钟提醒这类需要全局唯一的场景里出现。笔试如果考singleInstance几乎必问“此时按Home键再点击应用图标会发生什么”答案是会创建一个新的任务栈并不一定回到之前的singleInstance页面。结合Intent Flag出题时核心要记住两个FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP。前者和singleTask的效果类似但更灵活它可以跨Task启动后者则会把目标Activity之上的Activity全部清掉。两者结合使用时效果就是singleTask的完整版。很多笔试答案里会把这两种Flag与launchMode混为一谈这是最典型的混淆点。高分作答思路不是背四种模式的特性而是能讲清楚两个维度实例是否复用、任务栈是否变化。只要把这两个维度想清楚任何组合题都能迎刃而解。2.2 Handler消息机制从三个类讲到同步屏障Handler机制是Android面试题里频率最高、也最能拉开分数差距的题目。2018年那会儿很多背题党能说出“Handler用来线程间通信、Looper负责消息循环、MessageQueue存储消息”这种话但面试官紧接着问一句“Looper.loop()为什么不会阻塞主线程”就懵了。我把这套机制拆成一条完整的链路来讲。Handler发送消息时会通过enqueueMessage把Message插入到MessageQueue中。MessageQueue的数据结构不是队列而是一个按时间排序的单链表——按消息的触发时间when字段从小到大排列。这里有个容易误解的点MessageQueue的“时间排序”不是按入队顺序而是按延迟执行时间。如果你sendMessageDelayed发了一个延迟10秒的消息它会被排到当前时刻附近的消息之后而不是插到队尾。主线程的Looper通过loop()方法不断循环取出下一条消息如果消息时间还没到就调用nativePollOnce阻塞等待如果时间到了就分发消息给对应的Handler去处理。这个nativePollOnce是理解“为什么不阻塞主线程”的关键。它底层用到的是Linux的epoll机制在等待期间主线程其实是休眠状态不消耗CPU一旦有消息到达或者时间到epoll会立刻唤醒线程。这就像你在食堂排队打饭如果前面的人还没到你可以先去旁边长椅上坐着休息轮到你的时候再起身而不是站在窗口前干等。这里还有一个绝大多数人没搞清的概念ThreadLocal的作用。Looper是通过ThreadLocal来保证每个线程最多只有一个实例的。ThreadLocal的原理是每个Thread内部维护一个ThreadLocalMap以ThreadLocal对象为key。所以你在主线程调用Looper.getMainLooper()拿到的是主线程的Looper在子线程new Handler()则需要先调用Looper.prepare()创建该线程的Looper。很多人写子线程Handler时报错“Cant create handler inside thread that has not called Looper.prepare()”就是没理解这条生态链。2018年的笔试题里同步屏障是区分度最高的点。同步屏障消息的本质是一个target为null的特殊Message它的作用是让MessageQueue在处理消息时优先跳过所有同步消息只执行异步消息。这个机制的典型应用场景是UI渲染在需要立即刷新界面前插入一个同步屏障使VSync消息可以优先被处理等渲染完成后移除屏障。能主动把这个机制写在简答题里哪怕答案不完整面试官也会对你另眼相看。除了原理本身笔试还喜欢考内存泄漏问题。非静态内部类Handler会隐式持有外部Activity的引用如果Activity已经销毁但消息队列里还有待处理的Message就会导致Activity无法被GC回收。标准解法是使用静态内部类WeakReference并在onDestroy时removeCallbacksAndMessages(null)。这个点几乎年年考属于白给分但每年都有很多人丢分。2.3 多线程与并发线程池参数不是背出来的2018年的Android笔试题已经开始大量涉及线程池和并发编程了。原因很简单——项目里的异步任务越来越重面试官需要确认你写并发代码时不会把线上搞挂。线程池的核心考点是ThreadPoolExecutor的七个参数但很少人能把它们之间的计算逻辑讲清楚。corePoolSize和maximumPoolSize是理解线程池的入口。当一个任务提交到线程池时执行逻辑是如果当前工作线程数小于corePoolSize就新建线程如果大于corePoolSize但小于maximumPoolSize且阻塞队列未满就把任务放进队列如果队列也满了才继续创建线程到maximumPoolSize如果线程数已经到达maximumPoolSize且队列满了就执行拒绝策略。这套阈值逻辑用生活类比来解释一家餐厅有5个固定厨师corePool顾客多的时候先让5个厨师忙起来还是忙不过来就把菜单排到等候区workQueue等候区也排满了再招临时厨师新增线程最多到maximum临时厨师也忙不过来就只能拒客了RejectedExecutionHandler。笔试常考的一个陷阱是corePoolSize0时的行为。当核心线程数为0时新任务到达后会先尝试放入队列而不是先创建线程。只有当队列满了才会创建非核心线程去处理。这会导致一个现象任务延迟升高、吞吐量下降但线程数量能得到控制。如果你在项目里遇到“线程池创建了但任务一直不执行”的问题八成是这个参数配置的问题。同样的知识点在不同题目里的变体是CallerRunsPolicy拒绝策略。它不是在调用线程里抛异常而是把被拒绝的任务直接交给调用线程去执行。这个策略的好处是既能降速又能保证任务不丢失。笔试可能会问“哪种拒绝策略最不可能丢任务”答案就是CallerRunsPolicy。但它的代价是占用调用线程的时间如果用主线程提交任务就可能卡UI。我建议大家准备这部分时不要只看参数含义要能把线程池的执行流程在纸上画出来并说明每个分支发生在什么条件下。笔试题里经常给出一段代码让你判断线程池内线程数量随时间的变化曲线这种题只要掌握流程图就没问题。3. 从笔试题到实战解题思路与避坑经验3.1 拿到卷子后的审题顺序笔试的时间通常是一个半小时到两个小时看起来充裕但题量往往很大尤其是选择题每道题都要认真推演时间并不宽裕。我的答题策略是先做简答题和场景题再回过头做选择题。原因是简答题只要踩到知识点拿分相对容易但思考时间不定选择题虽然看起来快但很容易在模糊的记忆里反复横跳一纠结就是好几分钟。先把容易拿的分拿到手心里不慌后面做选择题时状态会好很多。审题环节有一个很大的坑选项里的绝对化表述。比如“一定会回调onDestroy”“不会触发onNewIntent”这种带绝对词的选项往往是错的因为Android的生命周期和回调受太多系统状态影响。反过来带“可能”“在特定条件下”这种留了余地的大概率是正确的。这不是玄学而是出题人为了制造模糊刻意在选项里埋的雷。看到绝对化表述先给你自己提个醒再回题干里找有没有例外条件。另一个坑是题干里的时间定语。2018年那会儿Android 9已经发布但很多老项目还在用targetSdkVersion 26甚至更低。同一个API在不同SDK版本下行为不一致的情况非常常见比如运行时权限、后台启动限制、电池优化这些。出题人如果写了“targetSdkVersion 26”你要按26的规则去作答而不是拿最新版本的规则来套。答题时圈出题干里的版本号、机型、厂商这些关键词能帮你避开至少三分之一的坑。3.2 从常见错误答案看知识薄弱点我接触过很多候选人也在面试后复盘过他们的笔试答案发现错误答案也分层次。最基础的一类是概念张冠李戴。比如把onPause和onStop的调用时机搞混onPause是Activity部分不可见时调用比如弹了一个Dialog或者来了一个半透明ActivityonStop是Activity完全不可见时调用。很多人写场景题时判断不出哪个阶段走了onPause、哪个阶段走了onStop本质是对“可见性”的理解不够深入。第二类是知其然而不知其所以然。典型的例子就是“为什么不能在子线程更新UI”。很多人能背出答案但说不出根本原因。其实本质是UI控件的线程安全性问题——Android的UI访问没有加锁如果在多线程里同时操作View就可能导致状态不一致。为了保证简单高效Android规定了只有主线程能修改UI检测机制就是ViewRootImpl里的checkThread方法。如果笔试的简答题问到这个你把这个机制写出来比单纯背结论强很多。第三类是缺少链路思维。比如问“点击一个Button到界面更新中间发生了什么”。这个问题的完整链路是触摸事件由InputManager读取通过WindowInputEventReceiver传递到ViewRootImpl再经过View的事件分发机制dispatchTouchEvent-onTouchEvent到达Button触发onClick后执行更新逻辑requestLayout触发measure/layoutinvalidate触发draw最后通过Choreographer同步到垂直信号完成渲染。能把这条链路串起来说明你对Android系统的理解是体系化的。答不完整也不可怕但至少要把事件分发、UI刷新这两个核心环节写清楚。3.3 实操心得如何高效准备这一类的笔试针对这类考察面广、深度也不浅的笔试我不推荐刷题海。更高效的路线是先搭知识框架再往框架里填细节。具体来说可以按照“组件机制、异步与并发、UI绘制、性能优化、网络与存储”这五大模块每个模块梳理出三条主线这条线解决什么问题、核心类之间的调用关系、经典使用场景是什么。框架搭好之后再针对每一条主线去看系统源码验证自己的理解。比如消息机制这一块你不需要读完所有源码只需要看三个方法Handler.enqueueMessage、MessageQueue.next、Looper.loop。看完这三个方法的实现你对消息机制的整个运行流程会有脱胎换骨的理解。源码阅读不要求逐行看明白像nativePollOnce这种底层实现知道是epoll阻塞就够了关键是把Java层的调用逻辑串通。另外强烈建议在准备笔试的同时开一个Demo工程把理解变成代码。像Handler同步屏障、线程池拒绝策略、Activity在不同启动模式下的返回栈变化这些都是可以写小Demo验证的。验证一次比背十遍强。而且面试时如果被追问你能说“我自己写Demo验证过这个场景”面试官的好感度会直线上升。4. 高频易错点速查与面试官隐藏意图4.1 易混淆知识点速查表每年笔试题都有那么几个高频混淆点我把它们整理成一张表方便你考前快速过一遍。对比项核心区别典型坑点onPause / onStoponPause是部分不可见onStop是完全不可见弹Dialog只走onPauseonSaveInstanceState / onRestoreInstanceState前者在异常销毁前调用后者在重建时恢复状态按Home键也会触发保存但不一定触发恢复invalidate / requestLayoutinvalidate触发drawrequestLayout触发measurelayout改变尺寸必须requestLayout否则只重绘Handler.sendMessage / postpost最终也会创建Message只是把Runnable包进callback两者本质一样post只是语法糖Serializable / ParcelableParcelable专为Android设计效率更高Intent传对象只能用Parcelable或SerializableANR / CrashANR是主线程超时无响应Crash是异常崩溃产生原因和排查思路完全不同Activity的onNewIntent触发条件只有reuse实例时才调用不是所有启动模式都会触发这些对比项在选择题里出现的频率极高。考前把这张表过一遍能帮你稳定住大多数基础题的得分。4.2 从笔试题看到面试官的隐藏意图聪明人做笔试题不只是为了得分还会去揣摩出题人想考察什么能力。我以“自定义View的measure过程”为例这道题表面上是考知识点实际上有三个隐藏考察点。第一个考察点是工程意识。面试官想知道你在自定义View时有没有认真处理wrap_content和padding这两个坑。初创者在自定义View时最容易犯的错就是只处理EXACTLY模式没有写wrap_content的默认逻辑结果在XML里设了wrap_content却得到和match_parent一样的效果。第二个考察点是边界处理能力。有没有考虑过measureSpec的UNSPECIFIED模式比如ScrollView里的子View高度测量就会用到这个模式。第三个考察点是阅读系统源码的意愿。如果你能说出View的onMeasure默认实现和setMeasuredDimension的关系说你读过源码面试官是会放在心里的。再比如笔试题里如果考了ContentProvider的权限设置一般不是真的让你当系统级应用开发者而是想考察你对敏感数据保护的理解。2018年后Android对权限的控制越来越严从运行时权限到分区存储都是数据安全方向的延伸。回答这类问题能让面试官看出你平时写代码有没有安全意识。4.3 笔试通过后的下一站技术面的准备思路笔试只是第一关。比试卷本身更有价值的是它帮你完成的自我诊断。拿到笔试结果后不管过没过都建议把错题整理一遍分析错误类型是概念模糊、经验缺失还是对源码理解不够。然后针对薄弱点去补强这比漫无目的地刷题效率高得多。如果笔试通过了接下来就是技术面。技术面问的问题往往是笔试的延伸。比如笔试考了“MessageQueue的next方法如何阻塞”面试时可能会追问“IdleHandler是干什么的”“什么时候会执行”。建议你在准备笔试时就对每个知识点主动做一次追问向上问一层的设计原因向下问一层的代码实现。从我这些年带新人的经验来看能把笔试题目讲透的人工作中写代码通常也有章法。因为Android开发的本质就是不断在系统机制和业务需求之间做权衡。对系统理解越深你就越能在产品需求里找到最优方案。这套知识体系值得花时间慢慢沉淀。
返回列表