7.1 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 等其他要员会在后续案件中逐步登场。
7.2 Activity 与生命周期
Activity 是什么
本官先审理第一桩关键案情:Activity 究竟为何物?简而言之,一个 Activity 就是一个"公堂"——你在手机上所见的每一个界面(登录页、聊天列表、设置页),背后都对应一个 Activity,正如大唐每审理一桩案件,便要开设一次公堂。
若你有 Web 开发经验,可将 Activity 理解为一个"堂审主官"——它负责布置公堂(创建界面)、审理案情(处理用户交互)、并按律令管理整个审理过程的起止(生命周期)。
生命周期回调
查明真相须知:Activity 从开堂到散堂,会经历一系列状态变化。系统如同朝廷典仪官,在每个状态转换时鸣锣通报,调用对应的回调方法。且看此流程图,犹如一份详尽的审理程序案卷:
Activity 生命周期流程:
启动 Activity
│
▼
┌──────────┐
│ onCreate │ ← 初始化:加载布局、绑定数据
└────┬─────┘
▼
┌──────────┐
│ onStart │ ← 可见但尚未获得焦点
└────┬─────┘
▼
┌──────────┐
│ onResume │ ← 前台运行,用户可交互
└────┬─────┘
│
(用户离开/被遮挡)
│
▼
┌──────────┐
│ onPause │ ← 部分可见,失去焦点
└────┬─────┘
▼
┌──────────┐
│ onStop │ ← 完全不可见
└────┬─────┘
▼
┌───────────┐
│ onDestroy │ ← 被销毁,释放所有资源
└───────────┘
此六步如同审案六道程序:onCreate 乃升堂布置之时;onStart 公堂已开、众人就位但尚未正式问案;onResume 惊堂木一拍、审理正式开始;onPause 好比有人上堂递折子,审理暂停片刻;onStop 散堂退朝,公堂已无人可见;onDestroy 则是案件归档、拆除公堂,一切恢复原状。
代码示例:一个简单的 Activity
且看此案卷中的代码实例,记录了一个 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 阶段便妥善归档保存,切不可拖延至最后。
7.3 Intent 与页面跳转
接下来勘察第二桩要案:组件之间如何传递消息?答案便是 Intent(意图)。Intent 是 Android 各组件之间通信的核心机制,犹如大唐朝廷中各衙门之间传递的公文与圣旨——你可以用它来启动 Activity、启动 Service、发送广播,一纸 Intent 便可调动整个系统。
显式 Intent:跳转到指定 Activity
显式 Intent 好比一道点名圣旨,明确指定了目标官员的姓名与衙门。它用于应用内部的页面跳转,目标明确、毫不含糊:
// 从当前 Activity 跳转到 DetailActivity
val intent = Intent(this, DetailActivity::class.java)
startActivity(intent)
隐式 Intent:让系统选择合适的 App
隐式 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, "分享到"))
传递数据:putExtra / getStringExtra
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 组件来统一管理各公堂之间的案卷共享,既规范又高效。
7.4 资源管理
继续勘察下一层线索。Android 将代码与资源分开管理,正如朝廷将律法条文与案卷档案分库存放。资源文件(布局、图片、字符串等)统一归入 res/ 目录之下,编译时系统会自动生成一个 R 类作为索引册,供你随时调阅。
res/ 目录结构
让本官带你勘察这座"案卷档案室"的布局:
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:字符串资源
本官特别提醒:所有面向百姓(用户)的文字告示,都应统一登记在 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>
colors.xml 与 dimens.xml
色彩与尺寸亦有专门的案卷登记簿,统一管理、统一调用:
<!-- 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>
R 类:代码中引用资源
编译器会根据 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"
dp 和 sp 单位
本官还须查明一项重要度量制度:Android 不用 px(像素),而采用密度无关的标准单位,确保在各种屏幕上呈现一致——如同朝廷颁布统一的度量衡:
- dp(Density-independent Pixels):用于布局尺寸和间距。在不同屏幕密度下,1dp 对应不同的实际像素数,确保在各种设备上视觉效果一致。
- sp(Scale-independent Pixels):用于字体大小。在 dp 基础上,还会根据用户的字体大小偏好进行缩放,尊重百姓的个人意愿。
简单记忆
尺寸用 dp,字号用 sp。一般情况下,1dp 在 160dpi 的屏幕上等于 1px。此乃本官查案多年总结的速记口诀。
7.5 AndroidManifest.xml
现在本官要审理一份至关重要的文书: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,它会自动帮你办妥登记手续。
7.6 Gradle 构建系统(Android 视角)
最后一桩待审之案:Gradle 构建系统。它是 Android 工程的总管衙门,负责将你的代码、资源、依赖统统编排成一个可运行的应用。一个典型的 Android 项目有两份关键构建文书:
项目级 build.gradle.kts (Project)
位于项目根目录,如同朝廷通令,配置所有衙门(模块)共用的构建规则:
// build.gradle.kts (Project)
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
模块级 build.gradle.kts (App)
位于 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)
}
SDK 版本参数详解
本官层层分析这三个版本参数——它们如同朝廷法令的适用范围,决定了你的应用能在哪些设备上运行:
| 参数 | 含义 | 建议 |
|---|---|---|
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")
}
版本目录 (libs.versions.toml)
现代 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 界面构建之道。
本章练习
练习 1:生命周期侦查 入门
假设用户正在使用你的 App(Activity 处于 onResume 状态),此时来了一个电话,系统弹出通话界面覆盖了你的 App。请按顺序写出此时 Activity 会依次触发哪些生命周期回调。当用户挂断电话返回你的 App 时,又会依次触发哪些回调?
提示
思考 Activity 从"前台可交互"到"被遮挡不可见"需要经过哪些状态转换。回来时则是反向过程。注意区分"部分遮挡"和"完全不可见"两种情况。
参考答案
电话来时(Activity 被完全遮挡):onPause → onStop。
挂断电话返回时:onStart → onResume。
若通话界面仅部分遮挡(如画中画模式),则可能只触发 onPause,返回时触发 onResume。
练习 2:Intent 传递案卷 进阶
请编写代码,实现以下场景:从 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")
}
练习 3:资源档案管理 入门
你的应用需要支持中文和英文两种语言。请写出 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>
练习 4:Manifest 户籍核查 进阶
以下 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) 及以上版本,这是必须显式声明的,否则应用将无法安装。