ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

智能汽车Android笔试:底层原理与性能优化全解析

智能汽车Android笔试:底层原理与性能优化全解析 智能汽车厂商的Android笔试到底会考什么这是很多准备投递车机方向的同学问过我的问题。恰逢整理旧资料时翻到了小鹏汽车2020秋招的Android笔试题触发了不少回忆。当时新能源汽车赛道还没像现在这么白热化但小鹏的笔试已经明显和普通互联网公司的Android题拉开了档次——它不只是考你会不会写页面、会不会调接口而是实打实地在考察你对Android底层、性能稳定性、甚至是系统定制改造的理解。这篇文章就把当时那套笔试的考察范围、题目类型、背后知识点以及我后来复盘时总结的备考思路完整拆解一遍。无论你是正准备投递小鹏还是想了解车机方向Android开发的门槛这篇应该都能给你一个比较清晰的参照。说实话第一次看到小鹏这套笔试时我第一反应是“这难度和BAT中厂差不多了”。整张卷子题型分布大概是选择题、简答题、编程题各占一定比例考察重心明显偏向Android Framework层、性能稳定性、多线程并发和数据结构算法。尤其让我印象深刻的是好几道题都在反复问“系统如何在极端条件下保持稳定”——这其实是车机场景最核心的诉求。车机不像手机不能“死机就重启”用户正在开车中控黑屏或者卡顿是直接影响安全的事故级问题。所以小鹏的笔试逻辑从一开始就很清晰你不是来写App的你是来维护一套在车轮上运行的Android系统的。1. 车企Android笔试到底在考什么一场“应试”背后的行业信号1.1 为什么车企笔试偏爱Android深水区先聊个背景问题为什么小鹏不像普通互联网公司那样考UI布局、网络请求库、Jetpack组件反而往深了考原因在于车机系统的技术栈和手机App开发有很大区别。车机上的Android往往是一个深度定制的系统镜像厂商会基于AOSP源码做大量修改比如增加多屏交互、语音助手唤醒、车辆状态感知、蓝牙电话、高精地图渲染等能力。这些能力大部分落在Framework层和系统服务层而不是普通的App层。如果候选人对AMS、WMS、Binder、Handler这些底层机制没有概念进入项目组之后光是熟悉系统源码就要花掉两三个月更别说参与真正的定制开发了。从小鹏的角度看2020年正是P7交付、XPILOT自动驾驶辅助系统持续迭代的关键时期。智能座舱的流畅度、稳定性直接决定用户口碑。当时的车机团队需要的人是“能改系统”的人而不只是“能写界面”的人所以笔试的题目直接往Android内部机制、进程通信、渲染性能这些方向倾斜是有明确的业务诉求在背后的。1.2 从题目分布看小鹏的技术侧重我把当时考生反馈和论坛里能拼凑出来的题目方向梳理了一下大致可以分成下面几个板块考察模块常见出题方向占分比重感觉Java/Kotlin基础集合源码、并发、JVM内存模型、泛型、反射中等Android四大组件启动模式、生命周期、进程存活、IntentFilter匹配较高消息机制与异步Handler/Looper/MessageQueue全链路、AsyncTask、线程池高进程与BinderBinder原理、AIDL、进程间通信高系统稳定性ANR成因、内存泄漏、Crash治理高View体系View绘制流程、事件分发、自定义View中等性能优化布局优化、内存优化、启动速度高算法与数据结构链表、栈、队列、二叉树、动态规划、LRU较高这个分布很能说明问题基础知识不能差Android重点是底层机制性能稳定性的题目多到甚至让人怀疑小鹏是不是把系统组的面试题直接搬到了笔试里。这是车机团队的典型画像——重稳定、重性能、重系统改造能力。下面我逐个板块展开说。2. 选择题背后藏着的Android基础分水岭选择题这部分乍一看像常规八股但深入看会发现“坑”特别多。很多背题族能过初筛却在这些选项上反复犹豫因为每道题不只是在考“是什么”更在考“为什么是这样”。2.1 四大组件与启动模式背下来不等于懂Activity启动模式是笔试的常客小鹏也不例外。Standard、SingleTop、SingleTask、SingleInstance这四种模式的定义大部分人都能背出来但题目一旦结合场景就很容易翻车。比如有一类经典问法当Activity A是SingleTask模式A启动BstandardB再启动A时A的onNewIntent会不会被调用栈里的Activity会不会被清掉实际上A被B拉起时系统会找到已有的A实例把栈内A之上的所有Activity清掉然后回调A的onNewIntent。这里面藏了几个考点onNewIntent的调用时机是在onRestart之后、onStart之前。如果A在栈里已经有实例那么不会走onCreate而是走onNewIntent onRestart onStart onResume。Intent在新旧两种情况下需要用setIntent()手动更新否则下次getIntent()拿到的还是旧数据。这些细节在平时写业务代码的时候确实不容易触发但在车机上很常见——比如语音助手从地图页面拉起音乐页面再从音乐页面切回地图整个任务栈的跳转关系必须精确可控否则就会出现“页面越叠越多”“返回时跳过了中间页面”这类问题。另外小鹏还很爱考IntentFilter的匹配规则。action、category、data三种匹配条件各自是什么优先级隐式Intent什么时候会抛出ActivityNotFoundException这些都要能做到“不假思索”才算过关。我记得当时有一道题专门问隐式Intent至少要显式指定category吗答案是Intent隐式启动Activity时默认会自动加上android.intent.category.DEFAULT所以如果你的IntentFilter里面没有声明这个category隐式调用就会失败。这个细节一旦疏忽写了个自定义的隐式Intent在测试时怎么也跳转不过去排查半天才发现是category问题——这种教训写代码的人应该都体会过。2.2 Handler消息机制和ANR每次必考的两个大坑Handler是Android笔试的“钉子户”小鹏的试卷里它几乎贯穿了选择题和简答题。常见考察点有这么几个方向Handler、Looper、MessageQueue、Message的协作关系。MessageQueue里消息的排列顺序到底由什么决定正常情况下按when时间戳排列但postSyncBarrier同步屏障会让异步消息优先执行。ThreadLocal在Looper中的角色每个线程只能有一个Looper主线程的Looper是Looper.prepareMainLooper()创建的普通子线程需要手动Looper.prepare()和Looper.loop()。内存泄漏陷阱非静态内部类Handler默认持有外部Activity引用如果消息还没处理完Activity就销毁了泄漏就发生了。正确做法是使用静态内部类弱引用同时在onDestroy里removeCallbacksAndMessages(null)。ANR相关的小鹏题目也出得比较刁钻。选择题可能会问“以下哪种情况不会触发ANR”选项里给出一堆耗时操作。答案是Activity的输入事件5秒未处理会触发ANRBroadcastReceiver的onReceive10秒未处理完会触发ANRService的onStartCommand20秒未处理完会触发ANR但子线程里做耗时操作并不会直接触发ANR——它只是UI线程卡顿的诱因。这里考的就是对“ANR本质是系统对主线程响应超时的反馈”这个底层逻辑的理解。2.3 内存泄漏与垃圾回收车机性能的隐形雷区这一块小鹏考得格外多并不是偶然。想想车机的使用场景通电就开始运行熄火可能也还在待机一个应用要长时间保持存活。手机上的内存泄漏可能用户过两天重启手机就感觉不到了但车机上的泄漏是会不断累积的。导航开着、音乐开着、行车记录仪在后台录着多个应用长时间并发运行内存占用一旦控制不住系统就会被系统低内存Killer逐个杀进程最终表现为导航突然退出、中控卡死。选择题里出现过比较典型的内存泄漏场景我整理一下Handler持有Activity引用消息队列还有延迟消息。静态变量直接或间接持有Activity/View。匿名内部类/Runnable执行耗时任务时持有外部类引用。资源对象BroadcastReceiver、Cursor、IO流、Bitmap没有及时关闭和回收。单例模式中传入Activity作为Context。关于JVM垃圾回收的题目也不少比如问“哪些对象可以作为GC Roots”答案是虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。还有新生代和老年代的分配与回收策略Minor GC和Full GC的触发条件——如果平时只写业务代码这部分很容易答不全但车机团队优化应用内存时这些都是基本功。我记得有一道选择题印象比较深问的是“使用Bitmap加载大图时最合理的缩放方式是什么”。它给了几个选项用BitmapFactory.Options的inSampleSize进行采样缩小、直接用createScaledBitmap、用Matrix进行缩放、让ImageView自己设置adjustViewBounds。正确答案是按采样率缩小的方式因为inSampleSize在解码阶段就减少了对内存的占用而后面的方式都需要先在内存里加载完整图片——那大图本身就已经让内存爆掉了。后来我在车机项目里处理高精地图瓦片时发现这个思路是对的解码前就得控制尺寸不能指望事后缩放。3. 简答题那些“背了就会”又说不出原理的高频老题简答题部分把整个笔试的深度又拉了一个档次。选择题还能靠排除法和记忆蒙一蒙简答题是真的要把原理讲清楚写明白。这部分我复盘下来核心集中在Binder、View绘制、性能优化这三座大山上。3.1 Binder机制一次拷贝的哲学Binder是Android系统里最绕不开又最不好讲清楚的东西。小鹏的简答题基本都会涉及它比如让你结合AIDL描述Binder的通信流程、说明Binder为什么高效、说说Binder线程模型和普通线程池的区别。要答好这类题得先理清Binder的骨架Binder基于C/S架构Client、Server、ServiceManager上下文管理者三者的关系是ServiceManager管理各种系统服务的注册和查询所有跨进程访问都先从ServiceManager拿到Binder代理。Binder驱动工作在内核态负责数据的跨进程拷贝。传统IPC需要两次拷贝用户态到内核态内核态到用户态而Binder利用内存映射mmap只需要一次拷贝这是它高效的核心原因。AIDL只是方便开发者的封装底层本质是生成Proxy和Stub两个类通过transact()和onTransact()传递Parcelable对象。线程模型上Binder线程池是系统维护的从Binder线程池中取线程执行跨进程调用不是随便new一个Thread去跑。每次面试我都会建议候选人用“寄快递”来类比Binder你不需要自己开着车把东西送到对方手里只需要把包裹交给统一的快递中心Binder驱动快递中心通过地址mmap映射直接送到对方门口全程只中转一次。这个类比在笔试里写出来不是不行但最好还是把底层机制写扎实比如明确提到“Binder驱动在内核态通过mmap把数据从内核缓冲区映射到接收进程的用户空间避免了一次拷贝”。能写出这个细节阅卷人一眼就知道你是真懂还是背模板。3.2 View绘制流程从Measure到Draw的完整链路View绘制是Android开发进阶的一道分水岭。小鹏简答题里出现过一题描述View的measure、layout、draw三个阶段以及DecorView如何被添加到Window中。这题乍一看很简单但展开写其实内容非常多。先说整体链路应用启动后Activity通过Window的setContentView把布局文件解析成View树PhoneWindow里有一个DecorView作为整个View树的根。ViewRootImpl.performTraversals()是绘制的总入口它依次调用performMeasure、performLayout、performDraw三个方法把测量、布局、绘制三个阶段串联起来。Measure阶段的核心是MeasureSpec。它由两部分组成SpecMode和SpecSize。三种模式的含义要搞清楚UNSPECIFIED父容器不对子View施加任何约束子View想要多大就多大一般用于ScrollView、AdapterView的测量。EXACTLY父容器已经确定子View的精确大小对应match_parent和具体dp值。AT_MOST子View不能超过父容器给定的最大值对应wrap_content。实际开发中经常遇到的坑是wrap_content的自定义View如果没有在onMeasure里处理AT_MOST模式它会默认使用父容器可用的最大尺寸看起来和match_parent一样这就是网上很多人继承View后不重写onMeasure导致布局异常的原因。笔试如果要拿高分应该把这个场景写出来。Layout阶段是确定每个子View在父容器中的位置核心是layout(l, t, r, b)四个坐标。Draw阶段则调用dispatchDraw把背景、内容、子View、装饰逐层绘制出来。还有一个高频考点View.post方法为什么能拿到正确的宽高。原因在于View.post的Runnable会被封装成RunQueue的HandlerAction在View首次被attach到Window之后ViewRootImpl执行完measure和layout流程会通过RunQueue把已入队的Runnable发出去所以在onCreate里用View.post能拿到测量后的宽高而直接在onCreate里getWidth()拿到的还是0。3.3 性能优化布局优化与启动速度的实战逻辑性能优化这块小鹏的题从基础到偏门都有。最基础的比如如何减少布局层级、include和merge的区别、ViewStub的使用场景。这些都很容易答难的是把“为什么”说清楚。比如include和merge的区别常规答案是include用于布局复用merge用于减少嵌套层级。如果要拿高分得说清楚merge只能作为根标签使用当LayoutInflater解析到merge时它不会作为View添加而是直接把子View添加到父容器中所以它能减少一层不必要的FrameLayout。但如果merge不是根标签而是直接写在某个LinearLayout里它还能用吗答案是不能编译器会直接报错这也是很多人使用merge时容易踩的坑。再比如ViewStub它本质上是一个轻量级、不可见、不占布局位置的View。只有调用inflate()或setVisibility(View.VISIBLE)的时候才会真正加载布局。车机场景里这种按需加载特别实用——比如只有用户插上U盘才显示文件浏览器入口平时完全不渲染这块布局。启动速度优化的题也出现过比如如何减少冷启动白屏时间。除了常规的windowBackground设置启动图外更核心的思路是把Application的onCreate里的初始化任务按轻重缓急拆分能懒加载的绝不首屏加载能用IdleHandler延后处理的就放到主线程空闲时再跑。车机系统里启动速度就是用户坐进车里到中控可用的时间每压缩100毫秒体验的差别都是能感知的。4. 编程题笔试里真正拉开差距的算法与工程意识编程题部分是小鹏笔试里最能看出候选人与岗位匹配度的地方。题目难度中等偏上不像大厂校招那样追求竞赛级算法但也不是随手就能写出来的水平。4.1 从考生反馈拼出来的算法题画像因为原题不会公开发布但从多个渠道的面经汇总来看2020年秋招前后小鹏Android笔试里出现的算法题方向大概是这几种链表反转包括K个一组反转数组中找第K大的数LRU缓存淘汰算法字符串括号匹配/最长有效括号两个有序数组合并二叉树层次遍历这些题目都不算冷门但放在笔试里有一个很重要的信号小鹏考算法不追求偏题怪题而是考察最基本的代码功底和复杂度分析能力。比如LRU缓存它天然适合用来考察候选人对哈希表双向链表的理解也直接对应到车机上图片缓存、导航数据缓存的实现场景。4.2 一道典型编程题手写LRU缓存我拿LRU缓存为例写一下笔试时候比较稳妥的实现思路。需求很简单设计一个支持get和put操作的数据结构容量满时淘汰最久未使用的数据get和put的时间复杂度都要是O(1)。核心思路是哈希表双向链表。哈希表保证O(1)查找双向链表保证O(1)删除和移动。每次get命中时把节点移动到链表头部每次put新节点时加到头部如果容量满了删除尾节点。import java.util.HashMap; import java.util.Map; class LRUCache { private static class Node { int key; int value; Node prev; Node next; Node(int key, int value) { this.key key; this.value value; } } private final int capacity; private final Node head new Node(-1, -1); // 虚拟头节点 private final Node tail new Node(-1, -1); // 虚拟尾节点 private final MapInteger, Node map new HashMap(); public LRUCache(int capacity) { this.capacity capacity; head.next tail; tail.prev head; } public int get(int key) { Node node map.get(key); if (node null) { return -1; } moveToHead(node); return node.value; } public void put(int key, int value) { Node node map.get(key); if (node ! null) { node.value value; moveToHead(node); return; } Node newNode new Node(key, value); map.put(key, newNode); addToHead(newNode); if (map.size() capacity) { Node last removeTail(); map.remove(last.key); } } private void moveToHead(Node node) { removeNode(node); addToHead(node); } private void addToHead(Node node) { Node next head.next; node.next next; next.prev node; head.next node; node.prev head; } private void removeNode(Node node) { Node prev node.prev; Node next node.next; prev.next next; next.prev prev; } private Node removeTail() { Node last tail.prev; removeNode(last); return last; } }写这个题有几个拿分细节第一虚拟头尾节点的使用能省掉大量边界判断第二put已存在key时的处理是更新value再移动第三容量满时删除尾节点的同时要记得从map里移除。代码风格干净、逻辑完整这些在阅卷时都很加分。4.3 从编程题看工程能力不只要“跑通”还要“会分析”笔试编程题其实不只看你会不会写还看你题目做完之后有没有复杂度的概念。比如考题要求只输出第K大的数你可能会想到先排序再取下标复杂度是O(n log n)。但如果你能写出基于快速选择或堆的O(n log k)方案说明你考虑过大数据量下的性能问题——这正是车机场景需要的高并发、大数据处理意识。我见过很多候选人笔试算法题都能AC但一问“这个思路的时间复杂度和空间复杂度是多少”就卡壳。这在普通App团队可能还能忍但车机团队不行。车机上导航地图的数据量动辄几十MB蓝牙音乐列表、行车日志同样是大数据量场景。你能写出O(n²)的代码也许功能没问题但在车机这种低功耗、低内存的设备上它可能跑不起来。所以备考时一定要养成写完代码顺口分析复杂度的习惯这比多做十道题都管用。5. 从笔试反馈看小鹏秋招的“命中率”与备考策略5.1 牛客网上的反馈和实际体感笔试之后我也逛了一圈牛客网看了很多人的反馈。整体评价是“题量适中但难度集中”选择题部分合格线并不高简答题和编程题才是拉分项。有不少人栽在简答题上原因是平时只准备了一些高频面试题没有真正理解过原理临时组织语言写出来很苍白。小鹏的笔试还有一个特点非技术因素也会影响分数。比如编程题如果只用System.out.println瞎试答案、代码风格潦草混乱、变量命名随意这些在自动评测和人工阅卷时都会留下不好的印象。我甚至见过同一个人写的两套解法代码风格差别巨大的情况——一套是抄的一套是自己写的阅卷人一眼就能看出来。所以笔试时尽量把每个变量名都起清楚把注释写到位哪怕题目做完了也要回头把代码格式整理一遍这个习惯能从细节上帮你多拿一些印象分。5.2 小鹏笔试与面试的交互逻辑笔试其实在给面试埋伏笔复盘之后我发现小鹏笔试有一个很明显的策略笔试题目和面试官后续追问的方向是高度一致的。笔试里你答得不深不透的题目面试时大概率会被再次拎出来深挖。所以笔试不是考完就结束它其实就是一份“预筛简历”面试官会拿着它来寻找你的知识边界在哪里。比如笔试里有个简答题让你谈对Handler消息机制的理解你如果只写了“主线程不能做耗时操作否则会ANR”这个结论面试时面试官就会追问为什么耗时操作会导致ANRANR的超时机制是怎么实现的主线程的Looper什么时候退出这些连环追问会把“背过答案”和“真的会”区分得干干净净。所以备考时不要只背结论要沿着每个考点往下深挖两层把原理的链条补完整。5.3 车机方向Android笔试的通用备考路线结合小鹏这套笔试的特点我给准备投递车机/智能座舱方向的同学一个备考路线建议不一定全面但方向是对的第一先把Java基础打牢。集合源码HashMap、ArrayList、LinkedHashMap要自己读过源码JVM内存区域和GC回收流程要能画出图来。车机系统很多代码是Java和C混编的如果Java基础不扎实后面看Framework源码会非常吃力。第二Android基础八股要过一遍但标准不是“会背”而是“能推导”。Activity的启动流程从startActivity到AMS再到ActivityThread的调用链要能口述出来Activity和Window、DecorView、ViewRootImpl之间的关系要理清楚Handler的整个消息循环机制要知道每一步发生的线程上下文。第三性能优化一定要投入大量时间。车机对性能的要求比手机高一个量级。布局优化、内存优化、卡顿优化、启动提速、耗电优化这五个方向要每个都能说出具体手段和检测工具。Android Studio自带的Profile工具链CPU Profiler、Memory Profiler、Layout Inspector一定要会看因为笔试过了候选人进入面试面试官大概率会让你现场分析一段卡顿或内存泄漏的代码。第四算法刷题不要奔着竞赛难度去把LeetCode上链表、二叉树、哈希表、堆、栈、队列、动态规划、贪心的高频题刷明白就够了。重点是把“思路-复杂度-边界条件”这个分析习惯养好笔试时就会稳很多。第五有条件的话自己折腾一下AOSP源码编译哪怕只是跑一个模拟器镜像或者用repo同步一次源码。这个过程能帮你建立对系统级工程的整体认知很多Framework层的概念在写完源码后会有豁然开朗的感觉。对于想进车机团队的人这一点尤其加分——因为它代表你有能力接触和维护大型系统项目而不只是写应用。回到那套2020年的秋招笔试题本身它放在今天来看依然有很强的参考价值。车机赛道现在竞争更激烈了但核心能力模型没有变底层原理要扎实性能意识要刻进骨子里代码功底要干净利落。如果你正准备投类似岗位不妨按我上面列的这些点一个一个去自查。能把这些内容都弄扎实了不管笔试形式怎么变你都稳得住。最后再分享一个我自己的备考体会笔试前不要只刷题不看书也尽量不要只看书不刷题二者要交叉着来。刷题过程中遇到的每个模糊知识点都花半小时把它彻底搞懂再把结论写进自己的笔记里这个做法比考前突击一个月都管用。
返回列表