
开头2018年秋天我坐在商汤科技校招笔试的考场里面对那张Android开发工程师的试卷第一反应是“这公司的笔试怎么一股算法味儿”。说实话当时很多同学是冲着AI公司的光环去的以为Android岗就是考四大组件、写个界面结果一拿到卷子就发现商汤考的东西比想象中深得多。这场笔试虽然过去几年了但直到现在我依然觉得它是我参加过的最有代表性的AI公司Android校招笔试之一也成了我后来面试其他大厂时反复复盘的一份材料。这篇内容适合两类人看一类是准备投递AI公司、图像类App团队Android岗位的应届生另一类是已经工作一两年、想补一补底层原理短板的Android开发。我会把这场笔试的题型构成、核心考点、答题思路、现场踩坑全部拆开讲不是单纯回忆题目而是告诉你每一类题背后面试官到底在考察什么以及你现在该往哪个方向准备。哪怕你拿到的不是商汤的卷子这套分析思路也完全能用。1. 商汤校招笔试的定位与整体设计思路1.1 AI公司招Android工程师到底想招什么人先聊一个很多人没想明白的问题商汤这种以计算机视觉算法为核心的公司为什么要大规模招Android开发2018年正好是移动端AI开始落地的关键节点。人脸识别解锁、AR特效、智能相册、端侧检测这些能力都需要跑在手机App里而App的载体就是Android和iOS。算法团队写出模型不能只放在服务器上还得有一个专门的Android工程师把模型封装成SDK、集成进App、处理相机流、优化推理速度、解决各种机型适配问题。这就决定了商汤对Android工程师的画像不是“会写界面就行”而是“能理解系统底层、能对接算法SDK、有性能优化意识、能独立解决疑难问题”。这个定位直接反映在笔试题型上。我在那场笔试里明显感觉到选择题和简答题覆盖的知识面特别广Java、数据结构、网络、Android Framework、多线程并发全都有而且难度不是均匀分布的有些题明显是拿来筛掉基础不牢的人有些题则是拉开差距的。编程题不是纯LeetCode风格反而带了点工程味道给的场景往往需要你综合考虑时间复杂度和代码可读性。设计题更是直白直接就让你设计一个在实际业务中会遇到的模块。1.2 第一场笔试题型结构与时间分配复盘商汤的笔试第一场我记得是线上笔试总时长大概120分钟题量不小题型大致分为四块题型题量考察重点建议用时单选题15题左右Java基础、数据结构、Android基础、网络20分钟多选题5题左右Android原理、并发、内存优化10分钟简答题3题左右Handler、Binder、进程通信等原理题30分钟编程题2题左右算法与数据结构、工程编码能力40分钟设计题1题系统设计、架构能力、逻辑完备性20分钟这个时间分配是个理想模型实际操作中很多人会翻车因为选择题里藏着几道很刁钻的题目一纠结就五分钟过去了。我复盘时总结出的策略是选择题遇到没把握的先标记跳过不要恋战把时间留给后面的简答和编程题编程题就算不能完全AC也要写出能跑通大部分用例的代码不要一句“不会”就空着。另外还有一个容易被忽略的点商汤的笔试系统是支持本地IDE写完再贴上去的不像某些平台只有网页编辑器。这个细节很重要意味着你完全可以用自己熟悉的Android Studio或者IDEA把代码跑通再提交比纯手写代码的容错率高很多。1.3 2018年的技术背景决定了题目风格2018年Android技术栈正处在一个分水岭上。Kotlin刚发布1.2版本不久虽然Google已经宣布Kotlin First但大部分学校课程和企业项目还在用Java所以笔试选择题里Java语法和并发仍然是主流Kotlin几乎没怎么出现。Jetpack那一套LiveData、ViewModel、Room虽然已经推出了但普及率还没有现在这么高笔试里也很少直接考Jetpack组件更多是考最原始的Android组件机制。但有一个趋势是明显的开源库原理开始成为笔试的高频考点。2018年Retrofit、OkHttp、Glide、RxJava这些库已经成了项目标配所以笔试简答题里经常出现“Retrofit的原理是什么”、“Glide的缓存机制了解吗”这类题目。这其实是面试官的一种考察策略——你简历上写了用过这些库那就得知道它们内部是怎么实现的否则只是停留在调用层面的“使用工程师”而不是他们想要的“解决工程师”。顺便说一句那时候商汤已经开始在移动端做深度学习框架相关的工作了所以笔试里偶尔会出现一些跟NDK、JNI、性能优化相关的考察点。你不需要会写C但至少得知道Java层和Native层之间是怎么通信的这在后面的简答题或设计题里会成为一个加分项。2. 核心知识点拆解从Java基础到Android Framework2.1 Java基础与并发选择题里的“送分题”和“送命题”Java基础这部分表面上看是送分题实际上藏着很多坑。我记得有一道题是问HashMap在Java 8中的数据结构选项里包含了“数组加链表”、“数组加红黑树”、“链表加红黑树”这些排列组合如果你只背过“数组加链表”这个口诀就会掉进坑里。正确答案是数组加链表但当链表长度超过8且数组长度大于等于64时链表会转成红黑树。这种题考的是对细节的掌握程度而非概念性的了解。更狠的是并发相关的题目。volatile和synchronized的区别、ConcurrentHashMap在Java 7和Java 8中的实现差异、线程池的核心参数和执行流程、CAS和AQS的基本原理、ThreadLocal的内存泄漏问题这些几乎是商汤这类技术型公司笔试的固定组合。我当时复习的时候总结过一套口诀volatile保证可见性不保证原子性synchronized是可重入的独占锁ConcurrentHashMap读不加锁写加锁并且锁粒度细化到桶。这套口诀能帮你快速解70%的并发选择题但剩下30%会考得更细比如“ThreadLocal的key为什么用WeakReference”以及“内存泄漏发生在什么场景下”。我建议大家复习Java并发时不要只看结论要把执行流程画出来。比如线程池执行一个任务时是先判断核心线程数还是先往队列里丢还是先判断最大线程数这个顺序如果弄反了遇到那种“核心线程数为2、最大线程数为4、队列容量为10同时来了15个任务”这种题就直接蒙圈。正确顺序是先判断核心线程是否已满没满就创建核心线程执行满了就尝试放入阻塞队列队列也满了再判断是否达到最大线程数没达到就创建非核心线程执行达到就执行拒绝策略。这个流程在简答题里也经常出现值得画下来贴桌子上。2.2 Android四大组件与Binder机制必考但容易丢分的重灾区四大组件属于Android开发的基本盘但商汤笔试考得比一般公司深入。Activity会问启动模式standard、singleTop、singleTask、singleInstance各自的使用场景以及onSaveInstanceState和onRestoreInstanceState的调用时机。Service会问startService和bindService的区别以及混用时生命周期怎么走。BroadcastReceiver在2018年是个热点因为Android 8.0对隐式广播做了限制静态注册很多场景下收不到广播了这个知识点当年很多人不知道答不上来就会丢分。ContentProvider相对考得少一些但如果是做跨进程数据共享的场景也会考到它和Binder的关系。Binder是整张试卷里最硬核的一部分。选择题可能会问“Binder通信中数据拷贝了几次”答案是1次因为Binder利用mmap把内核空间和用户空间做了映射避免了传统IPC的两次拷贝。简答题里更常见的是让你描述Binder的完整通信流程客户端通过ServiceManager获取远程服务代理调用代理方法时数据被序列化后写入共享内存驱动层完成数据传递服务端收到请求后执行方法并通过同一个通道返回结果。这个流程要说清楚并不是靠背模板而是要有自己的理解。我当时吃透Binder用的方法是自己写了一篇笔记把整个调用链路用文字一行一行地描述清楚比如“client调用remote.transact()然后陷入内核态binder_thread_write把数据写入binder_thread_read等待返回值驱动唤醒server端的binder线程server执行onTransact返回结果”。写完这个流程之后再去看那些选择题你会觉得出题人已经没什么花样可玩了。这里提醒一下Binder只考Android原生IPC那套不要扯到AIDL之外的什么神秘技术AIDL只是Binder的上层封装考的还是Binder本身。2.3 自定义View与事件分发商汤特别爱考的内容为什么说商汤特别爱考自定义View因为他们的业务场景里大量涉及相机预览、人脸框绘制、图像特效渲染这些功能在Android上实现基本绕不开自定义View和SurfaceView/TextureView的选择。所以笔试里出现一道“onMeasure、onLayout、onDraw的调用顺序”或“invalidate和requestLayout的区别”这种题太正常了。onMeasure和onLayout是很多校招生的弱点因为平时写界面都是XML里套用现成控件很少自己实现测量逻辑。笔试里如果考“自定义View支持wrap_content需要注意什么”你就得知道不重写onMeasure的话wrap_content会被当成match_parent处理因为父类默认的测量模式是AT_MOST时直接使用了父容器剩余空间作为测量结果。正确的做法是在onMeasure里根据MeasureSpec的mode做判断如果是AT_MOST指定一个默认值。事件分发这块核心就是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法的职责划分以及事件从Activity到ViewGroup再到View的传递顺序。选择题经常给一个场景比如“一个Button嵌套在LinearLayout里两者都设置了OnTouchListener和onTouchEvent点按钮时方法调用顺序是什么”这题如果不画一张事件分发链路图很容易记混。我的记忆技巧是事件总是先给最外层的ViewGroup做拦截判断如果ViewGroup不拦截就传递给子View子View如果消费了事件那父ViewGroup的onTouchEvent就不会被调用反之则逐级向上传递。父View拦截后子View收不到事件但父View自己可以收到后续事件。2.4 数据持久化与网络层不会直接考但设计题绕不开数据持久化在2018年笔试里通常不会单独出简答题而是出现在设计题或者场景题里。比如“让你设计一个图片加载库缓存策略你怎么做”这就涉及到内存缓存LruCache和磁盘缓存DiskLruCache的配合使用本质上是数据持久化方案设计。数据库也是如此如果考“设计一个聊天消息存储模块”你就要考虑SQLiteOpenHelper的使用、版本升级、索引优化、事务处理等细节这些知识点虽然不直接出现在选择题里但你在设计题中的思考深度直接决定面试官对你的评价。网络层在选择题里考得比较基础主要是一个HTTP请求从发起到返回的完整过程、TCP的三次握手和四次挥手、HTTPS和HTTP的区别、HTTP/2的新特性等。这些内容在大二计算机网络课上都学过但很多人忘得差不多了考前一定要重新过一遍。我记得有一道选择题问“TCP连接建立时前两次握手的标志位是什么”答案是SYN和SYNACK难度不大但如果你只记得确认号序号的概念忘了标志位就容易选错。到了设计题阶段网络层就重要了。比如“设计一个App的崩溃日志上报系统”如果你能想到日志先存本地数据库、在合适的时机批量上报、失败要重试、上报时携带设备信息和App版本号、考虑网络状态和流量消耗、用WorkManager或JobScheduler做任务调度那这道设计题你就已经稳稳拿到80%的分数了。不过2018年WorkManager还没推出当时更合理的答案是IntentService加网络状态监听这些细节在答题时要结合当年技术栈。3. 实操复盘当年真题的思路推演与答题模板3.1 一道典型算法题最大连续子数组和的完整推演算法题在商汤笔试里通常不会出偏难怪题更偏向在LeetCode上常见但稍加改动的真题。我印象深的一道是最大连续子数组和题目描述是给定一个整数数组找到一个具有最大和的连续子数组返回其最大和。这道题的原型是LeetCode 53题难度属于中等偏低但它有一个要求很关键代码里不能使用额外空间也就是空间复杂度要为O(1)。解题思路用到的是动态规划。我们可以定义一个变量cur表示以当前元素结尾的最大子数组和另一个变量max表示全局最大子数组和。遍历数组时cur的更新方式是“cur Math.max(num, cur num)”这行的含义是如果当前元素本身比加上之前的累加和还大说明之前的累加和是负收益直接舍弃从当前元素重新开始。然后用max Math.max(max, cur)更新全局最大值。这样一轮循环就解决了问题时间复杂度O(n)空间复杂度O(1)。这道题的易错点在于初始化。我第一次写的时候把cur初始化成了0结果是如果数组里全是负数答案就会错误地返回0而不是最大的那个负数。正确做法是cur nums[0]max nums[0]然后从第2个元素开始遍历。这个边界细节笔试时一定要写清楚因为出题人很喜欢在测试用例里放全负数的情况。如果题目进一步扩展让你同时输出最大子数组的起始位置和结束位置那就要在更新cur的时候记录起点和终点下标。遇到cur Math.max(num, cur num)且选择了num这一侧时就把临时起点设置为当前下标当max被更新时把最终起点和终点同步过去。这种“一题多问”的情况在笔试里也出现过所以我建议大家复习算法题时不要只背题解要理解每个变量的含义学会在同一个思路上做扩展。3.2 一道Android架构题设计图片加载库的答题框架图片加载库这道题在2018年出现频率极高因为Glide当时已经非常流行但很多人只懂用不懂原理面试官正好拿这个来筛选。设计题通常是“如果让你自己实现一个类似Glide的图片加载库你会怎么设计需要考虑哪些模块”这道题没有标准答案但有一个公认的答题框架。第一步是明确核心模块划分。至少要有三个模块图片请求模块、缓存模块、显示模块。图片请求模块负责接收外部传入的URL和ImageView把加载任务封装成一个Runnable丢给线程池缓存模块负责判断内存缓存、磁盘缓存、网络加载的优先级显示模块负责在主线程将Bitmap设置到ImageView上同时处理图片的裁剪、缩放、圆角等变换操作。第二步是讲清楚缓存策略。我当时的回答是先检查LruCache内存缓存命中就直接返回没命中再检查DiskLruCache磁盘缓存命中就加载磁盘图片并回填内存缓存都没命中才走网络请求请求成功后按顺序写入磁盘缓存和内存缓存。这里有一个加分项三级缓存机制不是简单的“内存-磁盘-网络”而是每一级缓存都要设置合适的容量和淘汰策略例如LruCache的容量要考虑App可用内存的大小具体可以用ActivityManager.getMemoryClass()来估算避免占用过多内存。第三步是考虑生命周期管理。如果Activity销毁了图片加载任务还在执行就会导致内存泄漏和崩溃。所以设计库里必须有一个机制可以监听Activity/Fragment的生命周期在销毁时取消正在执行的任务。2018年最流行的方案是给Activity注册一个不可见的Fragment利用Fragment的生命周期回调来感知宿主销毁这也是Glide早期的实现思路。能对着这道设计题说到这个层面面试官基本就会认为你真正理解图片加载库而不是只会用。3.3 开放性简答题不会的题怎么答才能拿分笔试里总会有一些你不太会的题目比如有一道问“Android中为什么主线程不会因为Looper.loop()里的死循环而卡死”这题看着简单但真正想答完整也不容易。我当时的思路分三层第一层说明Looper.loop()确实是一个死循环会不断从MessageQueue中取消息第二层说明这个循环是主线程唯一的活路因为一旦循环退出主线程就结束了App也就挂了第三层是核心说明主线程之所以没有被这个死循环卡死是因为当MessageQueue为空时主线程会调用epoll机制进入休眠状态释放CPU而不是忙等所以不会造成CPU占用过高。这种回答方式有一个通用模板我觉得对后来的面试非常有用叫做“是什么、为什么、底层怎么做”三步法。第一步回答“是什么”直接给出题目中概念的定义或结论第二步回答“为什么”解释这个设计的原因和背景第三步回答“底层怎么做”从源码或机制层面展开细节。用这个模板答题就算你源码细节记得不全也能给面试官留下“这个学生有系统的认知框架”的印象拿到大部分步骤分。还有个技巧是关于填空题和简答题的写法。线上笔试的简答题通常不是选ABCD而是直接在一个文本框中输入回答所以你的文字排版很重要。我当时会分点作答每一点单独一行关键术语加粗。不要写一大段文字让阅卷人找重点阅卷人在屏幕上快速扫答案时分点作答的识别效率远高于大段文字。这套技巧在任何一家公司的线上笔试里都能用上。4. 常见问题与避坑实录笔试现场的教训4.1 时间分配错误最容易犯的错几乎没有例外我发现身边所有参加商汤笔试的同学出来后吐槽最多的问题都是“时间不够”。选择题里纠结太久编程题写到一半没时间优化设计题草草写了两行就交卷这种情况非常普遍。我自己的策略是“30分钟死线法”。每道选择题最多给自己2分钟超过2分钟还没头绪就随便选一个直觉答案并标记“待检查”等所有题目做完再回来看。编程题如果15分钟还没思路先把暴力解法写出来确保能过一些基础用例然后如果还有时间再优化成更高效的解法。设计题一定要留足20分钟因为这道题是拉开差距的关键哪怕你前面选择题有几道拿不准只要设计题答得有逻辑整体分数都不会低。另外要特别提醒编程题提交之前一定要自己造几个测试用例跑一遍。我当时就吃过亏代码逻辑看着没问题但漏了空数组的边界情况结果在LeetCode风格平台上得分减半。笔试题的测试用例往往不给你提示所以你要主动考虑空数组、全负数、只有一个元素、超大数溢出这些场景。4.2 遇到不会的题千万别空着不写笔试和面试有个很大的区别面试的时候如果你完全不会强行编可能会留下负面印象但笔试里空着不写等于主动放弃了得分机会。尤其是主观简答题和设计题只要你写的内容沾边就有可能拿到步骤分。举个例子去年我一个学弟也在准备AI公司笔试他遇到一道“如何优化App启动速度”的简答题完全没复习过这个知识点但他按照自己的理解写了减少Application里不必要的初始化、用懒加载延迟初始化、把耗时操作放到子线程、启动页不要加载过多图片资源。这些答案虽然不完整但每一条都是正确的优化方向阅卷人看了就知道这个人是懂Android的。还有一点编程题的代码即使报错也一定要贴上去。我判断阅卷人的批改逻辑是“先看思路是否正确再看代码是否可运行”。如果你能把思路写清楚哪怕代码有语法错误分数也不会全扣。反过来如果你只写了一个“不会”那这道题就是0分。4.3 被忽略的加分项代码规范与命名习惯笔试虽然是机器判卷和人工review结合但你的代码质量在人工review阶段会直接影响主观评价。我当时就因为用了清晰的方法命名和注释在编程题上拿到了不错的印象分。具体来说方法名不要叫fun1、fun2要用能够描述功能的动词加名词比如findMaxSubarraySum变量名不要用a、b、c要用curSum、maxSum这种有语义的命名关键逻辑处加上一行简短注释让阅卷人快速理解你的思路。还有一个容易被忽略的细节是缩进和空行。很多在线笔试平台的代码编辑器不会自动格式化代码你粘贴上去的代码如果挤成一团阅卷人看起来非常痛苦。提交前一定要花10秒钟手动整理好代码的缩进和空行这个动作本身就会让人觉得你是一个注重工程规范的开发者。4.4 常见丢分点速查表丢分点具体表现应对策略边界条件遗漏数组为空、只有一个元素、全负数提交前自测边界用例时间分配失衡选择题纠结太久编程题时间不足设置每道题的时间死线简答题没有逻辑一大段文字堆砌找不到重点用“是什么-为什么-底层”三步法分点回答代码可读性差变量名无意义、没有缩进写代码时直接注意规范设计题答得太浅只写“用缓存”不写具体实现至少列出模块划分和关键流程编程题空着不写遇到不会的题目直接放弃写暴力解法或者完整思路也能得分不检查代码编译都不通过就提交预留5分钟检查编译运行5. 从2018到当下这份笔试的参考价值在哪里这套笔试过去这么多年了里面很多技术细节已经被Android生态的演进覆盖掉了。比如Kotlin在2019年成为Android开发的主流语言协程取代了很大一部分线程池和回调的写法Jetpack Compose从2021年开始逐步替代传统的XML布局体系现在的Android校招笔试风格和2018年已经有挺大差异。但我要说的是底层原理永远是那套东西。Binder还是那个BinderHandler的消息循环机制没有本质变化Activity的启动流程也只是加了更多的生命周期状态和兼容性分支。你去看现在大厂的Android面试题问的最多的依然是“Handler为什么不会卡死主线程”、“Binder为什么比Socket高效”、“App启动流程是怎样的”这些问题在2018年就被问烂了现在还在问。所以我把这场笔试拿出来复盘重点不是让你去背2018年的真题而是让你理解一份高质量的Android笔试到底在考什么。它会考你对系统底层的理解会考你的工程思维会考你在遇到不熟悉的问题时如何拆解和组织答案。这些能力永远不过时。另外2018年商汤笔试里的AI方向内容在今天已经变得更重要了。现在Android工程师如果懂得对接端侧推理框架、了解模型量化、会做相机流和帧数据的处理在职场上会非常有竞争力。笔试里如果出现相关场景题你哪怕只是提到“移动端深度学习模型需要转换为轻量级格式才能在端侧运行”也能显示出你对公司核心业务的了解这种“业务匹配度”在筛选过程中会起到关键作用。6. 给现在准备校招的同学一些实用建议如果你正在准备Android开发相关的校招笔试我结合这次复盘和后来几年带新人的经验给你几条实在建议。第一系统梳理底层原理至少看完Handler、Binder、AMS、WMS这四大源码专题。不需要逐行记住源码但要能说出关键流程和关键类名。比如聊到Handler你要能说出Loop、MessageQueue、Message、Handler这几个类的关系以及epoll机制为什么能让主线程休眠。聊到Binder你要知道一次拷贝怎么实现的以及驱动层和用户空间的关系。这部分知识是Android面试的基本盘学校和培训一般不会讲这么深但笔试面试一定会考。第二把常用的开源库原理搞明白。Retrofit的动态代理和注解解析、OkHttp的拦截器链和连接池复用、Glide的三级缓存和生命周期绑定这三个是最高频的考点。不要停留在“我会用”的程度至少要知道它们各自解决什么痛点、核心实现思路是什么。第三刷题不能停但刷法要有重点。校招笔试的算法题不会超纲到竞赛难度一般就是LeetCode的简单和中等题。你不需要刷完500题但要把高频题型的套路吃透比如双指针、滑动窗口、动态规划基础、二叉树遍历、链表操作、排序和二分查找。LeetCode上按“频率”排序刷前100道基本覆盖了大部分场景。第四想清楚自己的定位。如果你目标就是AI公司或者大厂的Android岗一定要在简历和笔试答案中体现出对业务的了解。比如商汤做视觉你可以在回答性能优化题时提到“相机预览帧数据处理”、“YUV转RGB”、“内存复用”这些与图像相关的关键词这会让你在一堆只会Java语法的人里面瞬间被注意到。最后再说一句关于心态的话。笔试没通过不代表你不适合做Android有时候就是时间没分配好、某类题型恰好没复习到。我当年也栽过好几家公司的笔试后来每次复盘都发现自己其实离通过只差一点点。把这个过程当成打怪升级每一场笔试都会让你离offer更近一步。