1. 这套卷子到底在筛什么人
先说说我看到这份“小红书2020校招Android方向笔试题卷一”时的第一反应。很多同学拿到题就急着刷答案,觉得笔试就是换个地方考八股文,背一背Android生命周期、四大组件就能过。但如果你真在2020年投过小红书的校招,或者后来研究过这套题,你会发现它根本不是单纯考记忆,而是在筛选一类人:具备工程直觉、看过系统源码、能理解技术选型背后代价的候选人。
2020年是一个挺特殊的节点。那时候Kotlin已经转正成为Android官方推荐语言,Jetpack组件基本成熟,协程正在大规模普及,但很多学校的课程还在教Java和Eclipse。小红书作为内容社区产品,DAU增长快、业务复杂度高,客户端团队需要的不只是会写页面的人,而是能搞定启动优化、列表流畅度、包体积治理、崩溃排查这些实打实问题的工程师。所以这套笔试题的倾向性非常明显:Java/Kotlin基础、Android核心机制、性能优化思路、数据结构和算法,这些不是孤立的考点,而是对应着实际业务中每天都会遇到的真实场景。
对考生来说,你得先搞清楚一个底层逻辑:笔试不是期末考试,它的目的不是看你“会多少”,而是看你在有限时间内能“想多深”。同一道Handler题,有人写“Handler用于线程间通信”就交卷了,有人能从Looper.prepare、MessageQueue.next的阻塞唤醒机制、同步屏障、IdleHandler一路聊到Choreographer。这两种答案在阅卷人眼里的分量是完全不同的。
这篇文章我就用这套卷子作为引子,把Android校招笔试中最常出现的几类题目拆开揉碎,讲清楚每类题背后的出题意图、答题思路和容易踩的坑。不管你是准备校招的应届生,还是想系统补一补Android基础的开发者,这篇内容应该都能给你一些参考。
2. 基础题其实一点都不基础
2.1 Java和Kotlin考点背后的工程考量
先看最容易被低估的部分。卷子前半段通常会出现一些Java基础题,比如HashMap的实现原理、线程池参数含义、synchronized和volatile的区别、JVM内存区域的划分。很多同学觉得这些是老掉牙的东西,随便背背就行。但2020年的小红书Android笔试题,这类题表面考语法,实际考的是你在真实项目里能不能写出正确的并发代码、能不能定位线上OOM。
举个例子,线程池那道题经常这样考:给你一个ThreadPoolExecutor构造方法,里面corePoolSize、maximumPoolSize、keepAliveTime、workQueue、RejectedExecutionHandler这几个参数,问你线程池的执行流程是什么。如果你只是背了“先core后queue再max”这个口诀,面试官一追问“那workQueue用无界队列会有什么后果”“AbortPolicy和CallerRunsPolicy分别在什么场景下用”,你可能就答不上来了。
正确的思路是:核心线程被占满后,新任务进入队列;队列满后再创建非核心线程;非核心线程也满了就触发拒绝策略。无界队列的风险是任务可能无限积压,内存涨到OOM;CallerRunsPolicy让提交任务的线程自己跑这个任务,适合不想丢失任务的场景,但会阻塞提交线程。这些推导过程,比干巴巴背定义有说服力得多。
再补充一个细节:asyncTask后,Android主线程不能做耗时操作。所以当时项目里的做法是:所有网络请求走线程池,回调通过Handler切回主线程。线程池参数怎么定?CPU密集型用N+1,IO密集型用2N,这是基础;但真正的问题在于,如果你的线程池是全局单例还是业务单独建?全局单例的问题是某个业务的任务堆积会把线程池占满,影响其他业务;单独建的缺点是资源浪费。这个两难问题没有标准答案,但你能说出“按业务域隔离、共享线程池只跑短任务、线程数动态调整”这类思路,就比只会背公式的强。
Kotlin的题也一样。考协程作用域、Flow背压、Compose状态管理,后面还有Hot Flow和Cold Flow的区别。这些题不是要求你把源码背下来,而是看你有没有真正理解“协程是线程框架,不是语言特性”这句话的含义。协程的挂起不阻塞线程,但如果你在GlobalScope里乱开协程,照样会泄漏;Flow是冷流,每次collect都会重新执行生产者的代码,如果你把数据库查询放在Flow里但没加distinctUntilChanged,页面每次重组都会查一次库。这些细节才是出题人想看到的。
2.2 数据结构与算法题在筛什么
卷子里算法题的比重大概在20%到30%之间,题目一般是LeetCode中等难度偏下,比如LRU缓存、二叉树层序遍历、字符串反转、数组去重、手写单例模式。很多Android方向的同学看到这部分就头疼,觉得“我是做客户端的,刷算法有什么用”。其实算法题在客户端面试中的地位非常高,因为客户端开发的核心就是UI渲染、列表滑动、内存管理、图片加载,这些全是数据结构和算法的实际应用场景。
举个例子,LRU缓存是图片加载框架的标配。Glide的缓存策略就是LruCache+DiskLruCache的组合。面试官考你手写LRU,真实意图是看你知不知道LinkedHashMap的accessOrder参数有什么用、put和get的复杂度为什么是O(1)、线程安全怎么保证。如果你能答到“用LinkedHashMap构造方法传入accessOrder=true实现按访问排序,重写removeEldestEntry控制淘汰”,再补一句“多线程环境下可以用Collections.synchronizedMap包一层,但高并发场景建议用ConcurrentLinkedHashMap”这种工程细节,就是高分答案。
再比如二叉树题。客户端里视图树就是一棵View树,自定义ViewGroup的measure和layout过程就是DFS遍历的过程。你写一个树的前序遍历,其实是帮面试官确认你是否理解View层级结构的递归处理方式。
算法题这里给一个备考建议:不贪多,把高频题型吃透,包括链表反转、两数之和、二叉树遍历、LRU、快速排序、二分查找、动态规划入门。每道题都手写到自己能闭眼写出来的程度,然后一定要做复杂度分析,面试官问“为什么这题用HashMap不用数组”的时候,你要能说出两种方案在不同数据规模下的差异。
3. Android核心机制题:从背答案到讲原理
3.1 Handler、Looper、MessageQueue三件套
这套题里,Handler相关题目出现的概率几乎是百分之百,2020年那场也不例外。常见考法有两种:一种是直接问原理,另一种是给你一段代码让你说出执行顺序。
原理部分需要分四层来讲:
- Handler.sendMessage最终走到MessageQueue.enqueueMessage,按时间戳将消息插入到有序链表
- Looper.loop是阻塞循环,调用MessageQueue.next获取下一条消息,没有消息时进入epoll等待
- 消息被取出后,Looper把消息交给Handler的dispatchMessage处理
- 处理完消息后,回收消息到消息池,避免反复创建对象
注意最后一步很多人会漏,但这恰恰是性能类题目的加分项。Message.obtain()从全局消息池复用对象,能减少GC压力,这在列表快速滑动场景下非常重要。
给你一段代码的执行顺序那道题一般会这样设计:主线程Handler post一个Runnable到子线程的Handler,子线程Handler又把结果post回主线程。这个过程的难点是子线程的Looper需要自己创建和调用Looper.loop(),很多人会忘记HandlerThread的内部实现就是帮你做了这件事。
还有一类衍生题:在主线程postDelayed一个消息,delay的时间不准怎么办?答案是因为MessageQueue是按时间排序的,队头消息如果不阻塞,排在后面的消息可能提前执行;如果队头消息处理耗时长,后面所有消息都会被延迟。这就引出了同步屏障和异步消息的概念,Android系统在VSYNC信号来临时,会通过同步屏障优先处理异步的Choreographer消息,保证UI渲染不被业务消息阻塞。
我曾经面试过一个候选人,他知道同步屏障,也能说出SystemServer里用了很多异步消息,但当问到“自定义View的invalidate和requestLayout会不会产生同步消息”时卡住了。这道题的真实答案是:requestLayout会触发ViewRootImpl的scheduleTraversals,而scheduleTraversals内部通过Choreographer.postCallback投递一个异步消息,用于保证帧的连贯性。这道题答出来,基本说明你对渲染链路是真的有理解,而不是只看了几篇博客。
3.2 Activity启动模式和View绘制流程
Activity这部分,常规考点是四种启动模式、TaskAffinity、onNewIntent触发条件。2020年的题里还问了“A启动B,B的onCreate和onStart里分别做了什么事情,B能那么快显示出来吗”,这基本就是考AMS到ActivityThread的调用链。
要让这题答出新意,需要补充一个细节:B的onCreate里如果调用setContentView并开始inflate布局,这一过程是白屏阶段的一部分。系统在启动B的窗口时,会先根据B的theme创建一个StartingWindow,这个窗口的显示用的是系统进程的SurfaceFlinger,不依赖B的进程。所以如果你在B的theme里设置了windowBackground,这个背景会先展示,然后等B的onCreate执行完之后再替换成真正的contentView。这也是为什么很多应用冷启动会先白屏一下再进页面——你在onCreate里做的事情太重,StartingWindow等不到内容替换,就会显示空白背景。
View绘制流程也是高频考点。measure、layout、draw三步走,这个谁都知道,但真正拉开差距的是你对measureSpec的理解。MeasureSpec分为UNSPECIFIED、EXACTLY、AT_MOST三种模式,分别对应wrap_content、match_parent和确定值。自定义View时,onMeasure里如果不处理wrap_content,默认会和match_parent效果一样,因为父View传下来的AT_MOST模式,你不做处理就默认用父容器的剩余空间。
这里我补充一个实战案例。之前有一个需求是要做一个高度支持wrap_content的图片轮播控件,Banner高度根据图片比例自适应。直接在onMeasure里读取图片宽高比,然后计算期望高度并setMeasuredDimension。但很快发现一个问题:图片还没加载出来时,onMeasure已经执行了,此时拿不到图片尺寸,高度就会变成0。解决办法是注册图片加载回调,在图片解码完成后重新requestLayout。这个问题的本质是:View的测量时机和图片异步加载是冲突的,你需要在正确的时间点触发重新measure。能把这个案例聊清楚,比干背measure流程有说服力得多。
3.3 Binder机制和AMS、WMS这些系统服务
Binder机制是Android进阶绕不开的大山,也是2020年校招笔试中常见的一类题——往往不会直接问“Binder原理是什么”,而是在一道Activity启动流程题里,考察Binder的调用链和目标。
先建立最低限度的认知:Binder是Android的进程间通信机制,基于内核的Binder驱动,通过mmap实现一次拷贝,因此效率比传统Socket和管道高。四大组件之间的通信,进程间的Service绑定,系统服务的调用,底层全是Binder。
Binder题目常见的坑是概念混淆。比如有人把Binder的内存映射和NIO的mmap混为一谈,说“Binder就是mmap所以不用拷贝”。这个说法不准确:Binder仍然是先拷贝到内核空间,但接收方与内核通过mmap共享了同一块内存,所以省去了“从内核拷到用户空间”这一步,最终实现“一次拷贝”。面试官如果追问到这一层,很多人就会露馅。
AMS、WMS、PMS是三个最常被问到的系统服务。AMS管Activity栈和进程调度,WMS管窗口层级和输入事件分发,PMS管包安装和权限。比如Activity启动流程一题,要能说出“App进程里ActivityThread通过Binder调用AMS.startActivity,AMS处理完生命周期状态后,通过ApplicationThread这个Binder回调告诉App进程该建Activity了”。这个链条涉及两头Binder调用:App到AMS,以及AMS回App。
理解这些机制对日常开发有什么实际帮助?举一个例子:为什么Activity泄漏会导致内存泄漏,而单纯创建一个View不会?因为AMS为了恢复任务栈,持有Activity的引用;如果你在子线程里持有Activity的引用,就等于通过子线程->AMS->Activity这条链,把Activity泄漏了。这一类题考的是你对系统机制的理解能不能落到内存优化实操上。
4. 性能优化和网络题库:出题人最想看的工程思路
4.1 启动优化、内存优化、ANR排查
小红书这类内容社区产品,对客户端性能极其敏感。首页Feed流滑动掉帧、冷启动速度慢、图片内存占用高,这些都是直接影响用户体验的问题。所以笔试里性能优化题目绝不缺席。
启动优化这题最常见的问法是:“App冷启动做了什么,怎么优化”。答题框架可以这样拆:
冷启动链路:进程创建、Application.onCreate、MainActivity的onCreate和onResume、首帧渲染。优化方式分为三类:
- Application.attachBaseContext里避免做任何耗时操作,尤其不能引入不必要的内容提供器初始化
- 同步任务改成异步:比如SDK初始化放到子线程、延迟初始化放到首帧之后
- 用启动器框架管理任务的依赖关系:打印任务耗时,找瓶颈
面试官感兴趣的往往不是你能列出多少优化手段,而是你如何证明优化有效。这里必须讲“能测量才能优化”这个原则。用adb shell am start -W来测冷启动时间,拿到WaitTime和TotalTime;用systrace或Perfetto看主线程的执行片段,定位到底哪个方法占了最多时间。不要提没有任何数据支撑的“我感觉快了很多”。
内存优化题一般会从“OOM怎么排查”切入。我的答题思路是:
- 先区分是内存抖动、内存泄漏还是真实OOM
- 内存抖动看Memory Profiler,看分配频率是不是高频率;内存泄漏用LeakCanary,或者手动dump堆后分析引用链;真实OOM一般出现在图片加载大图、列表数据量过大、日志缓存积压等场景
- 图片内存占用:一张1000x1000的ARGB_8888图片占用约4MB,如果屏幕只显示300x300,这4MB就浪费了。用inSampleSize进行采样压缩,用inJustDecodeBounds获取原图尺寸再计算缩放比
这里最容易被忽略的是“图片的像素大小不等于文件大小”。一张只有80KB的JPEG,解码成Bitmap后照样占1000x1000x4字节。这个知识点在笔试里经常以选择题出现:给一个图片文件的大小和宽高,问解码后占用多少内存。很多人只看文件大小而忽略了像素格式,一选就错。
ANR题目通常是给你一段日志让你判断原因。2020年这套笔试题就有一道类似“主线程执行了网络请求导致ANR”的选择题。其实这类题考的是几个维度:
- 主线程有没有被IO阻塞
- 输入事件5秒没被处理,还是广播10秒没处理完
- 主线程MessageQueue里是不是堆了太多消息
- 是不是死锁了,比如主线程持有锁等子线程,而子线程又在等主线程释放另一个锁
排查ANR的实用工具是/data/anr/traces.txt,看主线程栈顶在什么方法。另外,后台ANR和前台ANR的判断标准不一样,后台广播和服务超时时间更长,因为系统会考虑到后台进程更可能被冻结。
4.2 网络框架题:从HttpURLConnection到OkHttp和Retrofit
网络是客户端项目的重头戏,2020年的笔试直接考OkHttp拦截器源码的题目几乎成了标配,Retrofit的动态代理也是一个经典问法。
OkHttp的题目核心是拦截器链:
- 先创建RealInterceptorChain,把所有拦截器串起来
- 依次调用每个拦截器的intercept方法,最后一个拦截器负责真正的网络请求
- 自定义拦截器可以加日志、加公共参数、做缓存、做重试
为什么用责任链模式而不是直接串行调用?因为这样每个拦截器只关心自己的逻辑,扩展性极强。比如你给请求统一加签名,只需要新增一个Interceptor,不需要修改核心代码。我在实际项目里用Interceptor做过灰度方案,通过请求头把用户ID打到服务端,服务端根据ID决定返回新接口还是旧接口,客户端也不需要考虑兼容问题。
Retrofit的动态代理这块,很多人能背出“Proxy.newProxyInstance生成接口的代理类”,但并不知道为什么需要动态代理。原因是Retrofit要让你写一个Java接口,就能自动完成请求的封装、解析和回调。它用动态代理拦截接口方法,读取方法上的注解(@GET、@POST、@Path),从注解中解析出请求信息,然后用OkHttp发起请求,再把响应交给ConverterFactory转换成对象。如果你理解这一层,面试官再追问“为什么用接口而不用抽象类”时,你会知道接口是天然适合动态代理的,因为接口没有实现,所有方法都必须在代理里处理。
另一个网络题是HTTP和HTTPS的区别。除了“HTTPS多了一层TLS握手”这个常见答案,我建议补充一点:TLS握手过程中的证书校验机制,以及Android里如何做证书校验。很多App的HTTPS配置不到位,直接被中间人攻击。这个点在笔试中也可能以安全类选择题出现,比如“如何防止Charles抓包”。实际上,防止抓包的做法是把证书打包进App并在OkHttp里设置CertificatePinner,或者干脆不信任系统证书。你要能说清楚这些方案的副作用——证书绑定会让证书更新变得困难,一旦证书过期,所有用户都要升级App。
4.3 架构题:组件化、MVVM、Jetpack
2020年正是MVVM大规模普及的一年,小红书这套笔试里也出现了类似“LiveData和RxJava的区别”“ViewModel为什么能持有数据并在Activity重建后恢复”的题目。
ViewModel恢复数据这个考点,核心是ViewModelStore。Activity被销毁重建时,ViewModelStore不会被销毁,它存放在NonConfigurationInstances里,在configuration change时传递给新的Activity。ViewModel的onCleared方法,是等Activity真正finish时才调用的。所以ViewModel适合存放UI状态,但不适合存放大量不必要的数据,因为Activity重建不会清掉它,如果一直持有大对象,反而会造成内存压力。
LiveData和RxJava的区别,我的理解是:
- LiveData能感知生命周期,在RESUMED状态才派发数据,避免在后台更新UI引发的崩溃;RxJava则是事件流,功能更强,但需要自己管理订阅生命周期
- 两者不冲突,可以混用:用RxJava做复杂的业务链,但最终用LiveData回传UI
组件化的题考的是“多个业务模块互相跳转,怎么解耦”。常见方案是ARouter或自研路由表,核心思路就是用一个URL或者类名映射表,动态查找目标Activity。2020年小红书其实已经有自己的路由框架,所以笔试里出现路由设计题很合理。答这类题时,可以提一下ARouter的工作流程:运行时读取注解生成的映射表,通过跳转URL找到目标Activity,同时支持隐式传参、拦截器、降级策略。如果能额外说一句“路由表会占用启动时间,可以用编译期注解或者远端下发配置来优化”,就显示出工程经验了。
5. 实操题和代码题:怎么写出让面试官满意的答案
5.1 手写代码题:单例、观察者模式、自定义View
笔试代码题一般分两类:一类是算法题,一类是设计模式或者组件实现题。算法题前面说过,这里专门讲设计和组件。
比如“手写一个线程安全的单例”几乎是必考题。从饿汉式到懒汉式到双重检查锁到静态内部类,到枚举单例。你要能说出每种写法的优缺点:
- 饿汉式:类加载时就创建,简单线程安全,但可能造成资源浪费
- 懒汉式:需要synchronized,性能差
- 双重检查锁:DCL,需要volatile防止指令重排
- 静态内部类:JVM持有类锁的初始化机制保证线程安全
- 枚举:天然防止反射和序列化破坏单例
面试官考这道题看的是你对“并发安全的本质”是否理解。比如为什么DCL必须加volatile?因为new Singleton()不是原子操作,它有分配内存、初始化、赋值三步,如果不加volatile,其他线程可能看到一个未完全初始化但引用已非空的对象。这个解释比“保证内存可见性”更精准。
再比如“手写一个观察者模式”。这个题的深层用意是检验你对LiveData和EventBus原理的理解。LiveData内部就是一个观察者模式,它把观察者的生命周期和Activity绑定,在STARTED状态才通知数据变化。EventBus则用了注解处理器和反射。你如果只是写一个普通的Observable和Observer接口,分数不会高;如果你能补充“如何在状态恢复时重新绑定时触发最后一次事件”,那就很加分——因为LiveData的postValue和setValue区别就是高频数据更新时只保留最新值,这正是手机内存有限场景下的设计取舍。
自定义View的题也是一样。常见题是“自定义一个可以指定圆角的ImageView”。这类题的核心是Path裁剪和Xfermode的使用,以及硬件加速带来的限制。Canvas的saveLayer和clipPath在低版本设备上不支持硬件加速,需要做兼容处理。能说出这些限制的,才是真的在项目里趟过坑的人。
5.2 场景分析题:给一个业务场景,让你给出技术方案
小红书这套笔试题的压轴题往往是场景设计题,比如“设计一个短视频播放器的缓存机制”“首页Feed流怎么做流畅度优化”“图片加载框架如何做三级缓存”。这些题没有标准答案,但有一条清晰的答题路径。
答这类题的框架我总结为四步:
- 明确诉求:这个方案要解决什么问题?是缓存命中率,还是流畅度,还是省流量?
- 拆分模块:把问题拆成几个子问题。比如播放器缓存,拆成播放器状态管理、缓存调度、磁盘淘汰、预加载策略
- 列出技术选型和理由:为什么用DiskLruCache?为什么用三级缓存?每个选择的代价是什么
- 补充异常场景:断网了怎么办?缓存文件损坏怎么办?内存不够怎么办?
拿“图片加载框架三级缓存”举例:逻辑是内存缓存、磁盘缓存、网络加载三级,依次查找。面试官想听的其实不是这个流程,而是你对“缓存淘汰策略”的理解:
- 内存缓存用LRU,但要注意Activity重建时内存缓存要不要清空
- 磁盘缓存:用DiskLruCache还是自研?DiskLruCache会写journal文件,损坏了怎么办?
- 网络加载:图片请求的并发控制,比如同一url只发一个请求,其他线程等待同一个结果,避免重复下载
短视频缓存这道题还有一个坑:视频文件体积大,和图片缓存完全不是一回事。视频下载任务不能一拥而上,要控制并发数量;断点续传要做好Range请求;播放器要能在缓存不全的情况下边下边播。如果你能提到“缓存预加载是根据用户行为预测,比如评分高的视频提前缓存后两条”,面试官就能看出你是有产品思维的,不只是写代码的。
5.3 笔试答题的时间分配和卷面策略
除了知识储备,笔试还有一个容易被忽略的维度:答题策略。我在前面反复强调“这些题考的是原理理解”,但考试终究有时间限制,一张卷子90分钟,如果你的算法题卡了半小时,后面的场景设计和代码题就基本没有时间好好写了。
以2020年那张卷子为例,题量大概在30到40题之间,包括单选、多选、填空、简答、代码题。我的建议是:
- 快速浏览整卷,先做自己最有把握的题,不在一道选择题上纠结超过3分钟
- 填空题和简答题控制在每题5到8分钟,重点是把关键点写清楚,不要长篇大论
- 代码题至少留25分钟以上,因为代码题不只是写出来,还要检查边界条件和复杂度
- 场景设计题如果有,放在最后,因为这个题灵活度高,分数弹性也大,前面能拿分的题先拿到
另一个容易被忽视的是:笔试答卷的排版和书写。如果你在纸上写代码,务必先写清楚思路,再写实现。我见过很多考生代码写得乱,面试官根本读不下去。如果你在线答题,一定要写注释,特别是解释关键判断条件和边界处理。代码不只给机器看,更是给人看的,面试官正是那个“人”。
6. 常见翻车点与避坑清单
6.1 容易丢分的概念混淆清单
这部分我整理一份我在历年校招笔试中反复看到的错题清单,罗列出来分享给大家。
| 容易混淆的概念 | 错误理解 | 正确理解 |
|---|---|---|
| Bitmap内存占用 | 看文件大小 | 看像素尺寸x像素格式 |
| Activity启动模式 | standard每次新建实例 | 要分是否带flags,比如FLAG_ACTIVITY_NEW_TASK组合时行为会变化 |
| 线程和协程 | 协程是轻量级线程 | 协程是框架,封装了线程切换,本质上还是在线程上运行 |
| ANR的判定 | 主线程5秒没响应就ANR | 分场景,前台输入事件5秒、后台广播10秒、服务20秒 |
| LiveData和StateFlow | 两者等价 | StateFlow有并发和背压语义,LiveData与生命周期强绑定,取舍不同 |
| 虚拟内存和物理内存 | 两者概念一样 | 虚拟内存是地址空间,物理内存是真实内存,App的PSS才是真实占用 |
上面这些混淆点,在笔试里几乎都是选择题或判断题的高频设置点。例如,图片内存题往往把文件大小和像素尺寸混在一起当干扰项;协程题会设置一个“协程不占用线程所以不需要关心线程安全”的错误说法,诱导你选错。这些坑躲避的方法只有一个:不要背结论,要会推导。
6.2 面试官追问时容易暴露的问题
笔试虽然只交一次卷,但很多公司的笔试题在面试环节会被拿出来继续追问,比如“你这道题为什么这么写”。这时候最容易暴露的就是“背答案”和“真会”之间的差距。
举个例子,笔试题问“ThreadLocal的原理是什么”。你回答“每个线程持有自己的ThreadLocalMap,key是ThreadLocal,value是线程变量”。面试官追问“那ThreadLocal为什么会导致内存泄漏”?如果你不清楚,说明你根本没被线上的内存问题折磨过。答案是:ThreadLocalMap里的Entry继承WeakReference,key是弱引用,value是强引用;如果线程栈一直不结束,且ThreadLocal对象被回收,value就永远无法被回收,形成泄漏。这也是为什么在线程池场景里,用ThreadLocal要格外小心。
另一个追问高发区是“你说你用MVP还是MVVM,为什么选这个”。凡是只说“MVVM好用”而不解释和业务场景匹配关系的,基本都会被追问到死。正确的答法是结合项目规模:如果项目小、团队分工不明确,MVP反而简单直接;如果业务复杂、多人协作,MVVM靠ViewModel和LiveData把状态从Activity中抽离出来,更适合团队并行开发和单元测试。面试官真正想听的不是信仰,而是你做过取舍。
6.3 针对备考的实用建议
最后给准备Android校招笔试的同学一些实操建议。
第一,把Android官方文档的指南部分通读一遍,尤其是Activity、Fragment、Handler、Jetpack这几个章节。官方文档虽然啰嗦,但它是所有面试题的权威来源。很多网上的博客写得不对,要以官方文档为准。
第二,自己动手写几个Demo,把原理跑通。比如你知道Handler的原理,那就写一个子线程Handler发送消息的Demo,再打一个ANR,用traces文件定位一下。只有你亲手排过一次错,你才能把原理讲得不像背的。
第三,做LeetCode高频题时,不要只刷量。每天选2到3道题,把“思路 -> 代码 -> 复杂度分析 -> 边界条件”写清楚,相当于给自己做一份错题笔记。刷300道不全做完,但每道题都理解得透彻,价值比刷1000道一知半解的题高得多。
第四,关注版本变化。2020年之后Android的新特性层出不穷,12、13、14陆续发布,动态桌面图标、WiFi权限、前台服务类型限制、16KB页对齐这些新知识,都可能出现在新的笔试题里。备考时要结合当年发布的版本做针对性补充。比如Android 10之后分区存储成为强制要求,如果你还用旧的方式读文件,别说笔试,工程上都会直接踩坑。
踩过几次坑之后我有一种感觉:笔试考的不是你的“知识量”,而是你在有限的时间里能否快速定位问题、组织思路、并且用代码表达出来的能力。所以,准备笔试的唯一捷径,就是把每一个知识点都落实到最简单的“为什么”上——为什么这么设计,别人的方案有什么缺点,如果是我来做会怎么取舍。把这个习惯养成了,不只是笔试会顺利,以后做项目、带团队,都是硬通货。