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 的调度
数据传递 通过 Intent 和 Bundle 在组件间传递数据
配置响应 响应屏幕旋转、语言切换等配置变更

二、生命周期详解

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 归属
启动流程 Instrumentation → ATMS → ActivityStarter → ApplicationThread → ActivityThread
UI 链路 setContentView() → PhoneWindow → DecorView → WindowManager → ViewRootImpl → 屏幕
数据传递 Intent + Bundle,显式/隐式启动,Activity Result API
配置变更 默认重建,可通过 configChanges 自行处理或配合 onSaveInstanceState()
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui