CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Android 开发入门/课时/四大组件
RawBlame
课时目录12 节

Android 开发入门

  1. 01Android 开发概览
  2. 02四大组件
  3. 03传统 View UI
  4. 04Jetpack Compose
  5. 05架构模式
  6. 06依赖注入
  7. 07本地存储
  8. 08网络层
  9. 09异步与响应式
  10. 10Firebase 生态
  11. 11测试与质量
  12. 12综合练习

四大组件

Activity/Service/Intent。

35 分钟

四大组件

这一课你将能做到

  1. 画出 Activity 完整生命周期图,并解释每个回调在何时触发
  2. 区分显式 Intent 与隐式 Intent 的使用场景,并能写出带数据的页面跳转
  3. 说明 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: 从停止状态重新启动")
    }
}

SecondActivity 接收数据:

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"
    }
}

运行后观察 Logcat:

  • 打开 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
}

在 Activity 中控制:

// 启动服务
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" />

讲清楚要点

  1. Activity 生命周期是面试高频考点:必须能背出 onCreate → onStart → onResume → onPause → onStop → onDestroy,以及 onRestart 的触发条件。横竖屏切换默认会销毁重建,用 onSaveInstanceState 或 ViewModel 保存状态。

  2. Intent 分显式和隐式:显式指定目标类名,用于应用内跳转;隐式只声明 Action,系统或用户选择目标,用于跨应用。PendingIntent 是「延迟执行的 Intent」,常用于通知点击。

  3. Service 两种启动方式:startService(独立运行,不返回结果)和 bindService(绑定后可通过 Binder 通信)。Android 8.0+ 后台限制严格,长期任务必须用前台服务 + 通知。

  4. BroadcastReceiver 静态 vs 动态:静态注册在 Manifest 里,App 未启动也能接收(如开机广播),但 Android 8.0+ 对隐式广播限制很多。动态注册更灵活,随组件生命周期绑定。

  5. 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 内部线程

卡点急救

  1. 点击按钮跳转崩溃:检查目标 Activity 是否在 Manifest 声明,包名是否正确。最常见原因是忘记声明或声明了错误的 android:name。

  2. Service 启动后立即被杀:Android 8.0+ 后台启动限制。必须调用 startForegroundService 并在 5 秒内调用 startForeground 显示通知。

  3. 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+ 对隐式广播限制严格。动态注册在代码中,随组件生命周期,更灵活且不受限制。频繁事件(如电量变化)必须动态注册。

自测三问

  1. 从 MainActivity 打开 SecondActivity,再按返回键,两个 Activity 各经历哪些生命周期?
  2. 什么场景必须用前台 Service?如何启动?
  3. ContentProvider 和直接访问数据库有什么区别?

小结一句话

四大组件是 Android 的「积木」:Activity 管界面、Service 管后台、BroadcastReceiver 管事件、ContentProvider 管数据共享;掌握生命周期和系统限制是进阶的第一步。

延伸

  • Activity 生命周期官方文档
  • Service 概览
  • 下一课:传统 View UI——RecyclerView、Material 组件与自定义 View。
加载本节练习…
← 上一课Android 开发概览下一课 →传统 View UI