Android随笔-Activity

一、Activity 是什么

Activity 是 Android 四大组件(Activity、Service、BroadcastReceiver、ContentProvider)中唯一负责与用户交互的组件。它提供了一个窗口(Window),用于绘制 UI 并接收用户的触摸、按键等输入事件。

1.1 继承体系

复制代码
java.lang.Object
    └─ android.content.Context                    ← 提供应用环境接口
        └─ android.content.ContextWrapper         ← 包装代理模式
            └─ android.view.ContextThemeWrapper   ← 主题支持
                └─ android.app.Activity           ← 核心实现

Activity 间接继承自 Context,因此它具备访问资源、启动其他组件、发送广播等能力。同时它实现了 Window.Callback、KeyEvent.Callback 等接口,负责接收窗口和按键事件。

1.2 核心职责

职责 说明
UI 容器 通过 setContentView() 加载布局,管理 View 树
生命周期管理 系统通过回调方法通知 Activity 状态变化
任务栈管理 参与 Task 和 Back Stack 的调度
数据传递 通过 IntentBundle 在组件间传递数据
配置响应 响应屏幕旋转、语言切换等配置变更

二、生命周期详解

Activity 生命周期由 7 个核心回调 构成,系统在不同状态转换时触发对应方法。

2.1 完整生命周期图

复制代码
                                    ┌─────────────┐
                                    │   onCreate  │ ← 实例创建,初始化 UI
                                    └──────┬──────┘
                                           ▼
                                    ┌─────────────┐
                                    │   onStart   │ ← 可见但不可交互
                                    └──────┬──────┘
                                           ▼
                                    ┌─────────────┐
                                    │  onResume   │ ← 前台,可交互 ★
                                    └──────┬──────┘
                                           │
              ┌────────────────────────────┼────────────────────────────┐
              │  其他 Activity 部分覆盖      │  其他 Activity 完全覆盖      │
              ▼                            ▼                            ▼
        ┌───────────┐               ┌───────────┐                 ┌───────────┐
        │  onPause  │               │  onPause  │                 │           │
        │ (仍可见)   │               └─────┬─────┘                 │           │
        └─────┬─────┘                     ▼                       │           │
              │                     ┌───────────┐                 │           │
              │                     │  onStop   │ ← 完全不可见      │           │
              │                     │ (后台状态)  │                 │           │
              │                     └─────┬─────┘                 │           │
              │                           │                       │           │
              │              ┌────────────┘                       │           │
              │              ▼                                    │           │
              │       ┌───────────┐                             │           │
              │       │ onRestart │ ← 从后台回到前台               │           │
              │       └─────┬─────┘                             │           │
              │             │                                     │           │
              └─────────────┘                                     │           │
                                                                  ▼           │
                                                            ┌───────────┐     │
                                                            │ onDestroy │ ← 销毁
                                                            └───────────┘     │
                                                                              │
        ←─────────────────────────────────────────────────────────────────────┘
                              返回/退出

2.2 各回调方法详解

回调 触发时机 典型操作 调用次数
onCreate() Activity 实例首次创建,PhoneWindow、ContextImpl 赋值完成后 setContentView()、初始化变量、恢复 savedInstanceState 1 次
onStart() Activity 即将进入前台可见状态 注册广播、启动动画 多次
onResume() Activity 进入前台,可接收用户输入 获取焦点、启动相机/定位等需要前台权限的操作 多次
onPause() Activity 失去焦点(被部分覆盖或弹窗) 保存关键数据、停止动画、释放相机 多次
onStop() Activity 完全不可见 注销广播、释放资源、停止定位 多次
onRestart() onStop 状态回到前台时 刷新数据 多次
onDestroy() Activity 被销毁前 释放所有资源、取消网络请求、解绑服务 1 次

2.3 关键状态定义

  • Active/Running:onResume() 后,位于前台,可与用户交互
  • Paused:onPause() 后,失去焦点,但仍部分可见(如被透明/对话框 Activity 覆盖)
  • Stopped:onStop() 后,完全不可见,进入后台
  • Killed/Destroyed:onDestroy() 后,等待 GC 回收

2.4 生命周期回调顺序(典型场景)

场景 A 的生命周期 B 的生命周期
A 启动 B onPause()onStop() onCreate()onStart()onResume()
按返回键从 B 回到 A --- onPause()onStop()onDestroy()
onRestart()onStart()onResume() ---
按 Home 键 onPause()onStop() ---
从最近任务恢复 onRestart()onStart()onResume() ---
屏幕旋转 onPause()onStop()onDestroy()onCreate()onStart()onResume() ---

重要:新 Activity 的 onResume() 总是在旧 Activity 的 onPause() 之后执行。系统保证这两个回调的时序,以确保前台 Activity 始终只有一个。

三、启动模式与任务栈

3.1 Task(任务)与 Back Stack(返回栈)

Task 是用户在执行某项任务时与之交互的 Activity 集合,这些 Activity 按照打开顺序排列在 Back Stack(返回栈) 中。栈顶的 Activity 是当前正在显示的页面,按返回键时栈顶 Activity 出栈并销毁。

3.2 四种启动模式

在 AndroidManifest.xml 中通过 android:launchMode 配置:

启动模式 行为 适用场景
standard(默认) 每次启动都创建新实例,入栈 普通页面
singleTop 栈顶复用:若目标在栈顶,调用 onNewIntent() 而非创建新实例;不在栈顶则新建 通知点击跳转、搜索页面
singleTask 栈内复用:整个任务栈中只有一个实例。启动时若已存在,将其上方所有 Activity 清除,并调用 onNewIntent() 应用首页、WebView 页面
singleInstance 独占一个任务栈,栈中只有该 Activity 一个实例 来电界面、视频通话
3.2.1 singleTask 的 "clearTop" 效果
复制代码
当前栈:A → B → C → D
启动 B(singleTask)后:
结果栈:A → B        (C、D 被清除)
3.2.2 singleTop 的 onNewIntent()

当栈顶复用时,Activity 不会被重建,系统通过 onNewIntent(Intent) 传递新的 Intent:

java 复制代码
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent); // 重要:更新 Intent,否则 getIntent() 仍是旧的
    // 处理新数据
}

3.3 taskAffinity(任务亲和性)

taskAffinity 定义了 Activity 倾向于归属哪个 Task,默认值为应用包名。

单独设置 taskAffinity 不会生效,必须配合以下条件之一:

条件 效果
launchMode="singleTask" Activity 启动时,若不存在对应 affinity 的 Task,则新建 Task
launchMode="singleInstance" 强制新建独立 Task
Intent.FLAG_ACTIVITY_NEW_TASK 配合不同 taskAffinity 开启新 Task
allowTaskReparenting="true" Activity 可从启动它的 Task 迁移到 affinity 匹配的 Task
xml 复制代码
<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.main" />

3.4 Intent Flag(动态控制)

Intent Flag 的优先级高于 Manifest 中的静态配置:

Flag 作用
FLAG_ACTIVITY_NEW_TASK 在新 Task 中启动(通常配合 taskAffinity
FLAG_ACTIVITY_CLEAR_TOP 若目标 Activity 存在,清除其上所有 Activity 并复用目标
FLAG_ACTIVITY_SINGLE_TOP singleTop 效果
FLAG_ACTIVITY_CLEAR_TASK 清除整个 Task,目标 Activity 作为新根
FLAG_ACTIVITY_NO_HISTORY 启动后不保留在栈中

典型用法:退出登录后跳转登录页

kotlin 复制代码
val intent = Intent(this, LoginActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)

四、Activity 启动流程(Framework 层)

复制代码
用户点击图标 / startActivity()
    │
    ▼
Instrumentation.execStartActivity()        ← 应用进程
    │
    ▼
ActivityTaskManagerService.startActivity() ← Binder IPC 到 system_server
    │
    ▼
ATMS 进行权限检查、Intent 解析、启动模式计算
    │
    ▼
ActivityStarter.execute()                  ← 决定如何启动(新实例?复用?新 Task?)
    │
    ▼
RootWindowContainer / Task 管理栈状态
    │
    ▼
如果需要创建新进程:
    ActivityManagerService.startProcessLocked()
    Zygote fork 新进程
    │
    ▼
新进程启动后:
    ActivityThread.main() → Looper.prepare() → Looper.loop()
    attachApplication() 到 AMS
    │
    ▼
ATMS 调度 Activity 启动
    │
    ▼
ApplicationThread.scheduleTransaction()    ← Binder 回调到应用进程
    │
    ▼
ClientTransactionHandler.handleLaunchActivity()
    │
    ▼
Instrumentation.newActivity()              ← 反射创建 Activity 实例
    │
    ▼
Activity.attach()                          ← 创建 PhoneWindow、WindowManager
    │
    ▼
Instrumentation.callActivityOnCreate()     ← 调用 onCreate()
    │
    ▼
Activity.onCreate() → setContentView()
    │
    ▼
WindowManager.addView(decorView)           ← 将 DecorView 添加到 WMS
    │
    ▼
Choreographer 触发 VSync → 测量/布局/绘制 → SurfaceFlinger 合成 → 显示

关键关联点

  • Instrumentation 是 Activity 启动的"指挥棒"
  • ApplicationThread 是应用进程与 system_server 通信的 Binder 代理
  • ActivityThread 是应用进程的主线程入口
  • PhoneWindow 在 Activity.attach() 中被创建

五、Activity 与 Window / View 的关系

5.1 三者关系

复制代码
Activity
    ├── PhoneWindow(Window 的实现类)
    │       ├── DecorView(FrameLayout,根 View)
    │       │       ├── TitleBar(系统标题栏,可隐藏)
    │       │       └── ContentParent(FrameLayout,id = android.R.id.content)
    │       │               └── 你的布局(通过 setContentView 添加)
    │       │
    │       └── WindowManager(管理窗口的添加、更新、移除)
    │
    └── Window.Callback(接收窗口事件,如 dispatchTouchEvent)

5.2 setContentView() 到底做了什么

java 复制代码
// Activity.setContentView()
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);  // 委托给 PhoneWindow
    initWindowDecorActionBar();
}

// PhoneWindow.setContentView()
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();  // 1. 创建 DecorView + ContentParent
    }
    // 2. 将用户布局 inflate 到 ContentParent 中
    mLayoutInflater.inflate(layoutResID, mContentParent);
}

流程拆解

  1. installDecor():若 DecorView 不存在,创建它(加载系统布局 screen_simple.xml 或 screen_title.xml)
  2. 找到 ContentParent(id 为 R.id.content 的 FrameLayout)
  3. 将用户的布局文件 inflate 到 ContentParent 中

5.3 从 onCreate 到显示在屏幕上

复制代码
onCreate() 中调用 setContentView()
    │
    ▼
布局被 inflate 到 ContentParent,但尚未测量/布局
    │
    ▼
onResume() 执行完毕
    │
    ▼
Activity.makeVisible() 被调用
    │
    ▼
mDecor.setVisibility(View.VISIBLE)
    │
    ▼
WindowManager.addView(mDecor, params)  ← 第一次真正将 DecorView 添加到 WMS
    │
    ▼
ViewRootImpl 被创建,执行 requestLayout()
    │
    ▼
performTraversals():measure → layout → draw
    │
    ▼
Surface 锁定 Canvas → Skia/OpenGL 绘制 → SurfaceFlinger 合成 → 屏幕显示

关键 :setContentView() 只是将布局加载到内存中的 View 树,真正的测量、布局、绘制发生在 onResume() 之后、Window 首次变为可见时。

六、数据传递与返回

6.1 显式启动

kotlin 复制代码
val intent = Intent(this, TargetActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)

6.2 隐式启动

通过 Action、Category、Data 匹配:

xml 复制代码
<activity android:name=".ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
kotlin 复制代码
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "分享内容")
}
startActivity(intent)

6.3 带返回值的启动

kotlin 复制代码
// A 启动 B
val intent = Intent(this, BActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)  // 已废弃,推荐使用 Activity Result API

// B 返回数据
val resultIntent = Intent().apply {
    putExtra("result", "data")
}
setResult(Activity.RESULT_OK, resultIntent)
finish()

// A 接收返回
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
        val result = data?.getStringExtra("result")
    }
}

现代推荐方式(Activity Result API)

kotlin 复制代码
private val launcher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        val data = result.data?.getStringExtra("result")
    }
}

// 使用
launcher.launch(Intent(this, BActivity::class.java))

七、配置变更与状态保存

7.1 配置变更类型

  • 屏幕旋转(orientation)
  • 键盘可用性变化(keyboardHidden)
  • 屏幕尺寸变化(screenSize)
  • 夜间模式切换(uiMode)
  • 语言切换(locale)

默认情况下,配置变更会导致 Activity 销毁并重建(走完整的 onDestroy() → onCreate() 流程)。

7.2 状态保存与恢复

java 复制代码
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("edit_text", editText.getText().toString());
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        String text = savedInstanceState.getString("edit_text");
        editText.setText(text);
    }
}

保存时机 :onSaveInstanceState() 在 onStop() 之前调用(API 28+ 在 onStop() 之后),用于保存瞬时 UI 状态

恢复时机:onCreate(Bundle) 或 onRestoreInstanceState(Bundle)。

7.3 自行处理配置变更

若不想重建 Activity,可在 Manifest 中声明:

xml 复制代码
<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize|keyboardHidden" />

此时配置变更时,系统不重建 Activity,而是调用:

java 复制代码
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // 手动处理横屏布局
    }
}

八、Activity 的注册要求

Activity必须在AndroidManifest.xml中声明,否则启动时会抛出ActivityNotFoundException。

xml 复制代码
<manifest ...>
    <application ...>
        <!-- 入口 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=".SecondActivity" />
    </application>
</manifest>
  • MAIN + LAUNCHER:标识应用的入口 Activity,桌面图标点击后启动
  • exported="true"(Android 12+ 强制要求):允许外部应用启动该 Activity

九、总结

维度 核心要点
本质 用户交互的窗口容器,管理 View 树和生命周期
生命周期 7 个核心回调,掌握状态转换和时序关系
启动模式 4 种模式控制实例复用和栈行为,taskAffinity 控制 Task 归属
启动流程 InstrumentationATMSActivityStarterApplicationThreadActivityThread
UI 链路 setContentView()PhoneWindowDecorViewWindowManagerViewRootImpl → 屏幕
数据传递 Intent + Bundle,显式/隐式启动,Activity Result API
配置变更 默认重建,可通过 configChanges 自行处理或配合 onSaveInstanceState()
相关推荐
码农coding3 小时前
android 12 中的VSYNC的请求
android
YF02113 小时前
详解Android所有文件访问权限
android
时间的拾荒人5 小时前
MySQL C语言连接 - 从入门到实战
android·c语言·mysql
Meteors.5 小时前
Android性能优化:01. 指标体系 + 分析工具链
android
jike_20266 小时前
安卓平台免费录音转文字工具深度测评:5 款 APP 功能对比与选型指南
android·智能电视
Code Man6 小时前
Windows 下使用 Appium
android·windows·appium
码农coding7 小时前
android 12 中的VSYNC接收
android
GitLqr7 小时前
Flutter 3.44 性能飞跃:深度解析 Android Platform View 的 HCPP 新特性
android·flutter·性能优化
码农coding7 小时前
android 12 SurfaceFlinger中的CompositionEngine
android