四大组件与基础
四大组件是 Android 的根基,中级面试常深挖生命周期、启动模式、跨进程。
一、Activity
生命周期
onCreate → onStart → onResume →(运行)→ onPause → onStop → onDestroy;onRestart 用于从 onStop 返回。
关键场景:
- A 启动 B:
A.onPause → B.onCreate → B.onStart → B.onResume → A.onStop(B 的 onResume 在 A 的 onStop 之前)。 - 透明/Dialog 主题的 B 覆盖 A:A 只 onPause 不 onStop。
- 横竖屏旋转:默认销毁重建,走完整生命周期 +
onSaveInstanceState/onRestoreInstanceState;配configChanges可避免重建只回调onConfigurationChanged。
启动模式(LaunchMode)
- standard:默认,每次新建实例,入当前任务栈。
- singleTop:栈顶复用,栈顶是它则走
onNewIntent,否则新建。(防通知重复打开) - singleTask:栈内唯一,已存在则复用并清除其上的 Activity(clearTop),走 onNewIntent。(主页/首页)
- singleInstance:独占一个任务栈。(来电、闹钟)
taskAffinity 指定任务栈归属;Intent flag(FLAG_ACTIVITY_NEW_TASK 等)可动态控制。
二、Fragment
- 生命周期比 Activity 多:
onAttach → onCreate → onCreateView → onViewCreated → onStart → onResume ...→ onDestroyView → onDestroy → onDetach。 - commit vs commitNow vs commitAllowingStateLoss:commit 异步;commitNow 同步;前两者在 onSaveInstanceState 后调用会抛 IllegalStateException,allowingStateLoss 容忍但可能丢状态。
- 现代用
FragmentStateAdapter+ ViewPager2 替代旧懒加载;getViewLifecycleOwner 观察 LiveData 防泄漏。 - 为什么用 Fragment 而非多 Activity:轻量、复用、共享 ViewModel、单 Activity 架构配合 Navigation。
三、Service
- 启动方式:
startService(独立运行,需手动 stopSelf/stopService)vsbindService(绑定,随调用者销毁,可通信)。 - 前台服务:必须
startForeground显示通知(Android 8+ 后台限制),用于音乐、定位、下载。Android 12+ 还有前台服务类型限制。 - IntentService 已废弃:用
WorkManager或协程替代后台任务。 - Service 不是线程:默认运行在主线程,耗时操作仍需开子线程,否则 ANR。
四、BroadcastReceiver
- 注册方式:
- 静态注册(Manifest):适合系统或跨应用广播;Android 8.0 起对很多隐式广播做了限制,普通应用不要依赖静态注册来长期唤醒进程。
- 动态注册(
registerReceiver):跟随 Activity/Fragment/Service 生命周期,常在onStart/onResume注册、onStop/onPause反注册,用于只在界面可见或组件存活时接收。
- 分发流程:
sendBroadcast/sendOrderedBroadcast → ActivityManager/系统广播队列匹配 IntentFilter → 找到静态/动态 Receiver → 目标进程已在则直接回调,未启动且允许则拉起进程 → 主线程调用 onReceive。 - 类型边界:
- 普通广播:异步分发,多个 Receiver 接收顺序不应作为业务依赖。
- 有序广播:按优先级依次分发,前一个 Receiver 可通过
setResult*改结果,也可在允许的场景中abortBroadcast()终止后续接收;因此更像一条责任链。 - LocalBroadcastManager / 粘性广播:都已不推荐。应用内事件优先用
StateFlow/SharedFlow、回调、Lifecycle-aware observer 或明确的 Repository 状态流。
class BatteryReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// onReceive 运行在主线程,只做轻量解析;耗时任务交给 WorkManager/协程/Service
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
Log.d("BatteryReceiver", "level=$level")
}
}
private val receiver = BatteryReceiver()
override fun onStart() {
super.onStart()
registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
override fun onStop() {
unregisterReceiver(receiver)
super.onStop()
}
常见错误:
- 在
onReceive中做网络/数据库等耗时操作,导致主线程卡顿甚至 ANR;需要异步任务时用goAsync()争取短暂收尾,或转交 WorkManager/前台 Service。 - 动态注册后忘记反注册,导致组件泄漏或重复收到广播。
- 把广播当作应用内事件总线,导致来源不清、生命周期难控;中级面试更推荐说明“跨组件/跨应用通知可以用广播,应用内状态流优先用 Flow”。
- 忽略 Android 8.0+ 隐式广播限制,把 Manifest 静态注册当成稳定后台唤醒手段。
面试答题结构:先说“注册方式与生命周期”→ 再说“普通/有序广播分发差异”→ 补“8.0+ 限制与现代替代”→ 最后强调“onReceive 主线程、轻量处理、及时反注册”。
五、ContentProvider
- 定位:跨进程数据共享的标准组件,用
content://authority/path/id形式的 URI 寻址,对外暴露query/insert/update/delete/openFile等接口。系统通讯录、媒体库就是典型例子。 - 对象关系:
ContentResolver:调用方入口,不直接依赖 Provider 实现类。ContentProvider:被调用方组件,负责权限校验、URI 分发、数据读写。UriMatcher:把不同 URI path 映射到不同业务分支,避免手写大量字符串判断。
- 跨进程调用流:
调用方 ContentResolver.query(uri) → 系统根据 authority 找到目标 Provider → 必要时启动目标进程并创建 Provider → 通过 Binder 进入 Provider 的 query/insert/update/delete → Provider 访问 SQLite/文件/内存数据 → Cursor/结果返回调用方。
class UserProvider : ContentProvider() {
private val matcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI("com.example.user", "users", 1)
addURI("com.example.user", "users/#", 2)
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? = when (matcher.match(uri)) {
1 -> queryAllUsers()
2 -> queryUser(uri.lastPathSegment!!.toLong())
else -> throw IllegalArgumentException("Unknown uri: $uri")
}
}
// 调用方只依赖 URI + ContentResolver
val cursor = context.contentResolver.query(
Uri.parse("content://com.example.user/users/42"),
null,
null,
null,
null,
)
- 为什么能早于 Application 初始化:系统在创建应用进程时会先安装并创建该进程声明的 ContentProvider,然后再进入
Application.onCreate()。Jetpack App Startup 利用这个时机做库初始化,但现代工程应收敛自动初始化,避免启动链路不可见、冷启动变慢。 - 权限与边界:对外暴露 Provider 要配置读写权限、
exported、URI grant 或签名级权限;内部初始化 Provider 不应暴露敏感数据。Provider 方法可能被跨进程并发调用,数据库访问要考虑线程安全和事务。
常见错误:
- 只记“CRUD + URI”,说不清
ContentResolver → authority → Provider → Binder的调用链。 - 把 Provider 当普通单例初始化器滥用,导致 SDK 隐式启动、冷启动耗时难定位。
- 忽略
exported/permission/grantUriPermission,把内部数据暴露给其他应用。
面试答题结构:先解释“URI + ContentResolver/Provider 解耦”→ 再画出“跨进程 Binder 调用链”→ 举“通讯录/媒体库/App Startup”例子 → 最后补“权限、并发、启动成本”的坑。
六、序列化与 Context
- Serializable vs Parcelable:Serializable 是 Java 反射序列化,慢、产生大量临时对象;Parcelable 是 Android 专为内存/IPC 设计,快但代码繁琐(Kotlin 用
@Parcelize自动生成)。跨进程/Intent 传对象用 Parcelable。 - Context 类型:Application Context(全局,生命周期最长,不能用于 UI/Dialog)、Activity Context(带主题,可弹窗,注意泄漏)。getApplicationContext 用于单例避免持有 Activity。
进阶补充:现代组件 API 与版本限制
Activity Result API
startActivityForResult 已不推荐。Activity Result API 把启动和结果回调绑定到 lifecycle,避免配置变更后的回调丢失。
运行时权限
Android 6.0 后危险权限运行时申请;Android 13 引入通知权限;Android 14 对前台服务类型和后台启动有更多限制。面试要按版本讲,不要只背一套老流程。
Fragment 回退栈与状态丢失
commit() 是异步;commitNow() 立即执行但不能加入 back stack。onSaveInstanceState 后再提交可能 state loss。setMaxLifecycle 常用于 ViewPager2 控制页面生命周期。
前台 Service 类型
前台服务必须有用户可见通知,新版本要求声明类型,如 location、mediaPlayback、dataSync。不能把前台服务当万能保活工具。
**追问:**为什么 Fragment 会出现状态丢失?因为系统已保存 Activity 状态后,再提交 Fragment 事务无法保证恢复一致性。
高频面试题
Q1:singleTask 和 singleInstance 区别? singleTask 在所属任务栈内唯一,可与其他 Activity 共栈;singleInstance 独占一个任务栈,栈内只有它一个。
Q2:横竖屏切换 Activity 生命周期?如何保存数据? 默认销毁重建(onPause→onStop→onDestroy→onCreate→…)。用 onSaveInstanceState 保存临时数据,或用 ViewModel(配置变更时存活)保存。配 android:configChanges 可避免重建。
Q3:onSaveInstanceState 何时调用?和 onPause 顺序? 在 Activity 可能被系统销毁前调用(如旋转、内存不足、切后台)。Android P 后在 onStop 之后调用。正常返回键退出不调用(因为是用户主动销毁)。
Q4:Serializable 和 Parcelable 怎么选? 内存传递、IPC、性能敏感场景用 Parcelable;持久化到磁盘或简单场景可用 Serializable。Kotlin 用 @Parcelize 减少模板代码。
Q5:为什么不能用 Application Context 弹 Dialog? Dialog 需要 Activity 的主题和 Window token,Application Context 没有,会抛 BadTokenException。
Q6:Service 和 Thread 区别?Service 能做耗时操作吗? Service 是组件,运行在主线程,不是线程;它的意义是“后台运行、优先级高于普通线程、可被系统管理“。耗时操作必须在 Service 内另开线程,否则 ANR。
Q7:8.0 之后后台限制有哪些? 后台 Service 受限(需前台服务 + 通知)、隐式广播大量禁止静态注册、后台定位受限。推荐用 WorkManager 处理可延迟的后台任务。