随着 Android 技术的日益成熟与演进,许多开发者在面对日益庞大的技术栈时容易感到迷茫。但无论上层框架如何变化,夯实的基础与核心原理永远是应对大厂面试和“互联网寒冬”最坚固的护城河。
本文是 2026年Android中高级面试题专栏 的第一篇:Android基础篇。
我们剔除了浮于表面的“快餐式”背诵答案,紧扣 Android 基础核心,结合最新的架构演进与底层实现原理,带你一次性通关四大组件、Handler 消息机制、View 体系及 Context 机制。
一、 四大组件与应用架构
Q1: Activity 的生命周期在异常重建场景下是如何工作的?启动模式(LaunchMode)与 Task 栈管理有何深度细节?
核心回答:
-
异常重建(如屏幕旋转、系统内存不足回收) :
- 系统会触发
onSaveInstanceState(Bundle)(在onStop之前),将 View 树的状态(如 EditText 输入内容、RecyclerView 滚动位置)及自定义数据保存到Bundle中。 - 恢复时,系统在
onStart之后调用onRestoreInstanceState(Bundle),或者在onCreate(Bundle)中直接读取savedInstanceState。
- 系统会触发
-
四大启动模式与 Flag 深入:
standard:每次都新建实例注入当前 Task 栈。singleTop:若目标 Activity 已在栈顶,则复用并回调onNewIntent(Intent);不在栈顶则新建。singleTask:栈内单例。寻找是否存在对应的taskAffinity栈,若存在且包含该 Activity 实例,则将其之上的所有 Activity 清空(Clear-Top 效果)并回调onNewIntent()。singleInstance:全局单例。独占一个新的 Task 栈,且该栈内不允许存在其他 Activity。- 常用 Flag 对比:
FLAG_ACTIVITY_NEW_TASK(通常配合taskAffinity建立新栈)、FLAG_ACTIVITY_CLEAR_TOP(销毁目标以上的 Activity)、FLAG_ACTIVITY_SINGLE_TOP(等同于singleTop模式)。
Q2: startService 与 bindService 的生命周期有何区别?混合使用时如何正确销毁?
核心回答:
-
生命周期差异:
startService:onCreate()onStartCommand()服务后台运行。直到显式调用stopService()或stopSelf()才触发onDestroy()。与调用者生命周期无关。bindService:onCreate()onBind()返回IBinder给客户端。当所有绑定者都调用了unbindService()后,系统会自动触发onUnbind()onDestroy()。
-
混合使用(先 start 后 bind) :
- 服务启动后处于常驻状态;当页面与其绑定时,仅回调
onBind()。 - 正确销毁逻辑:必须既解绑(调用
unbindService())又停止服务(调用stopService()或内部stopSelf()),这两个条件同时满足时,Service 才会真正执行onDestroy()销毁。
- 服务启动后处于常驻状态;当页面与其绑定时,仅回调
Q3: 应用架构模式 MVP、MVVM 与 MVI 有什么本质区别与演进逻辑?
核心回答:
| 维度 | MVP (Model-View-Presenter) | MVVM (Model-View-ViewModel) | MVI (Model-View-Intent) |
|---|---|---|---|
| 数据流向 | 双向交互(View Presenter) | 单向数据绑定 / 响应式驱动 | 严格单向数据流(UDF) |
| 状态管理 | 状态分散在 View 和 Presenter 中 | ViewModel 持有多个 LiveData/Flow 状态 | 唯一可信数据源(Single State) |
| View 耦合度 | 通过接口强绑定,Presenter 易膨胀 | 通过观察者模式解耦,View 监听数据变化 | View 仅发送 Intent,订阅 UI State 变更 |
| 并发与状态重置 | 无法原生防御多线程状态竞争 | 多个独立状态改变时可能导致 UI 多次刷帧 | 状态不可变(Immutable),复制生成新状态 |
-
MVI 的核心架构逻辑:
- User Intent:用户操作(如点击、滑动)封装为 Intent/Event 发送给 Model/ViewModel。
- Model/Reducer:接收 Intent,处理业务逻辑并生成新的不可变状态(
UIState)。 - UI Render:UI 界面订阅唯一的
UIState,根据 State 的变化无副作用地渲染界面。
二、 消息机制与异步通信(Handler 深度专场)
Q1: 深度剖析 Handler 消息机制四大核心组件的设计与线程隔离机制
核心回答:
Handler 机制本质上是基于单线程事件循环的生产者-消费者模型。
[ Producer Thread ] [ Consumer Thread (Looper) ]
Handler ──> MessageQueue (Message) ──> Looper.loop()
(sendMessage) (PriorityQueue) │ (dispatchMessage)
▼
Handler.handleMessage()
Message:消息载体,内部通过单链表结构实现对象池机制(sPool),推荐使用Message.obtain()复用以减少 GC 压力。MessageQueue:消息单链表队列,按消息的触发执行时间(when)升序排列。Looper:线程的消息循环器。一个线程通过Looper.prepare()只能创建一个 Looper 实例,并存入当前线程的ThreadLocal中,保证了 Looper 与线程的绝对隔离与唯一性。Handler:消息发送者与处理者。构造时绑定当前线程的Looper与MessageQueue,通过sendMessage()将消息入队,并在Looper轮询到该消息时回调handleMessage()。
Q2: MessageQueue.next() 中的 Linux epoll 机制是什么?Looper.loop() 死循环为什么不会卡死 CPU?
核心回答:
-
非阻塞等待与唤醒(
epoll机制) :MessageQueue的底层使用了 Linux 的epoll_wait机制。当MessageQueue中没有消息,或者栈顶消息的执行时间when未到时,Native 层的nativePollOnce()会让当前线程陷入休眠状态,并释放 CPU 资源。 -
CPU 资源占用:
死循环(
for (;;))本身只是保持程序运行状态的一种结构。当没有消息时,线程处于休眠状态,不会占用 CPU 时间片。 -
唤醒逻辑:
当有新消息插入(
enqueueMessage())且该消息需要立即执行,或者队列被插入了优先级更高的消息时,Native 层会通过向eventfd写入数据唤醒epoll_wait,使MessageQueue.next()继续向下执行。
Q3: 什么是消息屏障(Sync Barrier)与异步消息?IdleHandler 的使用场景是什么?
核心回答:
-
消息屏障(Sync Barrier) :
- 概念:一种特殊的
Message,其target == null。 - 作用:当
MessageQueue检索到消息屏障时,会阻塞后续所有同步消息的执行,优先遍历并提取出队列中的异步消息(msg.isAsynchronous() == true)进行分发。 - 应用场景:Android 的 UI 刷新机制(
Choreographer)。在请求 VSYNC 信号时,系统会向MessageQueue插入一个消息屏障,确保 UI 绘制渲染的异步消息能够无延迟优先执行,绘制完成后再移除屏障。
- 概念:一种特殊的
-
IdleHandler:- 概念:
MessageQueue提供的内部接口。 - 触发时机:当
MessageQueue中当前没有消息可处理,或者所有消息的执行时间未到(处于空闲等待期)时触发。 - 应用场景:延迟加载低优先级的任务(如主界面加载完成后的后台预加载、日志上报、动态 View 预创建),既不阻塞首屏渲染,又能高效利用 CPU 空闲时段。
- 概念:
三、 View 体系与用户交互
Q1: 详细说明自定义 View 的三大绘制流程(Measure, Layout, Draw)及 MeasureSpec 机制
核心回答:
1. MeasureSpec 计算机制:
MeasureSpec 是由 32 位整型构成的打包值(高 2 位表示 Mode,低 30 位表示 Size)。系统根据父容器的 MeasureSpec 和子 View 自身的 LayoutParams 共同决定子 View 的 MeasureSpec:
EXACTLY:精确大小(如100dp或match_parent)。AT_MOST:最大上限(如wrap_content),子 View 不能超过父容器给出的size。UNSPECIFIED:不限制大小(多用于ScrollView等滑动控件)。
2. 三大流程详解:
-
onMeasure(widthMeasureSpec, heightMeasureSpec):测量 View 的宽高。如果是
ViewGroup,需要遍历调用子 View 的measure()方法,并结合子 View 的测量尺寸确定自身的最终宽高,最后必须调用setMeasuredDimension()存入结果。 -
onLayout(changed, l, t, r, b):确定 View 的位置。
ViewGroup必须重写此方法,根据自身的测量宽高和子 View 的布局规则,循环调用child.layout(l, t, r, b)设定子 View 的四个顶点坐标。 -
onDraw(Canvas canvas):绘制 View 画面。依次绘制背景 保存图层 绘制自身内容(
onDraw) 绘制子 View(dispatchDraw) 绘制装饰(如滚动条、前景)。
Q2: 经典面试题:Android Touch 事件分发机制的递交链条与滑动冲突解法
核心回答:
1. 事件分发责任链递交:
事件处理的核心逻辑由 dispatchTouchEvent、onInterceptTouchEvent(仅 ViewGroup 有)和 onTouchEvent 三个方法构成:
[ Down Event ]
Activity.dispatchTouchEvent()
└── PhoneWindow.superDispatchTouchEvent()
└── DecorView.dispatchTouchEvent()
└── ViewGroup.dispatchTouchEvent()
├── onInterceptTouchEvent() == true ──> 自己 onTouchEvent()
└── onInterceptTouchEvent() == false ──> 子 View.dispatchTouchEvent()
- 如果子 View 的
onTouchEvent返回true,则消费该事件,后续的MOVE、UP事件将直接交由该子 View 处理。 - 如果子 View 返回
false,事件将向上回溯,由父容器的onTouchEvent进行处理。
2. 滑动冲突两大解决范式:
- 外部拦截法(推荐) :点击事件先经过父容器的
onInterceptTouchEvent。如果父容器需要拦截(如横向滑动),则在ACTION_MOVE中返回true,直接由父容器处理事件。 - 内部拦截法:父容器不拦截任何事件(
ACTION_DOWN除外);子 View 在dispatchTouchEvent中通过getParent().requestDisallowInterceptTouchEvent(true)阻止父容器拦截。当子 View 检测到不需要该事件时,将其置为false重新交还父容器。
四、 Android 存储与 Context 机制
Q1: SharedPreferences 存在哪些性能与 ANR 隐患?MMKV 的底层原理是什么?
核心回答:
-
SharedPreferences 痛点:
- 全量加载阻塞:首次读取时会将整个 XML 文件一次性加载到内存,可能导致主线程阻塞。
apply()的 ANR 隐患:apply()虽然是异步写入磁盘,但它会将写入 Task 加入QueuedWork队列。在 Activity 切换(onStop/onPause)或 Service 销毁时,系统会强制等待QueuedWork队列中的磁盘写入完成,从而阻塞主线程引发 ANR。- 无法安全支持跨进程:
MODE_MULTI_PROCESS极易导致数据丢失和内存不同步。
-
MMKV 底层优化原理:
mmap(内存映射) :使用mmap将文件直接映射到进程的虚拟内存空间,读写操作直接在内存中完成,由操作系统内核负责同步回磁盘,彻底避免了频繁的 I/O 写入和阻塞。- Protobuf 序列化:采用 Protocol Buffers 编码,数据体积更小,仅在尾部追加修改增量,无需每次全量写入。
- 多进程并发安全:通过 Posix 文件锁(
flock)实现跨进程高可靠的读写互斥。
Q2: Application Context、Activity Context 与 Service Context 的继承关系与应用场景有何不同?
核心回答:
1. Context 继承链架构:
Context (abstract)
▲
│
ContextWrapper
┌────────────┼────────────┐
│ │ │
Application Service ContextThemeWrapper
▲
│
Activity
ContextImpl是Context的真正实现类。ContextWrapper内部持有mBase(即ContextImpl),采用装饰模式代理所有方法。
2. 使用场景差异与内存泄漏避坑:
| Context 类型 | 弹出 Dialog | 启动 Activity | 加载 Layout / View | 注册 BroadcastReceiver |
|---|---|---|---|---|
| Activity | 允许 | 允许(无需 Flag) | 允许(带主题) | 允许 |
| Application | 拒绝(崩溃) | 需要 FLAG_ACTIVITY_NEW_TASK | 不推荐(丢失主题样式) | 允许 |
| Service | 拒绝(崩溃) | 需要 FLAG_ACTIVITY_NEW_TASK | 不推荐(丢失主题样式) | 允许 |
- 防泄漏准则:单例对象、静态变量、长生命周期的异步回调中,严禁持有 Activity Context,必须使用
context.applicationContext。仅在涉及 UI 渲染、Dialog 弹出、显示 Theme 相关的场景下使用 Activity Context。
专栏目录导航
-
[第 1 篇] 2026年Android中高级面试题专栏(Android基础篇)
最后,祝愿每一位正在备战面试的 Android 开发者:
- 能够在反复的梳理与思考中打破瓶颈,将知识真正融会贯通;
- 在接下来的每一场面试中发挥出色、对答如流,展现出自己最出色的技术硬实力;
- 拨云见日,顺利斩获心仪的大厂与中高级职位 Offer,在移动开发的技术道路上披荆斩棘、一路高歌!