四大组件
Activity/Service/Intent。
四大组件
这一课你将能做到
- 画出 Activity 完整生命周期图,并解释每个回调在何时触发
- 区分显式 Intent 与隐式 Intent 的使用场景,并能写出带数据的页面跳转
- 说明 Service、BroadcastReceiver、ContentProvider 各自的适用场景与限制
为什么要学
四大组件是 Android 应用的骨架,任何 App 都至少用到其中两个。Activity 管理界面,Service 处理后台任务,BroadcastReceiver 响应系统事件,ContentProvider 共享数据。不理解它们,就无法理解 Android 的进程模型、内存管理和权限体系。
人话:Activity 就像 Web 里的「页面」,Service 是「Web Worker」,BroadcastReceiver 是「事件监听器」,ContentProvider 是「API 接口」。Android 系统通过这四种「积木」来组装和调度应用。
面试必考:Activity 生命周期、启动模式、Service 的两种启动方式、BroadcastReceiver 的注册方式差异。
先建立图像
┌────────────────────────────────────────┐
│ 你的 App 进程 │
│ ┌─────────┐ ┌─────────┐ │
│ │ Activity│ │ Activity│ ← 用户可见 │
│ │ (页面) │ │ (页面) │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────────────┴────┐ │
│ │ Service │ ← 后台运行 │
│ │ (音乐播放/下载) │ │
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ BroadcastReceiver │ ← 系统事件 │
│ │ (开机/网络变化) │ │
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ ContentProvider │ ← 数据共享 │
│ │ (读取通讯录/媒体) │ │
│ └──────────────────────┘ │
└────────────────────────────────────────┘
跟着做
步骤 1:Activity 生命周期实战
创建两个 Activity,在 MainActivity 中打印生命周期日志:
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "Lifecycle"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate: Activity 被创建")
findViewById<Button>(R.id.btnOpenSecond).setOnClickListener {
// 显式 Intent 跳转到 SecondActivity
val intent = Intent(this, SecondActivity::class.java).apply {
putExtra("username", "CodeRoadMap")
putExtra("level", 3)
}
startActivity(intent)
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart: Activity 即将可见")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume: Activity 获得焦点,可交互")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: Activity 失去焦点,但仍可见")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop: Activity 完全不可见")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: Activity 被销毁")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart: 从停止状态重新启动")
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val username = intent.getStringExtra("username")
val level = intent.getIntExtra("level", 0)
findViewById<TextView>(R.id.tvInfo).text = "用户: $username, 等级: $level"
}
}
- 打开 App:
onCreate → onStart → onResume
- 跳转到 SecondActivity:
MainActivity onPause → SecondActivity onCreate/onStart/onResume → MainActivity onStop
- 按返回键:
SecondActivity onPause → MainActivity onRestart/onStart/onResume → SecondActivity onStop/onDestroy
步骤 2:隐式 Intent 打开系统功能
// 打开浏览器
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://coderoadmap.com"))
startActivity(browserIntent)
// 分享文本
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "推荐这个学习平台: CodeRoadMap")
}
startActivity(Intent.createChooser(shareIntent, "分享到"))
步骤 3:Service 前台服务(音乐播放场景)
class MusicService : Service() {
private var mediaPlayer: MediaPlayer? = null
override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer.create(this, R.raw.sample_music)
Log.d("MusicService", "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
"ACTION_PLAY" -> play()
"ACTION_PAUSE" -> pause()
"ACTION_STOP" -> stopSelf()
}
return START_STICKY // 被杀死后尝试重启
}
private fun play() {
mediaPlayer?.start()
// Android 8.0+ 必须显示通知
val notification = NotificationCompat.Builder(this, "music_channel")
.setContentTitle("正在播放")
.setContentText("CodeRoadMap 背景音乐")
.setSmallIcon(R.drawable.ic_music_note)
.build()
startForeground(1, notification)
}
private fun pause() {
mediaPlayer?.pause()
}
override fun onDestroy() {
mediaPlayer?.release()
mediaPlayer = null
Log.d("MusicService", "onDestroy")
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
// 启动服务
val playIntent = Intent(this, MusicService::class.java).apply {
action = "ACTION_PLAY"
}
startService(playIntent)
// 停止服务
stopService(Intent(this, MusicService::class.java))
步骤 4:BroadcastReceiver 监听网络变化
class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork
val isConnected = network != null
Log.d("NetworkReceiver", "网络状态: ${if (isConnected) "已连接" else "已断开"}")
}
}
动态注册(推荐,Android 8.0+ 静态注册有限制):
class MainActivity : AppCompatActivity() {
private val networkReceiver = NetworkReceiver()
override fun onStart() {
super.onStart()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(networkReceiver, filter)
}
override fun onStop() {
super.onStop()
unregisterReceiver(networkReceiver)
}
}
步骤 5:ContentProvider 读取通讯录
fun readContacts(context: Context): List<String> {
val contacts = mutableListOf<String>()
val cursor = context.contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(0)
val phone = it.getString(1)
contacts.add("$name: $phone")
}
}
return contacts
}
<uses-permission android:name="android.permission.READ_CONTACTS" />
讲清楚要点
-
Activity 生命周期是面试高频考点:必须能背出 onCreate → onStart → onResume → onPause → onStop → onDestroy,以及 onRestart 的触发条件。横竖屏切换默认会销毁重建,用 onSaveInstanceState 或 ViewModel 保存状态。
-
Intent 分显式和隐式:显式指定目标类名,用于应用内跳转;隐式只声明 Action,系统或用户选择目标,用于跨应用。PendingIntent 是「延迟执行的 Intent」,常用于通知点击。
-
Service 两种启动方式:startService(独立运行,不返回结果)和 bindService(绑定后可通过 Binder 通信)。Android 8.0+ 后台限制严格,长期任务必须用前台服务 + 通知。
-
BroadcastReceiver 静态 vs 动态:静态注册在 Manifest 里,App 未启动也能接收(如开机广播),但 Android 8.0+ 对隐式广播限制很多。动态注册更灵活,随组件生命周期绑定。
-
ContentProvider 是进程间数据共享的标准方式:系统通讯录、媒体库都通过它暴露数据。自定义 Provider 时要在 Manifest 声明 authorities 和权限。
你可能会懵的地方
-
Activity 被回收后数据怎么办?
用 onSaveInstanceState 保存轻量数据,或更好的是用 ViewModel + SavedStateHandle。ViewModel 在配置变更时保留,在进程被杀时可通过 SavedStateHandle 恢复。
-
Service 和 Thread 什么关系?
Service 默认跑在主线程!耗时操作必须在 Service 里开线程或用 IntentService(已废弃,推荐 WorkManager)。
-
Fragment 是不是第五大组件?
不是,Fragment 是 Activity 的「碎片」,不能独立存在。但 Compose 时代 Fragment 使用场景大幅减少,单 Activity 多 Compose 页面成为主流。
-
四大组件都要在 Manifest 声明吗?
Activity、Service、ContentProvider 必须声明。BroadcastReceiver 可以动态注册,但静态注册也必须在 Manifest 声明。
-
App 退出后 Service 还在跑吗?
取决于启动方式。startService 启动的会持续到 stopSelf 或系统回收;bindService 在所有客户端解绑后销毁。前台服务优先级最高,不易被杀。
错法对照
| 错法 | 问题 | 正解 |
|---|
在 onCreate 里做网络请求 | 阻塞主线程,ANR | 用 lifecycleScope.launch 或 ViewModel |
| 忘记在 Manifest 声明 Activity | 运行时崩溃 ActivityNotFoundException | 所有 Activity 必须声明 |
| 在 BroadcastReceiver 里做耗时操作 | 10 秒超时,系统杀掉 | 启动 Service 或用 goAsync |
| 用静态广播监听频繁事件 | 耗电,Android 8.0+ 被限制 | 动态注册,或改用 WorkManager |
| 在 Service 主线程直接播放音乐 | UI 卡顿 | 用 HandlerThread 或 ExoPlayer 内部线程 |
卡点急救
-
点击按钮跳转崩溃:检查目标 Activity 是否在 Manifest 声明,包名是否正确。最常见原因是忘记声明或声明了错误的 android:name。
-
Service 启动后立即被杀:Android 8.0+ 后台启动限制。必须调用 startForegroundService 并在 5 秒内调用 startForeground 显示通知。
-
BroadcastReceiver 收不到广播:Android 8.0+ 大部分隐式广播不能静态注册。改为动态注册,或检查 IntentFilter 的 Action 字符串是否写错。
面试常问
-
Activity 的四种启动模式?
standard(默认,每次新建实例)、singleTop(栈顶复用)、singleTask(栈内复用,清掉上面所有)、singleInstance(独立栈,系统级单例)。singleTask 常用于主页,singleInstance 用于来电界面等需要独立栈的场景。
-
Service 的 onStartCommand 返回值含义?
START_STICKY:被杀后尝试重启,但不重传 Intent。START_NOT_STICKY:被杀后不重启。START_REDELIVER_INTENT:被杀后重启并重传 Intent,用于需要确保执行完的任务。
-
BroadcastReceiver 静态注册和动态注册的区别?
静态注册在 Manifest 中,App 未运行也能接收,但 Android 8.0+ 对隐式广播限制严格。动态注册在代码中,随组件生命周期,更灵活且不受限制。频繁事件(如电量变化)必须动态注册。
自测三问
- 从 MainActivity 打开 SecondActivity,再按返回键,两个 Activity 各经历哪些生命周期?
- 什么场景必须用前台 Service?如何启动?
- ContentProvider 和直接访问数据库有什么区别?
小结一句话
四大组件是 Android 的「积木」:Activity 管界面、Service 管后台、BroadcastReceiver 管事件、ContentProvider 管数据共享;掌握生命周期和系统限制是进阶的第一步。
延伸