从平台架构到四大组件,掌握 Android 开发的核心概念
核心方法论:系统分析,层层递进查案
「诸位,今日我们受理一桩大案——Android 系统之全貌。此案牵涉五层机构、四大要员、无数案卷文书。查案讲究层层分析、步步为营,切不可囫囵吞枣。且随本官从底层内核一路勘察到应用之巅,逐级查明每一层的职责与运作之道。待审理完毕,诸位便能洞悉这座庞大体系的运转真相。」
诸位,要查明 Android 之全貌,须先勘察其体制架构。Android 乃基于 Linux 内核的开源移动操作系统,其架构如同大唐朝廷,自下而上共分五级衙门,各司其职,层层递进:
┌─────────────────────────────────────────────┐
│ Applications (应用层) │
│ 你写的 App、系统自带 App (电话、短信...) │
├─────────────────────────────────────────────┤
│ Application Framework (框架层) │
│ ActivityManager, WindowManager, 各种 Manager │
├─────────────────────────────────────────────┤
│ Android Runtime (ART) + 核心库 │
│ 运行 DEX 字节码、标准 Java/Kotlin 库 │
├─────────────────────────────────────────────┤
│ Hardware Abstraction Layer (HAL) │
│ 摄像头、蓝牙、传感器等硬件的抽象接口 │
├─────────────────────────────────────────────┤
│ Linux Kernel (内核层) │
│ 驱动程序、内存管理、进程管理、安全机制 │
└─────────────────────────────────────────────┘
层层分析可知:最底层的 Linux Kernel 如同朝廷根基,掌管驱动、内存与安全;HAL 是连接硬件与上层的中间吏员;ART 运行时如同朝廷律法,确保所有代码按规矩运行;而你我日常办案打交道最多的,便是 框架层 (Framework)——Android SDK 提供的各种 API(如 Activity、View、Intent)皆属此层,好比朝廷六部,是你上达天听、下传旨意的关键枢纽。
经本官勘察,Android 应用由四大核心要员构成,各有分工,缺一不可:
| 组件 | 作用 | 典型场景 |
|---|---|---|
Activity |
提供用户界面的"屏幕" | 登录页、首页、设置页 |
Service |
在后台执行长时间运行的操作 | 音乐播放、文件下载 |
BroadcastReceiver |
监听和响应系统或应用广播 | 开机启动、网络变化 |
ContentProvider |
管理应用间共享的数据 | 通讯录、媒体库 |
本章重点
四大要员之中,Activity 是最为基础、出场最频繁的角色——如同大理寺的主审堂,是一切审理活动的核心场所。本章将围绕 Activity 展开审理,Service 等其他要员会在后续案件中逐步登场。
本官先审理第一桩关键案情:Activity 究竟为何物?简而言之,一个 Activity 就是一个"公堂"——你在手机上所见的每一个界面(登录页、聊天列表、设置页),背后都对应一个 Activity,正如大唐每审理一桩案件,便要开设一次公堂。
若你有 Web 开发经验,可将 Activity 理解为一个"堂审主官"——它负责布置公堂(创建界面)、审理案情(处理用户交互)、并按律令管理整个审理过程的起止(生命周期)。
查明真相须知:Activity 从开堂到散堂,会经历一系列状态变化。系统如同朝廷典仪官,在每个状态转换时鸣锣通报,调用对应的回调方法。且看此流程图,犹如一份详尽的审理程序案卷:
Activity 生命周期流程:
启动 Activity
│
▼
┌──────────┐
│ onCreate │ ← 初始化:加载布局、绑定数据
└────┬─────┘
▼
┌──────────┐
│ onStart │ ← 可见但尚未获得焦点
└────┬─────┘
▼
┌──────────┐
│ onResume │ ← 前台运行,用户可交互
└────┬─────┘
│
(用户离开/被遮挡)
│
▼
┌──────────┐
│ onPause │ ← 部分可见,失去焦点
└────┬─────┘
▼
┌──────────┐
│ onStop │ ← 完全不可见
└────┬─────┘
▼
┌───────────┐
│ onDestroy │ ← 被销毁,释放所有资源
└───────────┘
此六步如同审案六道程序:onCreate 乃升堂布置之时;onStart 公堂已开、众人就位但尚未正式问案;onResume 惊堂木一拍、审理正式开始;onPause 好比有人上堂递折子,审理暂停片刻;onStop 散堂退朝,公堂已无人可见;onDestroy 则是案件归档、拆除公堂,一切恢复原状。
且看此案卷中的代码实例,记录了一个 Activity 在各个生命阶段的行为:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // 加载布局文件
// 在这里做初始化工作
Log.d("Lifecycle", "onCreate: Activity 正在创建")
}
override fun onStart() {
super.onStart()
Log.d("Lifecycle", "onStart: Activity 即将可见")
}
override fun onResume() {
super.onResume()
Log.d("Lifecycle", "onResume: Activity 已在前台")
}
override fun onPause() {
super.onPause()
Log.d("Lifecycle", "onPause: Activity 即将失去焦点")
// 保存轻量级数据(如用户输入的草稿)
}
override fun onStop() {
super.onStop()
Log.d("Lifecycle", "onStop: Activity 已不可见")
// 释放不需要的资源
}
override fun onDestroy() {
super.onDestroy()
Log.d("Lifecycle", "onDestroy: Activity 正在被销毁")
// 释放所有资源、取消注册监听器
}
}
本官提醒诸位:理解生命周期的关键不在死记方法名称——那不过是背诵律条而已。真正要紧的是查明 在哪个阶段该办哪些事,如同查案要在正确的时机调取正确的证据:
| 回调方法 | 应该做什么 | 不该做什么 |
|---|---|---|
onCreate |
初始化布局、绑定 ViewModel | 启动动画(界面还没显示) |
onResume |
恢复暂停的操作、开始动画 | 做耗时操作(会卡界面) |
onPause |
暂停动画、保存草稿 | 做耗时的数据库写入 |
onStop |
保存数据、释放重量级资源 | 更新 UI(用户已看不到) |
onDestroy |
释放所有资源、取消订阅 | 依赖此方法一定会被调用 |
注意
本官查明一项关键隐情:系统在内存不足时可能直接杀掉进程,onDestroy 并不保证一定会被调用——好比朝廷突遇战事,公堂可能未及正式散堂便被紧急清场。因此,重要案卷(数据)应在 onPause 或 onStop 阶段便妥善归档保存,切不可拖延至最后。
接下来勘察第二桩要案:组件之间如何传递消息?答案便是 Intent(意图)。Intent 是 Android 各组件之间通信的核心机制,犹如大唐朝廷中各衙门之间传递的公文与圣旨——你可以用它来启动 Activity、启动 Service、发送广播,一纸 Intent 便可调动整个系统。
显式 Intent 好比一道点名圣旨,明确指定了目标官员的姓名与衙门。它用于应用内部的页面跳转,目标明确、毫不含糊:
// 从当前 Activity 跳转到 DetailActivity
val intent = Intent(this, DetailActivity::class.java)
startActivity(intent)
隐式 Intent 则如一道不点名的朝廷通告——只声明"需要一个能办此事之人",由系统(吏部)找到能处理该事务的合适 App。此乃跨应用协作的妙法:
// 打开浏览器访问网页
val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://kotlinlang.org"))
startActivity(webIntent)
// 拨打电话(跳转到拨号界面,不会直接拨出)
val dialIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086"))
startActivity(dialIntent)
// 分享文本
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "来看看这个 Kotlin 教程!")
}
startActivity(Intent.createChooser(shareIntent, "分享到"))
Intent 不仅可传递旨意,还能附带案卷证据(额外数据),以键值对形式在 Activity 之间流转。本官且用一个实例来审理此机制:
// ===== Activity A:发送数据 =====
class ListActivity : AppCompatActivity() {
private fun openDetail(articleId: Int, title: String) {
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("article_id", articleId) // 传递 Int
putExtra("article_title", title) // 传递 String
}
startActivity(intent)
}
}
// ===== Activity B:接收数据 =====
class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
// 从 Intent 中取出数据
val articleId = intent.getIntExtra("article_id", -1)
val title = intent.getStringExtra("article_title") ?: "无标题"
Log.d("Detail", "文章 ID=$articleId, 标题=$title")
}
}
最佳实践
本官经验之谈:若传递的案卷内容复杂,推荐使用 Parcelable 或 Serializable 接口将证物打包。在现代 Android 办案中,更可通过 ViewModel + Navigation 组件来统一管理各公堂之间的案卷共享,既规范又高效。
继续勘察下一层线索。Android 将代码与资源分开管理,正如朝廷将律法条文与案卷档案分库存放。资源文件(布局、图片、字符串等)统一归入 res/ 目录之下,编译时系统会自动生成一个 R 类作为索引册,供你随时调阅。
让本官带你勘察这座"案卷档案室"的布局:
app/src/main/res/
├── layout/ ← 布局文件 (XML 描述的界面)
│ ├── activity_main.xml
│ └── activity_detail.xml
├── drawable/ ← 图片和矢量图
│ ├── ic_logo.png
│ └── bg_gradient.xml
├── mipmap/ ← 应用图标(不同分辨率)
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
├── values/ ← 字符串、颜色、尺寸等常量
│ ├── strings.xml
│ ├── colors.xml
│ └── dimens.xml
└── values-en/ ← 英文资源(多语言支持)
└── strings.xml
每个子目录如同不同类别的案卷架:layout/ 存放公堂布局图纸,drawable/ 存放画像证据,values/ 存放各类常量记录。分门别类,井然有序——查案时方能迅速调取所需。
本官特别提醒:所有面向百姓(用户)的文字告示,都应统一登记在 strings.xml 中,而非硬编码在律法(代码)里。如此方能实现多语言通告的便利切换:
<!-- res/values/strings.xml (默认中文) -->
<resources>
<string name="app_name">我的应用</string>
<string name="welcome_msg">欢迎回来,%s!</string>
<string name="btn_login">登录</string>
</resources>
<!-- res/values-en/strings.xml (英文) -->
<resources>
<string name="app_name">My App</string>
<string name="welcome_msg">Welcome back, %s!</string>
<string name="btn_login">Login</string>
</resources>
色彩与尺寸亦有专门的案卷登记簿,统一管理、统一调用:
<!-- res/values/colors.xml -->
<resources>
<color name="primary">#7F5AF0</color>
<color name="on_primary">#FFFFFF</color>
<color name="background">#F8F9FA</color>
</resources>
<!-- res/values/dimens.xml -->
<resources>
<dimen name="padding_standard">16dp</dimen>
<dimen name="text_body">16sp</dimen>
<dimen name="text_title">20sp</dimen>
</resources>
编译器会根据 res/ 目录下的全部资源自动生成一个 R 类,此乃案卷档案的总索引册。你通过它便能在代码中精准调阅任何资源:
// 在 Activity 中使用资源
val appName = getString(R.string.app_name) // "我的应用"
val welcome = getString(R.string.welcome_msg, "小明") // "欢迎回来,小明!"
val color = getColor(R.color.primary) // 颜色值
// 在布局 XML 中引用
// android:text="@string/btn_login"
// android:textColor="@color/primary"
// android:padding="@dimen/padding_standard"
本官还须查明一项重要度量制度:Android 不用 px(像素),而采用密度无关的标准单位,确保在各种屏幕上呈现一致——如同朝廷颁布统一的度量衡:
简单记忆
尺寸用 dp,字号用 sp。一般情况下,1dp 在 160dpi 的屏幕上等于 1px。此乃本官查案多年总结的速记口诀。
现在本官要审理一份至关重要的文书:AndroidManifest.xml。此文件乃每个 Android 应用的"户籍档案"与"官凭印信"。它声明了应用的基本信息、所有注册在编的组件、所需权限等。系统在安装和运行 App 时都要审查此档案——少一条记录,便可能案件搁浅(应用崩溃)。
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<!-- 权限声明 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- 主 Activity:设置为启动入口 -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 其他 Activity 也需要在这里注册 -->
<activity android:name=".DetailActivity" />
</application>
</manifest>
本官逐条审理各项要点:
package:应用的唯一身份编号(包名),犹如官员的籍贯户帖,在整个应用市场中独一无二。uses-permission:声明应用所需的系统权限。此乃向朝廷请示之文书——未声明 INTERNET 权限便不能访问网络,如同未获朝廷批文便不可调兵。android:icon / android:label:应用的官印图案与名号。android:theme:应用的全局外观风格,如同衙门的装饰规制。intent-filter:包含 MAIN + LAUNCHER 的 Activity 会出现在手机桌面的应用列表中——此乃标注"正门入口"的门匾。android:exported="true":Android 12+ 要求所有带 intent-filter 的组件必须显式声明是否可被外部访问,犹如新律规定衙门须明示是否对外接案。常见错误
本官查办过无数此类疏忽之案:新建的 Activity 忘记在 AndroidManifest.xml 中注册,乃新手最常犯之错。运行时会抛出 ActivityNotFoundException——好比差役去传唤一位未入户籍的官员,自然无从查找。若你使用 Android Studio 的模板创建 Activity,它会自动帮你办妥登记手续。
最后一桩待审之案:Gradle 构建系统。它是 Android 工程的总管衙门,负责将你的代码、资源、依赖统统编排成一个可运行的应用。一个典型的 Android 项目有两份关键构建文书:
位于项目根目录,如同朝廷通令,配置所有衙门(模块)共用的构建规则:
// build.gradle.kts (Project)
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
位于 app/ 目录下,是每个具体衙门(模块)的详细施政纲领:
// app/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.example.myapp"
compileSdk = 35 // 编译时使用的 SDK 版本
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24 // 最低支持 Android 7.0
targetSdk = 35 // 目标 SDK 版本
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// AndroidX 核心库
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
// 测试
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
}
本官层层分析这三个版本参数——它们如同朝廷法令的适用范围,决定了你的应用能在哪些设备上运行:
| 参数 | 含义 | 建议 |
|---|---|---|
compileSdk |
编译时使用的 API 版本 | 始终用最新的稳定版 |
targetSdk |
应用适配的目标 API 版本 | Google Play 有最低要求 |
minSdk |
应用支持的最低 API 版本 | 24 (Android 7.0) 覆盖 99%+ 设备 |
引入外部依赖如同调用各地衙门的资源协助办案,须通过规范的文书格式声明:
dependencies {
// implementation: 仅当前模块可用(最常用)
implementation("androidx.core:core-ktx:1.13.1")
// api: 当前模块和依赖本模块的模块都可用
// 适用于你在封装的库模块中暴露的 API
api("com.google.code.gson:gson:2.11.0")
// testImplementation: 仅单元测试可用
testImplementation("junit:junit:4.13.2")
// androidTestImplementation: 仅 Android 仪器测试可用
androidTestImplementation("androidx.test.ext:junit:1.2.1")
}
现代 Android 项目推荐使用 版本目录 来集中管理依赖版本——如同朝廷将所有法令条文的版本号统一登记在册,修订时只改总册一处,各衙门自动同步。文件位于 gradle/libs.versions.toml:
# gradle/libs.versions.toml
[versions]
agp = "8.7.0"
kotlin = "2.0.21"
coreKtx = "1.13.1"
appcompat = "1.7.0"
material = "1.12.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
在 build.gradle.kts 中通过 libs.xxx 引用,如 libs.androidx.core.ktx。如此一来,各模块共享同一版本,升级时只改一处——正如朝廷修法,改总册则天下同行。
案件审理总结
本章案件至此审理完毕。本官带领诸位层层勘察了 Android 系统的五层架构、审理了 Activity 生命周期的六道程序、查明了 Intent 传递旨意的机制、清点了资源管理的案卷档案、核实了 Manifest 户籍文书,以及检阅了 Gradle 构建衙门的运作规章。这些知识乃构建任何 Android 应用的根基。下一章,我们将进入 Jetpack Compose 的殿堂,勘察现代 Android 界面构建之道。
假设用户正在使用你的 App(Activity 处于 onResume 状态),此时来了一个电话,系统弹出通话界面覆盖了你的 App。请按顺序写出此时 Activity 会依次触发哪些生命周期回调。当用户挂断电话返回你的 App 时,又会依次触发哪些回调?
思考 Activity 从"前台可交互"到"被遮挡不可见"需要经过哪些状态转换。回来时则是反向过程。注意区分"部分遮挡"和"完全不可见"两种情况。
电话来时(Activity 被完全遮挡):onPause → onStop。
挂断电话返回时:onStart → onResume。
若通话界面仅部分遮挡(如画中画模式),则可能只触发 onPause,返回时触发 onResume。
请编写代码,实现以下场景:从 SearchActivity 跳转到 ResultActivity,并传递三项数据——搜索关键词(String)、页码(Int)、是否精确匹配(Boolean)。在 ResultActivity 的 onCreate 中取出这些数据并打印日志。
使用 putExtra 可传递多种基本类型的数据。取出时注意使用对应的 getStringExtra、getIntExtra、getBooleanExtra 方法,且每个 get 方法都需要提供默认值(String 类型除外,返回可空值)。
// SearchActivity 中发送数据
val intent = Intent(this, ResultActivity::class.java).apply {
putExtra("keyword", "Kotlin 教程")
putExtra("page", 1)
putExtra("exact_match", true)
}
startActivity(intent)
// ResultActivity 中接收数据
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_result)
val keyword = intent.getStringExtra("keyword") ?: ""
val page = intent.getIntExtra("page", 1)
val exactMatch = intent.getBooleanExtra("exact_match", false)
Log.d("Result", "关键词=$keyword, 页码=$page, 精确匹配=$exactMatch")
}
你的应用需要支持中文和英文两种语言。请写出 strings.xml 的内容,包含以下三条字符串资源:应用名称(中文"待办清单"、英文"Todo List")、添加按钮文字(中文"新建任务"、英文"New Task")、空状态提示(中文"暂无任务,点击添加"、英文"No tasks yet, tap to add")。同时说明这两个文件应分别放在哪个目录下。
默认语言的 strings.xml 放在 res/values/ 目录下,其他语言版本放在对应的限定符目录下,如英文为 res/values-en/。每个 string 资源通过 name 属性保持一致。
中文文件位于 res/values/strings.xml:
<resources>
<string name="app_name">待办清单</string>
<string name="btn_add">新建任务</string>
<string name="empty_state">暂无任务,点击添加</string>
</resources>
英文文件位于 res/values-en/strings.xml:
<resources>
<string name="app_name">Todo List</string>
<string name="btn_add">New Task</string>
<string name="empty_state">No tasks yet, tap to add</string>
</resources>
以下 AndroidManifest.xml 片段中有三处错误或遗漏,请找出并说明原因:
<manifest package="com.example.myapp">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
假设该应用需要访问网络,并且包含一个 SettingsActivity 页面。
从三个方面勘察:一是权限声明是否完整;二是所有 Activity 是否都已登记在册;三是 Android 12+ 对含 intent-filter 组件的新要求。
错误 1:缺少 <uses-permission android:name="android.permission.INTERNET" />。应用需要访问网络却未声明权限,将无法联网。
错误 2:SettingsActivity 未在 Manifest 中注册。需要添加 <activity android:name=".SettingsActivity" />,否则跳转时会抛出 ActivityNotFoundException。
错误 3:MainActivity 含有 intent-filter 但缺少 android:exported="true" 属性。在 Android 12 (API 31) 及以上版本,这是必须显式声明的,否则应用将无法安装。