收到小满春招Android研发岗第一批笔试邀请的时候,我正在一家中厂做应用开发,日常主要写业务组件和性能优化,自认为Android基础还算扎实。但看到HR发来的在线笔试链接,用90分钟做完之后,我发现自己对“Android开发”的理解还是太窄了。这套卷子不是常见的“背八股”,而是把四大组件、Handler、Framework、Binder、构建工具链、甚至OTA和底层系统组件全部揉在一起考,很多题看着眼熟,真正落笔才发现自己只是“知道”而不是“理解”。考完和朋友对完答案,趁着记忆还热乎,我把它整理成一份回忆版复盘,希望能给后面投递小满或者其他中大型公司Android岗位的同学一点参考。
1. 笔试总览:先看清这套卷子的“出题脾气”
1.1 考试安排与题型分布
小满的笔试是在一个在线考试系统里完成的,整体时长90分钟,总分100分,题量不小。我看了一眼大致分布,题型可以分成四块:
| 题型 | 题量 | 大致分值 | 主要考察方向 |
|---|---|---|---|
| 单选题 | 10题 | 每题3分 | Java/Kotlin语言、四大组件、Handler、进程优先级 |
| 多选题 | 5题 | 每题5分 | Binder、View绘制、构建工具链、广播机制 |
| 简答题 | 3题 | 每题10分 | AMS/WMS职责、AGP版本兼容、R8混淆策略 |
| 开放设计题 | 1题 | 15分 | 系统升级/蓝牙音频/文件共享等真实场景设计 |
这个结构其实已经暗示了这家公司的技术栈倾向:不是单纯的业务开发,而是很看重候选人对Android整个系统体系的理解。单选题里大量出现“以下哪个进程优先级最高”“Handler的MessageQueue在什么时候阻塞”,多选题里频繁出现“R8会做哪些事情”“AGP 8.0带来了哪些破坏性变化”。如果你平时只用Android Studio点Run、靠AS自动打Release包,这些题基本靠蒙。
1.2 从高频搜索词反推考点覆盖
这里有个很有意思的细节。考试前后我在Android开发者社区看到很多人搜索“android studio hedgehog支持agp8吗”“android r8”“android ams”“android ota”“android apex”这类关键词,说明这套题确实打中了很多人的盲区。我当时也顺手搜了不少,结合回忆的试题,整理出一张考点地图:
- 语言基础(约20%):Kotlin的lateinit和by lazy、协程调度器、Java线程池
- 四组件与消息机制(约30%):Activity生命周期与进程优先级、Handler/Looper/MessageQueue、ContentProvider和FileProvider、BroadcastReceiver
- Framework与IPC(约25%):AMS/WMS、Binder传输限制、Zygote/SystemServer启动链
- 构建与工程化(约15%):Android Studio版本与AGP对应关系、R8和ProGuard差异、Resource适配
- 开放场景(约10%):OTA升级回滚、蓝牙音频焦点、Android 11+文件分享
说实话,前两项是传统笔试重点,多数Android开发者都能答个七七八八。但后三项明显偏系统向和工程化,如果你平时只做应用层,不关注Gradle插件版本、不研究R8规则、不读AOSP源码,很容易在这几块翻车。这也是我写这篇复盘的核心原因:三年前我自己面试的时候,考的还是“Activity四种启动模式”“handler内存泄漏为什么”,现在这批题已经明显换血了。
2. 选择题复盘:那些让人纠结的“送分题”与“陷阱题”
2.1 进程优先级、生命周期和回收顺序
单选题第一题就问:“用户按Home键之后,App内没有可见Activity、没有前台服务、没有正在运行的广播,系统内存吃紧时,这个进程属于哪个优先级?”我差点选了“后台进程”就提交,后来仔细看了一眼选项,发现还有“缓存进程”这个词。
Android的进程优先级从高到低大致是:前台进程、可见进程、服务进程、后台进程、空进程。按Home键后Activity走onPause和onStop,此时如果没有其他组件,进程确实属于后台进程。但如果Activity只是被对话框遮挡了一部分,还处在onPause但没有onStop的状态,那就属于“可见进程”,优先级会高很多。这道题真正想考察的,是你能不能把Activity生命周期和进程五级模型对应起来,而不是背出五个名词。
我答题的时候用的方法是先画生命周期图再填优先级:onResume对应当前可交互,属于前台进程;onPause未onStop,窗口仍可见,属于可见进程;onStop之后才降级到后台进程。如果你的App里同时有一个正在播放音乐的MusicService,那进程会被拉到“服务进程”档,比后台进程不容易被回收。这个知识点在工作中同样重要,很多“App被杀”“WebView频繁重建”的问题,归根结底都是进程优先级和组件生命周期没有配合好。
2.2 FileProvider与content://协议的前世今生
有一道题出得很实际:“应用A要把一张图片分享给应用B,A使用了FileProvider,最终分享出去的URI是什么格式?为什么不能直接传file:///storage/emulated/0/...路径?”选项里有content://、file://、http://、content://com.xxx.fileprovider/...。
这个知识点一定要和Android 7.0的行为变更绑在一起记。从Android 7.0开始,应用间直接暴露file://URI会直接抛FileUriExposedException,因为系统认为这种方式不安全——file://暴露的是绝对路径,接收方拿到后可以绕过权限访问同一个文件的其他部分,而且很容易被恶意应用利用。FileProvider本质上是ContentProvider的子类,它把底层文件路径映射成一个content://URI,同时通过Intent的FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION,临时授权给接收方。
我答完这道题后专门去翻了一下真实App的manifest,很多大厂的FileProvider authority都是“包名.fileprovider”,比如你在搜索引擎里偶尔能看到类似content://com.xxx.searchbox.fileprovider/baiddpath/...这样的日志,这就是FILE provider在做跨进程文件分享时留下的URI痕迹。实际配置的时候要在AndroidManifest里声明androidx.core.content.FileProvider,然后在res/xml里放一个file_paths.xml,把需要暴露的目录列出来,不能图省事直接暴露整个外部存储。
2.3 Kotlin/协程细节题:lateinit、by lazy和Dispatchers.IO
这套卷子对Kotlin的考察不是简单语法,而是偏“什么时候选择哪个”。有一道多选题问“关于lateinit和by lazy的说法,哪些正确”,选项里有“lateinit适用于var,by lazy适用于val”“lateinit不能用于Int等基本类型”“by lazy默认线程安全”“两者都是编译期检查”。
前三个选项都是对的。lateinit编译期只是做了声明,运行时如果没初始化就访问,会抛UninitializedPropertyAccessException;它没法用于基本类型,因为基本类型有默认值,无法通过“是否初始化”来做区分。by lazy是委托属性,第一次访问时才执行初始化代码,默认使用LazyThreadSafetyMode.SYNCHRONIZED,底层有锁,线程安全。这个题区分度很高,因为很多人只记得“lateinit用于var,lazy用于val”,不知道基本类型限制和线程安全细节。
协程那道题则考到Dispatchers.IO的底层实现:“频繁使用Dispatchers.IO会不会耗尽线程资源?”说实话这种题如果没读过源码,很容易凭感觉答。Dispatchers.IO底层是一个按需伸缩的线程池,和Dispatchers.Default共用了协程调度器里的机制,但IO调度器会根据任务需要创建新的线程,极限情况下线程数量可以很大,所以它适合IO密集任务,但不能拿来做CPU密集运算。我当时的答题策略是强调“IO调度器是弹性线程池,Default是固定为CPU核心数的线程池,两者适合的场景不同”,这个答案在面试里也经常被追问,建议系统看一下kotlinx.coroutines里CoroutineScheduler的实现。
3. Framework题解析:AMS、Binder和系统启动链路
3.1 AMS/WMS职责边界:别把“管理Activity”和“管理窗口”混为一谈
简答题第一题我记得很清楚:“简述ActivityManagerService和WindowManagerService各自的职责,以及它们在一次Activity启动过程中如何配合。”这题看起来基础,实际上对源码没读过的人很难答出层级感。
AMS负责的是“Activity的一生”:包括组件调度、生命周期状态机、任务栈管理、进程优先级更新。ActivityThread启动后,通过Binder拿到AMS的代理,然后调用ApplicationThread的scheduleLaunchActivity,最终回到主线程的Handler执行启动流程。WMS负责的是“窗口的一生”:每个Activity对应一个PhoneWindow和DecorView,WMS管理窗口的层级、尺寸、焦点以及输入事件分发。
两者的配合点在于:Activity启动时,AMS会先确保进程存在,再通知ActivityThread创建Activity,而Activity创建后调用WindowManager.addView,这个过程会跨进程通知WMS添加窗口。窗口加完之后才能开始测量、布局、绘制。我在答题时特意强调了“AMS是逻辑管理者,WMS是界面呈现者”,并且补充了从Android 10开始,Activity任务栈相关职责下沉到ActivityTaskManagerService,这一句话在简答题里很加分,因为大多数人的知识体系还是停在AMS只用来管Activity的旧版本。
3.2 Binder传输限制与TransactionTooLargeException
有一个多选题直接问:“Binder传输的数据量限制是多大?避免TransactionTooLargeException的正确做法有哪些?”我记得选项里有1MB、4KB、64KB、无限制,以及“避免在Intent里塞大Bitmap”“用文件或共享内存代替跨进程大数据”“用oneway异步消息”……
Binder事务缓冲区是通过mmap映射的,默认大小是1MB,但这个1MB不是让你随便用的,系统本身需要保留一部分空间,所以每次事务的实际可用上限会小于1MB。如果你把一张2MB的Bitmap塞进Bundle,再通过Intent传给另一个Activity,很可能就爆了。有人会说“那我把Bitmap放在一个静态变量里不就行了”,这在跨进程场景下根本不管用,因为静态变量只在当前进程可见。正确做法是将Bitmap先存成文件,然后传URI,接收方再解析。
至于oneway,它确实是异步Binder调用,发送方调完立刻返回,不阻塞,但接收方不能返回结果。这道题我给的建议是,凡是跨进程传数据,第一反应先想大小,超过几百KB就换文件或共享内存。这个习惯在很多公司实际代码评审里也是检查重点。
3.3 Zygote、SystemServer与OTA、APEX的底层关联
这套卷子还有一道“超纲”多选:“关于Android系统启动过程,下列说法正确的是?”选项有“Zygote由init进程启动后创建SystemServer”“SystemServer里面运行着AMS、WMS、PackageManagerService等服务”“APEX包可以用于OTA更新系统组件”“OTA升级失败后系统必须整机重装”。
前三个是对的,第四个是错的。Zygote由init进程启动,它预加载了很多类和资源,后续所有应用进程都由它fork出来;SystemServer是Zygote fork的第一个Java进程,在这个进程里会启动SystemServiceManager,然后创建各种核心服务,包括我们前面说的AMS、WMS、PackageManagerService等。
APEX和OTA的关联是我当时复习时漏掉的部分。Android 10开始引入APEX格式,它可以看成是“可更新的系统组件包”,但和普通APK不同,APEX可以被Linux内核直接挂载,系统适配后能通过OTA机制单独更新一些底层模块(比如媒体编解码器、Conscrypt安全组件),不需要完整整机升级。OTA则是整个系统的升级通道,现代设备一般用A/B分区方案:新系统写到备用分区,重启后切换,如果启动失败会自动回滚到旧分区。这题让我意识到,Android研发岗的笔试边界正在从“应用开发”慢慢扩大到“系统开发”,尤其是有车机业务的公司,这种题会越来越多。
4. 工程化考点:AGP版本、R8混淆和构建脚本里的坑
4.1 Android Studio版本与AGP版本兼容表
工程化方向的简答题是:“你正在使用Android Studio Hedgehog 2023.1.1 Patch 2,项目是否可以直接使用AGP 8.x?为什么?”这题考察的不是手机号一样的硬背,而是构建工具链的依赖关系。
先给一个我当时整理的对应表:
| Android Studio版本 | AGP推荐版本 | 最低Gradle版本 | 需要JDK |
|---|---|---|---|
| Android Studio Flamingo | AGP 8.0 | Gradle 8.0 | JDK 17 |
| Android Studio Giraffe | AGP 8.1 | Gradle 8.0 | JDK 17 |
| Android Studio Hedgehog | AGP 8.2 | Gradle 8.2 | JDK 17 |
| Android Studio Iguana | AGP 8.3 | Gradle 8.4 | JDK 17 |
Hedgehog 2023.1.1 Patch 2这个IDE版本按Google的兼容策略,可以支持的AGP范围主要是8.1到8.2,虽然理论上你手动指定到AGP 8.3甚至更高也能跑,但官方兼容性测试一般只覆盖到推荐版本,超过之后容易出现构建缓存、IDE侧插件不匹配等奇怪问题。所以我的结论是:可以用AGP 8.x,推荐直接用8.2,配合Gradle 8.2和JDK 17,这是最稳的组合。
如果你是在老项目上升级AGP,需要注意AGP 8.0开始有几条硬性变化:AndroidManifest里的package不能再写了,必须移到build.gradle里的namespace;R类不再可传递,子模块必须通过显式import访问其他模块的R;默认开启R8,而且内置的Kotlin支持会让你重新调整kotlin-android插件的配置方式。这些不是“选做题”,是很现实的升级坑,我身边好几个同事升级完项目编译报错,都是卡在namespace和R类这些点。
4.2 R8和ProGuard:别再把它们划等号
简答题里有一道是:“R8在Release构建中做了哪些事情?如果某个类因为反射被误删,你如何保留?”我答题时先讲概念:R8不是一个单纯的混淆器,它在执行时同时负责压缩(shrinking)、优化(optimization)、混淆(obfuscation)和脱糖(desugaring)。ProGuard主要就是压缩和混淆,R8在AGP 8.0之后是默认的代码处理器,并且和D8/R8工具链深度集成。
如果遇到反射调用,最容易踩的坑是:Java/Kotlin代码里通过字符串反射拿到一个类的方法,R8只看静态调用关系,不知道这个字符串对应的类名,于是把目标类当作无用类删掉。这时候要在proguard-rules.pro里加keep规则:
-keep class com.example.api.** { *; } -keepattributes Signature -keep class **.R$* -keepclassmembers class com.example.api.User { public <methods>; }-keep会把匹配到的类及其成员全部保留,-keepclassmembers只保留被指定的成员。加规则之后要用mapping.txt和usage.txt去验证最终包里的类是否还在,release包崩溃时也要能根据mapping反混淆堆栈。很多开发者在本地跑没问题,上了线上就崩,最后导出mapping查一下,基本都是keep规则没写全。
还有一个冷知识点:R8的优化模式比ProGuard激进,如果代码里大量使用Gson/Jackson等基于反射的序列化库,一定要给模型类加上keep规则。我记得网上有人遇到某个版本更新后release包突然解析不了服务端返回的数据,最后发现是R8优化把Gson泛型签名信息给剥离了,导致TypeToken匹配失败。所以在工程化方向,我的建议是:每次升级AGP后,用release包做一轮全量回归,重点检查第三方SDK的动态初始化。
4.3 主题图标、动态图标和Resources适配
选择题里有一道是:“Android 13上用户开启主题图标(Themed Icons)后,系统用的是什么机制?”代码库里有AndroidManifest里的<adaptive-icon>定义,我答了这个机制。这里要展开讲一下:
自适应图标分为三层:background、foreground,以及Android 13新加的monochrome。monochrome层是单色矢量图形,系统在用户开启主题图标时,会根据壁纸取色对monochrome做着色,替换掉原来的彩色图标,形成桌面统一色调。如果你的App只提供了background和foreground而没有monochrome,在Android 13的Pixel设备上主题图标会失效,桌面仍然显示旧图标。
如果你看到“android动态图标主题”这个热词,它其实还包括两类东西:一类是ShortcutManager动态快捷方式,可以在长按图标时创建动态入口;另一类是应用内通过AlarmManager定时切换桌面图标Icon,这种玩法在日历、天气类App里比较常见。但系统层面真正的新能力是monochrome和主题图标,这两个概念容易混,建议在简历里不要写“支持动态图标”这种模糊话术,要说清楚是主题图标还是shortcut。
5. 场景设计题:从文件分享、OTA到蓝牙音频切换
5.1 Android 11以上实现文件下载与分享的完整思路
开放设计题给了一个非常具体的场景:“用户下载了一份PDF,点击预览时希望调用系统或其他应用打开,请你设计实现方案,并说明不同场景下的权限适配。”这道题没有标准答案,但得分点非常清晰。
我当时的思路分三层:第一层是“下载到哪”,第二层是“如何授权给其他应用”,第三层是“如何兼顾不同Android版本”。
下载到哪,普遍做法是把文件写到getExternalFilesDir()应用专属目录,好处是不需要申请存储权限,卸载App时系统会自动清理;坏处是其他应用默认无法访问。如果要写进公共Downloads目录,就需要MediaStore.Downloads,Android 10及以上可以用MediaStore插入,Android 11及以上推荐用MediaStore.Downloads这个集合。这个差异本身就是考点,很多人会直接写“申请权限然后写公共存储”,这在Android 11之后已经被系统限制得很严,正确姿势是先插入MediaStore再拿Uri,通过OutputStream写内容。
打开和分享时,最稳妥的方案是把文件Uri交给FileProvider,生成权限URI,再通过Intent的setDataAndType配合Intent.FLAG_GRANT_READ_URI_PERMISSION启动第三方Viewer。如果目标是预览,也可以用Intent.ACTION_VIEW;如果需要持久化内容,可以考虑使用SAF(Storage Access Framework)让用户指定目录。我答题的时候还特意提到了一个权衡点:临时分享给其他应用一次,用FileProvider最合适;如果要让用户长期管理文件,用MediaStore和SAF更好;千万不要再直接使用file://,这在Android 7.0以后就是非法路径。
5.2 面向车机/系统开发:OTA失败回滚与i2c-tools
小满这套卷子里还有一道“另类”的开放题:“在一个已量产的Android车机系统上,你想通过OTA推一个新版本,如果新版本启动失败,如何保证设备不变成砖?”
这个题明显是在招系统方向或者偏底层的人。我当时的回答分三步:
第一步,分区方案。现代Android设备基本都会用A/B分区,系统固件同时存在A槽和B槽,OTA把新版本写入当前未使用的槽位。升级完成后,重启引导器切换到新槽位启动,如果启动失败或者连续几次启动没有进系统,引导器会自动切换回旧槽位。这个机制对应的HAL接口是boot_control,它还负责记录当前槽位和重试次数。
第二步,更新引擎。update_engine在写入新版本前会做签名校验、dm-verity校验和OTA包完整性校验。车辆场景下更加严格,因为车内电子控制单元(ECU)和IVI系统常常需要联动,所以一般会要求升级过程不能断电,并且要有擦除后恢复的能力。
第三步,应用层回滚。就算系统分区启动成功了,App层也可能因为数据库版本不兼容导致业务崩溃,所以车机OTA方案里通常会设计“软件包版本和业务数据版本”一起校验,发现业务数据无法兼容时,恢复到上一个版本的备份数据。这个设计在手机厂商的云端升级中也很常见。
我在回答里还提到了一个工程工具:i2c-tools。在Android系统上如果要做硬件调试,比如读取屏幕、触摸芯片、传感器寄存器,通常需要把i2c-tools交叉编译进系统镜像,然后在adb shell里使用i2cdetect、i2cget、i2cset这些命令。如果你有“android openocd”“android ota”这些搜索词出现在面试记录里,说明对方在考察你愿不愿意往下钻到硬件层。这个方向不一定人人要做,但系统工程师岗位基本必问。
5.3 蓝牙音频切换和AudioFocus的联动
还有一道开放题是:“音乐类App在用户连接或断开蓝牙耳机时,如何做到自动暂停/恢复播放?”这题结合了设备监听和音频焦点,非常好地考察了实际开发能力。
我的方案分成两个部分。第一部分是监听音频设备变化,使用AudioManager的registerAudioDeviceCallback,当音频设备列表变化时,回调里检查当前是否有蓝牙A2DP设备处于连接状态。如果从有线耳机切到蓝牙耳机,或者从蓝牙切回扬声器,App会收到通知,然后决定暂停还是恢复播放。
第二部分是处理AudioFocus。播放音乐前应该先申请音频焦点,使用AudioManager.requestAudioFocus,并且处理OnAudioFocusChangeListener回调。如果焦点被其他App抢走,比如导航语音开始播报,音乐应该暂停;焦点暂时丢失但允许duck,可以降低音量;焦点重新被授予时,再恢复播放。蓝牙设备断开本身也会触发系统音频焦点变化,很多手机在蓝牙断开时会暂停当前媒体播放,App层如果不对焦点做处理,会出现播放器状态和系统音量状态不一致的bug。
我给的代码关键伪码大致是这样:
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); am.registerAudioDeviceCallback(new AudioDeviceCallback() { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { // 检查是否有TYPE_BLUETOOTH_A2DP // 有则播放 } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { // 蓝牙设备移除,主动暂停播放 } }, null); AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) .setOnAudioFocusChangeListener(focusChangeListener) .build(); am.requestAudioFocus(request);这个场景我还特意联系了一下热词“android蓝牙”,因为在蓝牙耳机连接状态下,出现“音量自动跳变”“播放暂停异常”这类问题,十有八九不是设备的问题,而是App没有处理好音频焦点和音频设备回调的联动。这种题就是用来区分“会写代码”和“会处理真实问题”的。
5.4 CoordinatorLayout+Banner:嵌套滚动的经典实战
最后一道代码相关的题目是:“页面顶部有一张Banner,下面是一个信息流列表,要求上滑列表时Banner先被收回,下拉时再展开,你会怎么做?”
这题一看就是CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout + RecyclerView的组合。关键点在于滚动联动机制,而不是说你直接把布局文件里写上CollapsingToolbarLayout就完事。
要理解原理,必须先理解嵌套滚动机制。CoordinatorLayout是通过Behavior实现联动规则的,AppBarLayout有一个默认的ScrollingViewBehavior,它会监听RecyclerView滚动,同时AppBarLayout内部的子View通过layout_scrollFlags声明自己如何响应滚动。典型的配置是Banner所在的那个CollapsingToolbarLayout设置app:layout_scrollFlags="scroll|exitUntilCollapsed",下面的RecyclerView设置app:layout_behavior="@string/appbar_scrolling_view_behavior",这样才能把滚动事件传给AppBarLayout。
实际开发中会踩到两个坑:一是ViewPager2和CoordinatorLayout的嵌套滚动存在冲突,因为ViewPager2本身也是NestedScrollingChild,它会把子RecyclerView的滚动事件“吞掉”,导致顶部的AppBarLayout不响应;解决方法一般是自定义Behavior,或者在ViewPager2之外单独处理滚动。二是Banner内部的轮播定时器在页面滑动时容易误触,常用做法是在列表滚动时暂停自动轮播,停止滚动后恢复,需要复写RecyclerView的addOnScrollListener做一个标志位。这道题如果你能答出Behavior、scrollFlags、嵌套滚动事件分发的调用链,基本能拿到大部分分数。
6. 考后复盘:这套题透露的Android招聘风向
6.1 哪些题是真实业务里每天都能用上的
考完回顾一下,这套卷子的设计比很多“纯八股”笔试要认真得多。FileProvider、Binder传输限制、R8混淆、协调布局+Banner,这些是日常开发里高频遇到的东西,任何一个有三年经验的Android开发都不可能避开。它们真正想考察的不是“背答案”,而是你有没有在真实项目里思考过“为什么”。
比如Binder传输限制,大多数人只在报错的时候才去查TransactionTooLargeException,但如果你在设计AIDL接口时就已经知道跨进程数据量有限制,你会主动把Bitmap转成文件路径再传递,这种意识就是区分普通开发者和高级开发者的地方。再比如R8混淆,本地Debug跑得好好的,Release一打开闪退,很多新手第一反应是“写得好好的怎么崩了”,老手会直接看mapping.txt定位到被混淆的类,这就是能不能把构建工具链吃透的差距。
6.2 给下一批投递者的备考建议
如果你准备投递Android研发岗,尤其是小满这种明显带系统倾向的岗位,我建议按下面几条查漏补缺:
- 语言基础别只刷Java面试题,Kotlin协程要读底层源码。线程池和协程调度器的区别、Dispatchers.IO的实现,已经在笔试题里反复出现。
- 四大组件、Handler、Binder三件套必须能联系源码讲清楚。从Activity启动到窗口显示,中间经过AMS/WMS/Binder,这条链路要能画出来。
- 工程化能力要跟上。Android Studio版本、AGP版本、Gradle版本、JDK版本这一套依赖关系,至少要有一张自己的兼容表;R8和ProGuard规则至少要会写keep。
- 系统方向的知识不是超纲。OTA、APEX、启动流程、文件系统权限,这些开始成为中大型公司Android岗位的正常考察范围,尤其是车机、系统组、性能优化组相关岗位。
- 实战场景题最好提前准备一套自己的套路。文件分享、音频焦点、嵌套滚动、数据恢复,每个方向都整理一个“从问题出发、到方案设计、再到版本适配”的回答框架,比临时组织语言强很多。
最后再分享一个从这次笔试里悟出来的土办法:不要只看《Android开发艺术探索》和面试题,至少要把自己机器上的Android SDK目录翻一翻,读一读android.jar里的系统类源码,把LayoutInflater、Handler、Binder这些类的注释看一遍。很多笔试里你觉得偏、觉得难的知识点,其实AOSP源码和官方文档里都写得很清楚,只是以前没花时间去看。小满这套题不算刁钻,但它逼着你把“Android开发”从一个IDE按钮变成了一整个系统,这种思维转变,可能比笔试成绩本身更有价值。