第7章:Android 应用基础

从平台架构到四大组件,掌握 Android 开发的核心概念

🏛️

本章导师:狄仁杰

核心方法论:系统分析,层层递进查案

「诸位,今日我们受理一桩大案——Android 系统之全貌。此案牵涉五层机构、四大要员、无数案卷文书。查案讲究层层分析、步步为营,切不可囫囵吞枣。且随本官从底层内核一路勘察到应用之巅,逐级查明每一层的职责与运作之道。待审理完毕,诸位便能洞悉这座庞大体系的运转真相。」

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(如 ActivityViewIntent)皆属此层,好比朝廷六部,是你上达天听、下传旨意的关键枢纽。

四大应用组件

经本官勘察,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 并不保证一定会被调用——好比朝廷突遇战事,公堂可能未及正式散堂便被紧急清场。因此,重要案卷(数据)应在 onPauseonStop 阶段便妥善归档保存,切不可拖延至最后。

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

最佳实践

本官经验之谈:若传递的案卷内容复杂,推荐使用 ParcelableSerializable 接口将证物打包。在现代 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,字号用 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>

本官逐条审理各项要点:

常见错误

本官查办过无数此类疏忽之案:新建的 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 被完全遮挡):onPauseonStop

挂断电话返回时:onStartonResume

若通话界面仅部分遮挡(如画中画模式),则可能只触发 onPause,返回时触发 onResume

练习 2:Intent 传递案卷 进阶

请编写代码,实现以下场景:从 SearchActivity 跳转到 ResultActivity,并传递三项数据——搜索关键词(String)、页码(Int)、是否精确匹配(Boolean)。在 ResultActivityonCreate 中取出这些数据并打印日志。

提示

使用 putExtra 可传递多种基本类型的数据。取出时注意使用对应的 getStringExtragetIntExtragetBooleanExtra 方法,且每个 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" />。应用需要访问网络却未声明权限,将无法联网。

错误 2SettingsActivity 未在 Manifest 中注册。需要添加 <activity android:name=".SettingsActivity" />,否则跳转时会抛出 ActivityNotFoundException

错误 3MainActivity 含有 intent-filter 但缺少 android:exported="true" 属性。在 Android 12 (API 31) 及以上版本,这是必须显式声明的,否则应用将无法安装。

← 上一章 目录 下一章 →