金三银四跳槽季刚过,不少朋友在后台留言问安卓面试到底该怎么准备。说实话,看了几十份简历和面试反馈之后,我发现很多人不是技术不行,而是根本不知道面试官在问什么、为什么这么问。安卓面试题这个池子看起来很大,翻来翻去无非是Java基础、四大组件、Handler、性能优化、协程那几座山头。但每座山头的考法年年都在变,今年明显更偏向原理深挖、项目落地和全链路思考,再也不是背几道八股文就能混过去的时候了。
这篇文章不打算给你列一个几百道的题库,那没意义。我更想做的事,是把安卓面试里最核心、最高频的几大模块拆开揉碎,讲清楚每道题背后的考察意图、底层原理和面试官真正想听到的回答层次。不管你是准备校招的应届生,还是想跳槽涨薪的初中级工程师,只要把这套框架吃透,面试时的底气会完全不一样。
1. 面试前先搞清楚:安卓面试到底在考什么
1.1 技术考察的四个维度
安卓面试和纯后端面试有个很明显的区别:安卓岗位既要考察计算机基础,又要考察移动端特有的技术栈,还会夹杂大量项目经验和问题排查能力的验证。我面过很多人,也被人面过,总结下来,技术面基本围绕四个维度展开。
第一个维度是语言基础,主要看Java和Kotlin的掌握程度。这里不只是问语法,更关注你对内存模型、集合源码、并发机制、泛型擦除这类底层知识的理解。很多候选人能背出HashMap的put流程,但问到“为什么红黑树阈值是8”就卡壳了。这类问题其实没有标准答案,面试官想听的是你有没有思考过这个数字背后的概率统计和时间复杂度权衡。
第二个维度是安卓框架核心,包括Activity、Service、BroadcastReceiver、ContentProvider这四大组件,以及Handler、Binder、View绘制、事件分发、动画、Fragment等高频知识点。这是安卓面试的绝对主战场,几乎每一轮技术面都会从这里出题,而且出题方式越来越刁钻。比如“onSaveInstanceState的触发时机和局限”“为什么Handler的enqueueMessage不需要线程安全保护”“View.post为什么能在布局前拿到宽高”这类交叉问题,单纯背答案很难答好。
第三个维度是性能优化与稳定性,包含内存泄漏、卡顿优化、启动优化、Apk瘦身、ANR处理、网络优化等。这一块最能区分候选人的真实水平,因为很多问题是线上环境才会暴露的。面试官不满足于你背出工具名,更想听你“发现过什么问题→用什么工具定位→怎么解决→效果如何验证”的完整闭环。
第四个维度是工程化与架构设计,包括组件化、插件化、热修复、Jetpack全家桶、MVVM架构、协程、依赖注入、Gradle构建等。这部分考察的是你有没有大型项目的经验,能不能在复杂业务中做出合理的技术选型和架构设计。我见过不少候选人张口就是“我们项目用了MVP”,但问到他MVP和MVVM的本质区别、双向绑定怎么实现、会不会带来额外内存开销时,就支支吾吾了。
1.2 挂在简历和自我介绍上的常见问题
技术之外,我发现很多人在简历和自我介绍环节就输了一半。安卓面试的简历不是越长越好,也不是技术名词堆得越多越好。我收到过一份简历,七八个项目全部写“负责xx模块开发”,没有任何数据、没有技术难点、没有解决思路。这种简历在面试官眼里和一张白纸差不多,面起来也只能问基础题,问不出深度。
真正有效的简历写法是“项目背景+我的职责+技术难点+解决思路+数据结果”五段式。举个例子,不要写“负责首页模块开发”,要写“首页首屏加载从2.1s优化到0.9s,通过懒加载、预创建、布局异步inflate三种手段配合实现”。这样写的好处是面试官会顺着你的项目经历问具体细节,而细节恰恰是你真正做过的话最能答好的地方。
自我介绍也是一样,60到90秒就够,别把校园经历、兴趣爱好长篇大论念一遍。核心逻辑是:我是谁、做过什么、最擅长什么、为什么适合这个岗位。把你最强的技术点和项目亮点前置,引导面试官往你准备最深的方向问。
2. 必考模块深拆:Activity与任务栈背后的设计逻辑
2.1 启动模式不是背四种模式,而是理解任务栈
Activity的四种启动模式——standard、singleTop、singleTask、singleInstance,几乎是安卓面试的送分题。但正因为是送分题,很多候选人反而答得很浅。面试官问“singleTask和singleInstance有什么区别”,很多人只会说“singleTask是栈内复用,singleInstance是单独一个任务栈”。这没错,但远远不够。
我更建议按这个层次来答:先从任务栈的基本概念讲起,说明Task是一组Activity的集合,由Affinity决定归属;然后分别说明四种模式的进入条件、行为结果和典型使用场景;最后补上一些容易被忽略的坑。比如singleTask模式启动时会回调onNewIntent而非onCreate,如果不在onNewIntent里更新Intent数据,页面拿到的是旧数据。再比如设置了singleTask的Activity,不会因为这个模式自动清空上面的Activity,需要配FLAG_ACTIVITY_CLEAR_TOP才有清除效果。
场景举例也很重要。singleTop适合消息推送跳转这种可能重复打开的业务,比如通知栏点了三次,只保留一个实例。singleTask适合App主页或者需要全局唯一的页面,比如WebView容器页。singleInstance则适合需要和主进程隔离的页面,比如来电界面,因为它所在的任务栈只有自身一个Activity,独立于其他任务。
2.2 onSaveInstanceState和屏幕旋转:生命周期问题的高频陷阱
生命周期是安卓面试绕不开的另一个重头戏,尤其是屏幕旋转和进程被杀这两个场景。面试官常问:旋转屏幕时Activity会发生什么?默认情况下Activity会销毁重建,onPause、onStop、onDestroy依次调用,然后重新走onCreate。很多人能答出这个流程,但追问“哪些场景不会销毁重建”“onSaveInstanceState的时机和数据大小限制”时就不清楚了。
实际上google规定了两种情况会触发onSaveInstanceState,一是用户按Home键或最近任务键导致Activity不可见,二是屏幕旋转或配置变更导致Activity即将被销毁。这时系统会保存视图层级中带有id的控件状态,以及你在onSaveInstanceState里写入的自定义数据。但注意,如果你在onPause里做了耗时操作,onSaveInstanceState会一直等它完成,这也是为什么官方推荐避免在onPause里做重量级事情的原因。
再深一层,面试官还喜欢问ViewModel为什么能在配置变更后存活。这里要答到ViewModelStore和NonConfigurationInstances的关系,ViewModelStore会通过NonConfigurationInstance机制在Activity重建时被保留传递,所以ViewModel里持有的数据不会丢失。如果你能顺带说明ViewModel的onCleared回调什么时候触发——只有在Activity真正finish时才会触发,配置变更不会——那你这一题基本就稳了。
3. Handler机制:从源码到内存泄漏,一题吃透
3.1 ThreadLocal、Looper和MessageQueue的角色分工
Handler是安卓面试的必考题,几乎没有例外。这道题考察的是异步消息机制的全链路理解,我建议按“ThreadLocal→Looper→MessageQueue→Handler→Message”这条线来讲,不要东一榔头西一棒子。
先说ThreadLocal。每个线程都有自己独立的ThreadLocalMap,Looper通过ThreadLocal机制保证每个线程只能持有一个Looper实例。主线程的Looper在ActivityThread.main里通过Looper.prepareMainLooper()创建,这个就是主线程消息循环的起点。子线程如果要创建Handler,必须先调用Looper.prepare()再调用Looper.loop(),否则直接new Handler会抛RuntimeException——这个经典异常就叫“Can't create handler inside thread that has not called Looper.prepare()”。
MessageQueue的核心是enqueueMessage和next方法。enqueueMessage按时间顺序将Message插入链表,next方法是一个死循环,里面调用nativePollOnce进入阻塞状态等待新消息或超时。如果队列为空且没有延时消息,线程会一直阻塞,不会消耗CPU。这里有个容易忽略的细节:普通消息不需要锁,因为enqueueMessage只由当前线程调用;但如果使用了postAtFrontOfQueue,则会走synchronized锁保证线程安全。
3.2 Message的池化复用和同步屏障机制
Message的obtain和recycle机制也是面试官的宠儿。开发者应该通过Message.obtain()或Handler.obtainMessage()获取Message,而不是直接new。这是因为Message内部维护了一个基于单链表的对象池,obtain方法会优先从池子里取对象,避免频繁创建和GC。面试官如果问你“这个池子最大有多少个”,你可以说没有硬性上限,sPoolSize在源码里只是用于记录的字段,实际上链表会一直复用直到没人使用。
同步屏障是Handler机制里相对冷门但面试官近几年爱问的点。以Vsync信号驱动的Choreographer为例,UI绘制消息需要立即执行,不能被延时消息阻塞,所以系统会往MessageQueue插入一个SyncBarrier类型的消息。next()方法遇到这种消息时会跳过所有同步消息,只执行异步消息,也就是target为null的那个屏障令牌。知道这个机制,你就能解释为什么postAtFrontOfQueue的Message能插队,为什么帧绘制消息能抢在普通消息前面。
内存泄漏问题更是必问。非静态内部类Handler默认持有外部Activity的强引用,如果在onDestroy后还有延时消息未处理,GC就不会回收Activity,导致泄漏。标准写法有三件套:一是把Handler声明为静态内部类,通过WeakReference持有外部组件;二是在onDestroy里调用handler.removeCallbacksAndMessages(null)清空消息;三是使用Lifecycle-aware组件如LifecycleObserver来自动清理。你在回答时最好先说明泄漏产生的原因链:延时消息→MessageQueue持有Message→Message持有Handler→Handler持有Activity,这样比直接背写法更容易让面试官点头。
4. 事件分发与View绘制:常见难题的实战拆解
4.1 dispatchTouchEvent的决策链路和OnTouchListener的拦截顺序
事件分发是Android中原理性最强、最容易让候选人翻车的模块。面试官最爱问的就是“点击一个Button,事件是怎么从Activity传到Button的”,以及“OnTouchListener和OnClickListener谁先触发”。表面上看,事件分发是Activity→ViewGroup→View的传递过程,但每一层都涉及拦截和消费的判断,链路比较复杂。
先说决策链路。手指按下发生DOWN事件时,Activity先调用dispatchTouchEvent,通过PhoneWindow的DecorView往下分发。DecorView是一个FrameLayout,事件先进入ViewGroup的dispatchTouchEvent,再在onInterceptTouchEvent里判断是否拦截——DOWN事件默认不拦截。如果不拦截,就按照子View的Z轴顺序和触摸点位置找到目标View,调用子View的dispatchTouchEvent。子View如果是普通View,则走自己的onTouchEvent处理。
OnTouchListener和OnClickListener的执行顺序很多人记反了。实际上先执行的是OnTouchListener的onTouch,如果返回true,事件被消费,后面不会执行onTouchEvent,自然也不会触发OnClickListener。如果onTouch返回false或者没有设置,走onTouchEvent的ACTION_UP分支,触发performClick,最终回调OnClickListener。这个顺序的原因可以追溯到View.dispatchTouchEvent的源码逻辑,OnTouchListener的优先级设计就是让你能在默认点击处理之前拦截事件。
4.2 requestDisallowInterceptTouchEvent与嵌套滚动
面试官有时会用“滑动冲突”来考事件分发的实际应用,比如ScrollView嵌套RecyclerView横向滑动冲突,或者ViewPager嵌套轮播图。核心解法无非是外部拦截法和内部拦截法。外部拦截法在父View的onInterceptTouchEvent里根据判断条件决定是否拦截,内部拦截法用requestDisallowInterceptTouchEvent让子View请求父View不拦截。
这里有个容易踩的坑:requestDisallowInterceptTouchEvent对ACTION_DOWN无效。因为父View在收到DOWN事件时会重置FLAG_DISALLOW_INTERCEPT标志,所以你在子View的onTouchEvent里想要阻止父View拦截,必须从DOWN之后的事件开始处理。这个细节很多人不知道,实际上稍微翻一下ViewGroup源码就能看到 clearDisallowIntercept 逻辑。
嵌套滚动机制NestedScrolling也属于这部分的进阶考点。CoordinatorLayout配合Behavior、AppBarLayout、RecyclerView实现的下拉折叠、标题栏联动效果,本质上是父View和子View之间通过NestedScrollingParent和NestedScrollingChild接口协同处理滚动事件。如果能说清楚onStartNestedScroll、onNestedPreScroll、onNestedPreFling这几个方法的调用时机,再结合一个实际业务场景说明你是怎么用它解决滚动联动的,面试官对你这题的印象分一定不低。
4.3 measure、layout、draw与View.post的宽高之谜
自定义View的三大流程同样是高频考点。measure负责测量宽高,layout负责确定位置,draw负责绘制内容。问到“自定义View为什么有时拿不到宽高”时,很多人只会说“因为onMeasure还没执行完”,但面试官更想听到完整的时序解释。Activity的onCreate、onResume里执行findViewById后直接getWidth和getHeight,此时窗口还没有完成首次布局,View的宽高自然没有赋值。解决办法有View.post、ViewTreeObserver的addOnGlobalLayoutListener,以及在onWindowFocusChanged里获取。
View.post能拿到宽高的原理值得单独拎出来讲。如果View已经attach到window,post会通过ViewRootImpl的RunQueue直接执行;如果还没attach,则等待attach时统一执行。这一步很多候选人答不上来,但其实源码逻辑并不复杂,会看源码的人几分钟就能理解。面试官问这题,更多是想考察你有没有主动读过系统源码的习惯。
5. 性能优化专项:启动、内存、卡顿三个老生常谈但必问的领域
5.1 启动优化:从冷启动定义到具体指标拆解
启动优化几乎出现在所有高级岗面试中。一道标准的开放题是:你们的App冷启动需要2秒,怎么优化到1秒以内?这种题没有标准答案,但我建议按“现状测量→瓶颈定位→针对性优化→效果验证”四步来答,不要一上来就背“异步初始化、懒加载”这些词。
先说冷启动的定义:点击图标到用户看到第一帧可交互页面的时间。中间包括系统创建进程、Application创建、MainActivity创建、布局绘制、首帧渲染几个阶段。测量工具首选adb命令,比如adb shell am start -W可以拿到totalTime,配合手动插桩和Android Studio的Launch Profiler,可以区分哪些耗时在Application的onCreate里,哪些在Activity的onCreate和onResume里。
优化手段要分点讲:一是Application的onCreate里减少同步初始化,把非必须的SDK移到子线程或者按需初始化;二是用Startup更轻量的方案替代多SDK的自动初始化,或者自己封装一个初始化调度器;三是首页布局用AsyncLayoutInflater做异步inflate,或者用ViewStub懒加载非首屏区域;四是减少启动时的SharedPreferences读写,这些IO操作在低端机上非常伤启动速度。重要的是,每说一个手段,都要结合你自己的项目数据说明实际收益,比如从2.1s降到1.4s,靠的是哪几项的组合拳。
5.2 内存泄漏场景速查和LeakCanary的正确用法
内存泄漏在安卓面试里永远有席位。面试官常问“你遇到过哪些内存泄漏场景,怎么排查的”,这时候最忌讳的回答是“我们用LeakCanary检测”。LeakCanary只是工具,重点是你有没有真正理解泄漏发生的机理。
高频泄漏场景我列过一份清单。单例持有Activity或Context是最常见的,比如一个全局的Utils类持有了Activity引用,Activity退出后无法回收。Handler延时消息、内部类持有外部类引用、匿名内部类被静态集合持有、关闭不彻底的资源如Cursor和Stream、以及RxJava的订阅未取消,这些都是面试官爱听的案例。
排查思路要讲清楚闭环。先用LeakCanary看自动抓取的泄漏链路,再用Android Studio的Memory Profiler手动分析heap dump,对比多次GC后仍存活的对象。很多线上问题LeakCanary抓不到,这时候可以结合MAT或者AS的Analyze Tasks看Dominator Tree。我自己的经验是,排查内存泄漏最有效的还是“从可疑代码入手+heap dump验证”,纯靠工具点来点去反而浪费时间。
5.3 卡顿优化:从Systrace到Perfetto,再到实际修复
卡顿优化的高频考题是:线上用户反馈页面卡顿,你怎么定位?这个问题的标准思路是:先区分是掉帧还是ANR,掉帧用帧率监控,通过Choreographer.FrameCallback统计耗时超过16.6ms的帧;ANR用系统的traces.txt或者自研监控收集主线程堆栈。
定位工具方面,Perfetto是现在的首选,它比Systrace信息更全,可以查看每个线程的CPU调度、CPU频率、Binder调用和Lock竞争。如果你没在项目里接入Perfetto,面试时也可以讲一讲它的基本用法,比如抓trace的adb命令是perfetto -o /data/misc/perfetto-traces/trace.pb -t 10s sched freq idle am wm gfx view binder_driver hal,这个命令我在实际项目里测试过是可以用的,区别只是设备可能需要root权限或者开发者模式授权。
修复手段要结合具体原因。如果卡顿来自布局层级过深,考虑ConstraintLayout扁平化或者merge标签减少层级;如果来自主线程做了IO,把读写移到子线程;如果来自GC频繁,而且泄漏,先修泄漏再优化对象分配;如果来自锁竞争,可能要查Binder调用是否频繁跨进程。前端面经常问“你怎么做性能监控”,安卓这边本质也一样:先有监控数据,才能指导优化方向。
6. Binder与IPC:一题问透跨进程通信的本质
6.1 为什么安卓不用共享内存和Socket,而选Binder
Binder是安卓IPC面试的核心,几乎每个中高级岗都会问。考察方式通常是:为什么安卓要使用Binder而不是其他IPC方式?Binder一次数据拷贝的原理是什么?这题答得好,能极大提升面试官对你的评价。
性能角度,Binder只需要一次拷贝,而传统管道和Socket需要两次拷贝,共享内存虽然零拷贝但不适合做进程间通信的通用框架,因为缺少稳定性、同步机制和权限控制。安全性角度,Binder在内核态为每个进程分配UID,内核可以校验调用者身份,而传统IPC很难做到。这也是为什么安卓频繁用Binder做系统服务通信,比如ActivityManager、WindowManager。
Binder的通信模型要讲清楚:Client进程调用BinderProxy的transact方法,数据从用户空间拷贝到内核空间的Binder驱动,然后Binder驱动根据目标句柄找到Server进程对应的Binder实体,把数据拷贝到Server进程的用户空间,触发BBinder的onTransact方法。说白了一句话就是:一次拷贝发生在Client进程到内核空间,目标进程与内核空间之间共享了同一份映射区域,所以省了第二次拷贝。
6.2 AIDL的流程和Binder线程池的扩张机制
AIDL之所以面试常考,是因为它体现了Binder通信的完整编码流程。你需要说出:定义aidl接口文件,实现Stub类,在Service的onBind返回这个Stub对象,客户端通过bindService拿到IBinder后通过Stub.asInterface转换成接口调用。还要记得自定义权限验证、连接池、onServiceConnected回调、死亡代理DeathRecipient这些进阶点。
Binder线程池的机制很多候选人根本不了解。默认情况下Binder线程池起始为0,Binder驱动会为每个进程动态分配线程,上限默认是16个。高并发场景下如果Binder请求量很大,这些线程会被占满,后续请求就会排队,造成跨进程调用性能劣化。你可以通过调试的persist.sys.binder_thread_count参数调整上限,但大多数时候真正要解决的是减少不必要的Binder调用。
7. 体系化进阶:Kotlin协程、Jetpack、组件化与网络框架
7.1 协程的挂起恢复原理和调度器选择
Kotlin协程现在是安卓面试的必考内容,但很多人只是会用,没理解原理。面试官问“协程为什么能挂起而不阻塞线程”,你要回答到底层是状态机机制。编译器会把一个suspend函数编译成Continuation状态机,每一次挂起点对应一个状态,恢复时根据状态跳转继续执行。线程确实没有阻塞,只是执行权被释放了。
调度器选择也是高频问点。Dispatchers.Main用于UI操作,Dispatchers.IO用于磁盘读和网络请求,Dispatchers.Default用于CPU密集型计算。但面试官更想听到的是你对线程池的理解,比如Dispatchers.IO底层是一个线程池,它的并发数受系统CPU核心数和blocking任务比例限制。实际项目中我发现一个常见问题是把数据库查询和JSON解析不加区分地都扔到IO线程池,其实JSON解析属于CPU密集操作,放到IO线程池反而因为线程切换频繁导致性能下降。
Flow和协程的关系也是近两年新宠。StateFlow和SharedFlow的区别、冷流和热流的差异、collect和collectLatest的区别,这些都是值得提前准备的内容。举个具体差异:StateFlow是热流,始终保留最新值,适合做UI状态;Flow是冷流,每次collect都会重新执行上游生产者,适合做一次性网络请求。
7.2 Compose、Jetpack架构组件和组件化路由
Jetpack家族现在几乎成了中型以上安卓项目的标配,面试官一定会问。Lifecycle被问得最多的是lifecycle-aware组件是如何省内存的,比如LifecycleObserver能在onStop时自动暂停耗时任务,比手动在onPause里写逻辑更不易出错。ViewModel前面已经说过配置变更存活的机制。LiveData适合做UI事件和一次性消息,但当场景变成多次事件时,用SharedFlow更合适,因为LiveData有一定的粘性特性,容易重复触发。
Compose也在越来越多面试中出现,至少要知道它的关键概念:声明式UI、重组、状态提升、remember和rememberSaveable的区别、以及SideEffect和DisposableEffect的用途。不要只背概念,最好用一个小例子说明Compose的重组范围,比如列表中的某一行状态变化时,Compose如何实现只重组该行而不是整个列表。
组件化则是大型App考察的重头戏。需要说清楚拆分原则、模块间的通信方式——比如用路由框架做跨模块跳转、用接口下沉做模块间服务调用、用Gradle模块隔离避免循环依赖。ARouter的原理也不复杂:编译期通过APT生成路由表,运行时通过映射找到目标Activity,支持拦截器做登录校验和埋点。
7.3 网络与框架源码:Retrofit、OkHttp、Glide的高频追问
Retrofit面试题基本逃不开动态代理和注解解析。Retrofit用Java动态代理生成接口实现类,在invoke方法里解析方法注解和方法参数,构建出ServiceMethod,再通过OkHttp执行Call。面试官如果追问“为什么Retrofit就能把接口方法变成Request”,你就要说代码生成和反射增强的关系。
OkHttp经常考拦截器链。Application Interceptor和Network Interceptor有什么区别,自定义拦截器如何实现日志打印、请求参数加密和缓存策略。要能画出完整的拦截器链顺序:自定义应用拦截器→重试和重定向→桥接拦截器→缓存拦截器→连接拦截器→网络拦截器→CallServer。如果面试官问连接池,要提到HTTP/2多路复用和ConnectionPool的复用机制。
Glide的高频问题是缓存策略。要能说出内存缓存和磁盘缓存两级缓存,内存缓存基于LruCache,磁盘缓存基于DiskLruCache,区分activeResources的引用缓存和Bitmap池的复用机制。还要知道Glide默认只缓存最终加载的图像,所以加载大图时配合override方法设置合理的尺寸裁剪,避免内存浪费。
8. 实战模拟:几道高频面试题的完整拆解
8.1 说说你对Activity启动模式的理解,以及实际项目中的选型
这是一道看似基础的工作年限越长越不能答简单的问题。建议分三层来答。
第一层,说出四种模式的定义和区别。standard每次都会新建实例,singleTop如果在栈顶就复用,不在栈顶还是新建。singleTask如果栈里已有实例就复用并清掉它上面的Activity。singleInstance所在任务栈只允许有一个实例,这个Activity全局唯一。
第二层,结合业务场景说明选型依据。比如我们的消息页用了singleTask,因为从推送通知跳转时,我们希望复用已有的消息列表页,避免每一推送都新建一个页面。支付结果页我们用singleTop,因为支付回调可能重复触发,但页面已经在栈顶时直接回调onNewIntent更新结果即可。
第三层,讲你踩过的坑。比如singleTask的clearTop行为会导致页面上层状态被清掉,如果上层有未提交的表单就丢了。再比如singleInstance的Activity因为独立任务栈,在最近任务列表里会单独显示,可能会让用户体验变得怪异。这样答题的好处是,既体现了基础扎实,又体现你真正在项目里做过权衡。
8.2 Handler内存泄漏的原因和优化方案
这道题我几乎在每一轮技术面里都会问,回答质量差异很大。最差的回答是“用静态Handler就好了”,最好的回答会从根因讲起。根因是延时消息持有Message,Message持有Handler,Handler是内部类,非静态内部类持有外部Activity的引用,于是Activity无法被回收。如果消息队列里积压了大量延时消息,泄漏会持续更久。
优化方案要说完整。第一,使用静态内部类加WeakReference持有Activity或Context。第二,在onDestroy中调用removeCallbacksAndMessages(null)清空所有待处理消息。第三,对延时消息设置超时保护,比如用postDelayed时先检查页面是否已销毁。第四,如果使用的是Lifecycle组件,可注册LifecycleObserver在onDestroy时自动清理。最后补一个实际案例,比如我们的分享面板用延时Handler做自动关闭,后来改成在onDestroy里做清理后,在Memory Profiler里连续打开关闭页面10次,存活实例从8个降到1个。
8.3 RecyclerView的缓存机制和优化策略
RecyclerView的高频问题集中在缓存机制。要能说出四级缓存:mAttachedScrap、mCacheView、mViewCacheExtension、mRecycledViewPool。其中mCacheView默认容量是2,ViewHolder从缓存取出来不需要重新绑定数据;mRecycledViewPool按ViewType缓存ViewHolder,复用的时候必须走onBindViewHolder,所以要尽量避免为每个item单独创建ViewType。
优化策略要结合Reload问题来说。当数据变化时,建议用DiffUtil或ListAdapter做局部刷新,而不是notifyDataSetChanged。notifyDataSetChanged会让所有item重新绑定,如果你的item中有大量图片加载或复杂布局,性能损耗就会很明显。还有一个优化点是避免在onBindViewHolder里创建新对象,比如ColorStateList和Typeface这类可复用对象,在初始化时就准备好。
9. 面试最后的反问环节:这样问给面试官留下好印象
很多人把面试当作答题机器,面完就结束,完全无视反问环节。其实反问环节是你了解团队、判断Offer是否值得接的重要机会,也是进一步展示你思考深度的时机。
建议问技术团队相关的问题,比如:现在项目里主要在解决什么问题?技术栈演进方向是什么?测试和发布流程是怎样的?线上监控体系用的哪些方案?这些问题能帮你判断团队的技术氛围和成长空间。
尽量避免一上来就问薪资、加班、大小周之类的问题,这些问题不是面试官在这个环节能决策的,而且容易被记上一笔。如果面试官主动提起业务线加班情况,你可以顺势了解工作节奏,但没必要主动把薪资和加班作为唯一关心的事项全程追问。最后可以加一句“如果有后续机会,希望有机会和团队深入聊聊架构和性能优化上的实践”,既礼貌又留有余地。
10. 实操心得:从被面到面人的几点复盘
我既经历过被面试官追问到哑口无言的时候,也担任过技术面试官去评估候选人。这几年下来最大的体会是:安卓面试本质上不是在考你会不会某个API,而是在考你有没有体系化的知识框架和真实的问题解决能力。
体系化怎么来?我的建议是按模块做思维导图,每个模块至少写上五个“为什么”。为什么View.post能拿到宽高?为什么LiveData会有粘性事件问题?为什么SharedPreferences的apply是异步写磁盘却仍然不够快?为什么Glide默认只缓存最终图像?这些问题想明白了,面试官怎么变着法子问,你都能兜住。
真实项目经验怎么积累?尽量主动啃自己项目里的硬骨头。我见过不少人抱怨业务简单没成长,但同样一个搜索页面,有人能做出输入防抖、结果缓存、图片预加载、滑动流畅性优化,有人只是接接口填数据。差距不在业务,在于是不是愿意多问一句“这里有没有可能更快更稳”。
另外给大家一个实用建议:准备一个自己的“面试错题本”。每次面完试,把没答上的问题记下来,复盘原因,是知识盲区还是表达不清,然后针对性补强。这个方法比海量刷题有效得多,因为错题本里记录的才是你真正欠缺的地方。
安卓面试题表面上是技术问答,骨子里是工程思维的较量。把基础原理吃透,把项目经验讲出闭环,把开放性问题答出层次,这套方法论在哪个公司、哪个职级都适用。希望这篇文章能帮准备面试的朋友少走一些弯路,也希望大家都能拿到心仪的Offer。