跳到主要内容

Android 面经

核心考点

1. Activity & Fragment 生命周期

Activity: onCreate → onStart → onResume → [运行] → onPause → onStop → onDestroy
Fragment: onAttach → onCreate → onCreateView → onViewCreated → onStart → onResume
→ onPause → onStop → onDestroyView → onDestroy → onDetach
  • A 跳 B:A.onPause → B.onCreate → B.onStart → B.onResume → A.onStop
  • 按 Back 返回:B.onPause → A.onRestart → A.onStart → A.onResume → B.onStop → B.onDestroy

2. 内存泄漏常见场景

  • 静态变量持有 Activity 引用
  • 非静态内部类(Handler/AsyncTask)持有外部类引用
  • 未注销的监听器、广播、回调
  • 资源(Cursor、Stream、Bitmap)未及时释放
  • 解决:使用 WeakReference、在 onDestroy 中解绑

3. Handler / Looper / MessageQueue

  • Looper 在线程中维护 MessageQueue,loop() 死循环取消息
  • Handler 发送/处理消息,绑定到创建它的线程 Looper
  • 主线程 Looper 由系统启动时初始化(ActivityThread.main)

4. 协程 (Kotlin Coroutines)

  • 轻量级线程,挂起不阻塞线程
  • Dispatchers.Main / IO / Default:分别用于 UI、IO 密集、CPU 密集
  • viewModelScope / lifecycleScope:自动跟随生命周期取消
  • Flow:冷流,适合异步数据流;StateFlow / SharedFlow:热流

5. Jetpack 核心组件

组件作用
ViewModel跨配置变化保留数据
LiveData生命周期感知的可观察数据
RoomSQLite ORM
NavigationFragment 导航管理
WorkManager后台任务调度
Compose声明式 UI 框架

6. 性能优化

  • ANR 触发:主线程 5s 无响应(耗时操作放子线程)
  • 过度绘制:减少层级,避免不必要背景,开启 GPU 调试
  • 启动优化:懒加载、异步初始化、减少冷启动耗时
  • 内存优化:Bitmap 压缩复用,避免内存抖动

7. 高频面试题

  • 进程间通信 (IPC):AIDL、Messenger、ContentProvider、BroadcastReceiver
  • 四大组件:Activity、Service、BroadcastReceiver、ContentProvider
  • View 绘制流程:measure → layout → draw
  • RecyclerView vs ListView:RecyclerView 解耦更彻底,支持多种布局、动画、局部刷新

Android 性能优化专题

性能优化全攻略

1. 渲染性能

过度绘制(Overdraw)

  • 开启:开发者选项 → 调试 GPU 过度绘制
  • 蓝色=1x,绿色=2x,粉色=3x,红色=4x+(红色区域需优化)
  • 方案:移除不必要的背景色,减少布局层级,使用 clipRect 限制绘制区域

布局优化

  • 使用 ConstraintLayout 替代嵌套 LinearLayout,减少层级
  • include / merge / ViewStub 复用和延迟加载布局
  • 使用 Hierarchy Viewer / Layout Inspector 分析布局层级

列表优化

  • RecyclerView:合理设置 ViewHolder 复用
  • DiffUtil.Callback 精准局部刷新,避免 notifyDataSetChanged
  • setHasFixedSize(true):item 大小固定时跳过 requestLayout
  • 预取:RecyclerView.setItemViewCacheSize + RecycledViewPool

2. 启动优化

启动类型

  • 冷启动:进程从零开始(最慢,重点优化)
  • 温启动:进程存在,Activity 需重建
  • 热启动:Activity 在后台,直接前台

冷启动优化

  • Application onCreate 中异步初始化三方库(WorkManager / 协程)
  • 减少主线程耗时操作(SP 改 MMKV,IO 操作移至子线程)
  • 使用 SplashScreen API(Android 12+)
  • 启动页 windowBackground 设为占位图,消除白屏

测量工具

adb shell am start -W com.example/.MainActivity
# ThisTime: 此次 Activity 启动时间
# TotalTime: 整个启动过程时间

3. 内存优化

  • Bitmap 优化inSampleSize 压缩,RGB_565 替代 ARGB_8888(省一半内存)
  • 内存泄漏检测:LeakCanary 自动检测,adb shell dumpsys meminfo 分析
  • 内存抖动:避免在 onDraw/循环中创建对象,使用对象池
  • LruCache:内存缓存图片,基于最近最少使用淘汰

4. 电量与网络优化

  • Doze 模式:系统限制后台网络/CPU,使用 WorkManager 适配
  • 批量网络请求:合并请求减少无线电激活次数
  • 图片格式:WebP 替代 PNG/JPEG,体积减少约 30%
  • HTTP/2:多路复用减少连接建立开销
  • OkHttp 缓存:离线可用 + 减少重复请求

5. ANR 排查

# 查看 ANR 日志
adb pull /data/anr/traces.txt

# 常见原因
# 1. 主线程 IO(网络/磁盘)
# 2. 主线程等待锁
# 3. Binder 调用耗时(跨进程)
# 4. 主线程处理大量 Message
  • StrictMode 开发期检测主线程 IO
  • Systrace / Perfetto 分析线程状态和方法耗时

6. 高频面试题

  • 如何检测内存泄漏? LeakCanary + MAT(Memory Analyzer Tool)分析 heap dump
  • 卡顿原理是什么? 主线程一帧(16.6ms)内未完成 measure/layout/draw
  • 为什么不能在子线程更新 UI? ViewRootImpl 检查线程,invalidate 触发检查
  • Bitmap 如何高效加载大图? BitmapFactory.Options.inSampleSize 按比例采样

Android 架构模式(MVC / MVP / MVVM / MVI)

Android 架构演进

1. MVC

ModelControllerView
↑ ↓
└───────────────┘
  • Android 中 Activity 既是 Controller 又是 View,导致职责混乱
  • 随业务增长 Activity 代码膨胀,难以测试

2. MVP

View (Activity/Fragment)
↕ 接口
Presenter (业务逻辑)

Model (数据层)
  • View 和 Presenter 通过接口解耦,便于单元测试
  • 缺点:接口类爆炸,Presenter 持有 View 引用需注意内存泄漏

3. MVVM(Google 官方推荐)

View (Activity/Fragment)
↓ 观察
ViewModel (持有 LiveData/StateFlow)

Repository

Remote DataSource / Local DataSource

ViewModel 核心优势

  • 生命周期感知:屏幕旋转不丢失数据
  • 不持有 View 引用,无内存泄漏风险
  • SavedStateHandle:进程被杀后恢复状态
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

fun loadUser(id: String) {
viewModelScope.launch {
_uiState.update { it.copy(loading = true) }
val user = repo.getUser(id)
_uiState.update { it.copy(user = user, loading = false) }
}
}
}

4. MVI(单向数据流)

User Action (Intent)

ViewModel 处理 → 新 State

View 渲染
  • State:UI 的完整快照,单一数据源
  • Intent:用户操作,密封类描述所有可能事件
  • 单向数据流:状态不可变,每次更新生成新 State
  • 优点:状态可预测、易于调试和测试
  • 代表库:Orbit MVI、MVI Kotlin
sealed class UserIntent {
data class LoadUser(val id: String) : UserIntent()
object Logout : UserIntent()
}

data class UserState(
val user: User? = null,
val loading: Boolean = false,
val error: String? = null
)

5. Clean Architecture 分层

Presentation Layer (ViewModel + UI)

Domain Layer (UseCase + Entity) ← 纯 Kotlin,无 Android 依赖

Data Layer (Repository + DataSource)
  • UseCase(交互器):封装单一业务规则,可单独测试
  • Repository:抽象数据来源,协调本地缓存和远端数据
  • 依赖规则:内层不依赖外层

6. Hilt 依赖注入

@HiltAndroidApp class App : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var viewModel: UserViewModel
}

@Module @InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()...
}

7. 高频面试题

  • ViewModel 为什么能在屏幕旋转后存活? 存储在 ViewModelStore 中,与 Activity 生命周期解绑
  • LiveData 和 StateFlow 如何选择? 新项目推荐 StateFlow(更强大,支持初始值,协程原生)
  • Repository 的作用? 单一数据源,屏蔽本地/远端差异,统一数据格式
  • UseCase 一定需要吗? 简单场景可省略,复杂业务规则建议抽取便于测试

Jetpack Compose 深入面经

Jetpack Compose 核心知识

1. 重组原理

什么时候会重组?

  • 状态变化时,依赖该状态的 Composable 会重组
  • Compose 通过水平比较(Structural Equality)决定是否跳过重组

如何避免不必要的重组

// 1. @Stable / @Immutable 标记类,告知 Compose 安全跳过
@Stable // 所有 public property 变化时都通知 Compose
data class User(val name: String, val age: Int)

// 2. remember 缓存计算结果
val sortedList = remember(list) { list.sorted() }

// 3. derivedStateOf 对 state 派生新 state
val isScrolled = remember { derivedStateOf { scrollState.value > 0 } }

// 4. key() 给列表项设置稳定标识
Column {
items.forEach { item ->
key(item.id) { // 避免内容相同时不必要的重组
ItemComposable(item)
}
}
}

2. 状态管理

状态提升(State Hoisting)

// 非受控组件(自己持有状态)
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) { Text("$count") }
}

// 受控组件(状态提升到调用者)
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) { Text("$count") }
}

// 调用者
@Composable
fun ParentScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}

ViewModel 与 Compose 集成

@Composable
fun OrderScreen(viewModel: OrderViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is Event.NavigateToSuccess -> navController.navigate("success")
is Event.ShowError -> showSnackbar(event.msg)
}
}
}

when (uiState) {
is Loading -> CircularProgressIndicator()
is Success -> OrderContent((uiState as Success).order)
is Error -> ErrorView((uiState as Error).message)
}
}

3. 布局与修饰器

// Modifier 顺序很重要!
Box(
Modifier
.size(100.dp) // 先设大小
.background(Color.Blue) // 再设背景(背景在内容区域内)
.padding(8.dp) // 再设内边距
.clickable { } // 点击区域包含 padding
)

// Modifier 和 UI 组件顺序的弱和强规则
// padding -> background 让内边距内没有背景
Text(
text = "Hello",
modifier = Modifier
.background(Color.Red) // 背景包含 padding 内容
.padding(16.dp)
)

自定义 Layout

@Composable
fun MyCustomLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(content = content, modifier = modifier) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val width = placeables.maxOf { it.width }
val height = placeables.sumOf { it.height }
layout(width, height) {
var y = 0
placeables.forEach {
it.placeRelative(0, y)
y += it.height
}
}
}
}

4. 动画系统

// 状态动画
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 300)
)

// 可见性动画
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
MyContent()
}

// 转场动画
AnimatedContent(
targetState = currentPage,
transitionSpec = { fadeIn() togetherWith fadeOut() }
) { page ->
PageContent(page)
}

// 共享元素过渡
// Screen A
Box(Modifier.sharedElement(rememberSharedContentState("image"), ...) ) {
Image(...)
}
// Screen B 中同样的 sharedElement key 就会自动过渡

5. 性能优化

// 1. 长列表用 LazyColumn
LazyColumn {
items(items = list, key = { it.id }) { item -> // key 必须提供
ItemCard(item)
}
}

// 2. 避免在 composable 中读取 MutableList
// bad: list.forEach { ... } -> 无法追踪变化
// good: val items by rememberUpdatedState(newItems)

// 3. 使用 Baseline Profiles 提升启动性能
// 在 macrobenchmark 中定义核心路径,编译器预编译这些类

// 4. 不要在 Composable 中做老不必要的异步工作
// bad: 在 composable 中直接 launch
// good: 使用 LaunchedEffect(key) { ... }

// 5. 提副本加载验证
CompositionLocal

6. Compose 中的副作用

// LaunchedEffect:进入居和成立时执行,key 变化时重新执行
LaunchedEffect(userId) {
viewModel.loadUser(userId) // 副作用都在协程中执行
}

// SideEffect:每次重组成功后执行,不可以挂起
SideEffect {
analytics.reportScreen("OrderScreen") // 同步,安全访问外部系统
}

// DisposableEffect:组件离开时需清理
DisposableEffect(sensor) {
sensor.start()
onDispose { sensor.stop() } // 离开成居时执行
}

// rememberCoroutineScope:获取协程作用域,用于事件回调中
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doSomething() } }) { ... }

7. 资深面试题

  • Compose 与传统 View 的格局算法比较?
    • 传统 View:Measure(36项 spec) -> Layout -> Draw,可多次 Measure
    • Compose:单次 Measure(展开 Composable) -> 分配大小 -> 放置 -> 绘制
    • Compose 由设计上保证单次 Measure,性能更可预测
  • remember 在配置变化后是否会清除?
    • 默认会清除,用 rememberSaveable 写入 Bundle 保持状态
    • 复杂对象用 Saver 接口自定义序列化
  • derivedStateOf 和 remember { ... } 的区别?
    • remember 缓存结果,依赖的 key 变化时重新计算
    • derivedStateOf 用于导出 state,内部 state 变化时才重算,避免不必要重组
    • derivedStateOf 应包在 remember { }
  • 如何在 Compose 中使用传统 View?
    • AndroidView { context -> CustomView(context).apply { ... } }
    • AndroidViewBinding 使用 ViewBinding
    • setViewCompositionStrategy 管理成居策略

Kotlin 协程与 Flow 深入面经

Kotlin 协程深水区

1. 协程底层原理

CPS 变换(Continuation Passing Style)

  • Kotlin 编译器将 suspend 函数变换为状态机
  • 每个 suspend 点都是一个状态,函数被切分成多段
// 原始代码
suspend fun fetchUser(id: String): User {
val token = getToken() // suspend 点 1
val user = api.get(id) // suspend 点 2
return user
}

// 编译后(简化伪代码)
fun fetchUser(id: String, continuation: Continuation<User>) {
when (continuation.label) {
0 -> {
continuation.label = 1
getToken(continuation) // 挂起等待
}
1 -> {
val token = continuation.result as String
continuation.label = 2
api.get(id, continuation)
}
2 -> {
continuation.resume(continuation.result as User)
}
}
}

挂起与恢复

  • suspend 函数调用后,当前线程不阻塞,返回 COROUTINE_SUSPENDED 标记
  • IO 完成后,通过 continuation.resume(result) 恢复执行
  • 恢复时可能在不同线程(取决于 Dispatcher)

2. Flow 深入

冷流 vs 热流

// 冷流:collect 时才执行,每个 collector 独立
val coldFlow = flow {
println("开始生产")
emit(1)
emit(2)
}
// 没有 collect 时,内部代码不执行

// StateFlow:热流,始终有最新值
val stateFlow = MutableStateFlow(0)

// SharedFlow:热流,replay 控制新订阅者能收到的历史消息数
val sharedFlow = MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = 64)

背压处理

// buffer:生产者和消费者并发运行
flow { emit(heavyData()) }
.buffer(Channel.UNLIMITED)
.collect { process(it) }

// conflate:只保留最新值,丢弃中间值(适合 UI 状态)
stateFlow.conflate().collect { render(it) }

// collectLatest:新值到来时取消旧的处理
stateFlow.collectLatest { slowRender(it) } // 只完成最新一次

channelFlow 并发发射

// 允许在不同协程中 emit
val result = channelFlow {
launch { send(fetchFromNetwork()) } // 并发
launch { send(fetchFromCache()) } // 并发
}

3. 异常处理

// SupervisorJob:子协程独立失败
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch { failingTask() } // 失败不影响其他子协程
scope.launch { normalTask() } // 继续正常运行

// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, e ->
Log.e("TAG", "Uncaught exception", e)
}
scope.launch(handler) { riskyWork() }

// async 的异常在 await 时抛出
val deferred = async { riskyTask() }
try {
val result = deferred.await()
} catch (e: Exception) {
// 处理异常
}

// Flow 的异常处理
flow { emit(riskyOperation()) }
.catch { e -> emit(defaultValue) } // 只捕获 catch 上游异常
.collect { }

4. Dispatcher 选择

Dispatcher线程池适用场景
Main主线程UI 操作、轻量任务
IO64 个线程网络、文件读写
DefaultCPU 核数计算密集
Unconfined不指定测试用
// limitedParallelism 限制并发度
val limitedIO = Dispatchers.IO.limitedParallelism(4)
withContext(limitedIO) { /* 最多 4 个线程并发执行 */ }

5. 结构化并发

viewModelScope.launch {
// 并发执行两个请求
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts() }

// 任一失败,另一个自动取消
val user = userDeferred.await()
val posts = postsDeferred.await()
_uiState.value = Success(user, posts)
}

// coroutineScope:子协程全部完成才返回
suspend fun loadData() = coroutineScope {
val a = async { taskA() }
val b = async { taskB() }
Pair(a.await(), b.await()) // 任一失败都会取消其他
}

6. ViewModel 中的实践

class OrderViewModel(
private val repo: OrderRepository,
savedState: SavedStateHandle
) : ViewModel() {

private val orderId = savedState.get<String>("orderId")!!

// StateFlow 替代 LiveData
private val _uiState = MutableStateFlow<UiState>(Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

// 一次性事件用 SharedFlow
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events.asSharedFlow()

init {
loadOrder()
}

private fun loadOrder() = viewModelScope.launch {
_uiState.value = Loading
try {
val order = repo.getOrder(orderId)
_uiState.value = Success(order)
} catch (e: Exception) {
_uiState.value = Error(e.message)
}
}

fun submitOrder() = viewModelScope.launch {
try {
repo.submit(orderId)
_events.emit(Event.NavigateToSuccess)
} catch (e: Exception) {
_events.emit(Event.ShowError(e.message))
}
}
}

7. 资深面试题

  • suspend 函数为什么不能在普通函数中调用?
    • 编译后带 Continuation 参数,需要协程上下文提供该对象
    • 景默认解决:用 runBlocking 桥接(但会阻塞线程,仅用于测试)
  • StateFlow vs LiveData 如何选择?
    • 新项目一律推荐 StateFlow:要求初始值、协程原生、功能更强完整
    • LiveData 主要优势是自动处理生命周期,StateFlow 必须配合 repeatOnLifecycle
  • Flow 的 catch 操作符为什么只捕上游异常?
    • Flow 是流式数据,cat操作符在管道中,只能捕获它上游的异常
    • 下游(collect 中)的异常不会被捕获,需单独处理
  • 协程和线程的核心区别?
    • 协程是语言级的轻量调度单元,运行在线程之上
    • 一个线程可运行数千个协程
    • 协程切换无需内核介入,开销远小于线程切换